<?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>DEV Community: Olabode Lawal-Shittabey</title>
    <description>The latest articles on DEV Community by Olabode Lawal-Shittabey (@babblebey).</description>
    <link>https://dev.to/babblebey</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%2F375884%2Fb6668e67-9ac0-4982-b6c3-742564bd4242.png</url>
      <title>DEV Community: Olabode Lawal-Shittabey</title>
      <link>https://dev.to/babblebey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/babblebey"/>
    <language>en</language>
    <item>
      <title>Continuous AI Maturity Model: Where Do You Stand in the CAI Adoption?</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Mon, 22 Sep 2025 17:27:18 +0000</pubDate>
      <link>https://dev.to/babblebey/continuous-ai-maturity-model-where-do-you-stand-in-the-cai-adoption-22n2</link>
      <guid>https://dev.to/babblebey/continuous-ai-maturity-model-where-do-you-stand-in-the-cai-adoption-22n2</guid>
      <description>&lt;p&gt;I was talking with Brian (&lt;a class="mentioned-user" href="https://dev.to/bdougieyo"&gt;@bdougieyo&lt;/a&gt;) and said I thought I was already at a higher level in the &lt;strong&gt;"Continuous AI maturity model"&lt;/strong&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%2Fdqpriuh1cdt2q2870fdo.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%2Fdqpriuh1cdt2q2870fdo.png" alt=" " width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I kinda bragged about it. But then I stopped to think harder. Am I really there?&lt;/p&gt;

&lt;p&gt;That question is what led me to write this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Continuous AI Maturity Model?
&lt;/h2&gt;

&lt;p&gt;The idea of maturity models isn’t new. Frameworks like the &lt;strong&gt;AI Maturity Model&lt;/strong&gt; have long been used to describe how organizations grow in their use of artificial intelligence, moving from early experiments to full integration.&lt;/p&gt;

&lt;p&gt;The first place I came across a &lt;strong&gt;Continuous AI Maturity Model&lt;/strong&gt; was in &lt;a class="mentioned-user" href="https://dev.to/bekahhw"&gt;@bekahhw&lt;/a&gt;’s article - &lt;a href="https://blog.continue.dev/what-is-continuous-ai-a-developers-guide?utm_source=dev.to/babblebey"&gt;A Developer's Guide to Continuous AI&lt;/a&gt;. She framed it as a way to understand how developers and teams build up their use of Continuous AI over time. It gives us a shared language for naming where we are today and spotting what the next step might be in the adoption of Continuous AI.&lt;/p&gt;

&lt;p&gt;So what does this look like in practice? The model breaks down into levels, each with its strengths and limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1 — Manual AI assistance
&lt;/h2&gt;

&lt;p&gt;This is where most developers are today. You copy some code or error into ChatGPT or another tool. You ask a question. You get back a function or a fix. It saves time.&lt;/p&gt;

&lt;p&gt;The strength here is speed. You can solve problems faster. You can move past blockers. But the limit is clear too. You only get value when you remember to ask. The help is disconnected from your workflow. Nothing repeats itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples of level 1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Writing a test only when you ask the AI to write one&lt;/li&gt;
&lt;li&gt;Asking for a regex when you can’t remember the pattern&lt;/li&gt;
&lt;li&gt;Copying error logs into the AI to get a fix suggestion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Level 2 — Workflow automation
&lt;/h2&gt;

&lt;p&gt;Here AI starts to live inside the work itself. It takes care of repeatable tasks while you still keep oversight. The workflow runs every time, not just when you think to ask.&lt;/p&gt;

&lt;p&gt;The strength here is consistency. Everyone benefits from the same automation. The limit is trust. You still need to review and guide the AI. It can make mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples of level 2
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI adds missing documentation during a pull request review&lt;/li&gt;
&lt;li&gt;AI suggests changes for style and small bugs directly in the PR&lt;/li&gt;
&lt;li&gt;AI updates a ticket when a branch is merged&lt;/li&gt;
&lt;li&gt;AI generates tests when new code is pushed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Level 3 — Zero intervention workflows
&lt;/h2&gt;

&lt;p&gt;Here AI completes a process end to end with no human input. This is only safe today for narrow and low-risk workflows.&lt;/p&gt;

&lt;p&gt;The strength here is scale. Work happens even when no one touches it. The limit is scope. You can’t trust AI to handle complex or high-risk work on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples of level 3
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI merges dependency updates after tests pass&lt;/li&gt;
&lt;li&gt;AI keeps a changelog updated without review&lt;/li&gt;
&lt;li&gt;AI closes stale issues with a clear response&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;Not every developer or team is in the same place. Some are still mostly level 1. Others are testing level 2. Very few are ready for level 3. This model gives us a way to name where we are. It sets the tone for what we can aim at next without overpromising.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving up the levels
&lt;/h2&gt;

&lt;p&gt;Moving from level 1 to level 3 in one leap is not realistic. Progression is what matters. Each step builds on what you’ve already learned.&lt;/p&gt;

&lt;p&gt;Level 1 is where you get familiar with how your AI system behaves. You see its coding style when given your project context. You notice the way it writes documentation. You start to recognize patterns when you ask it to repeat the same tasks. At this stage, the key is learning how the AI works and where it fits.&lt;/p&gt;

&lt;p&gt;Level 2 is when you take those patterns and set them into rules. Instead of reminding the AI every single time, you define the standards. You write down how you want code to be generated. You capture your preferred style for documentation. You build recipes that can run automatically or manually in place of those repeatable workflows.&lt;/p&gt;

&lt;p&gt;A simple example from my own use: I have told my AI agent again and again to interact with GitHub through the CLI, not by trying to read the web page. When writing issues, pull requests, or comments, I ask it to create the content in a temporary markdown file and use the content as value for &lt;code&gt;--body-file&lt;/code&gt; flag with the CLI, deleting the temporary markdown file after. This avoids bad inline formatting and keeps the output clean. Instead of repeating this instruction every time, I can set it once as a rule in the assistant’s configuration or in something like an AGENTS.md file, that's some new convention now.&lt;/p&gt;

&lt;p&gt;All of this compounds in level 2. The more rules you set, the more the system adapts. If you’re using a tool like &lt;a href="https://continue.dev?utm_source=dev.to/babblebey"&gt;Continue&lt;/a&gt; that &lt;a href="https://blog.continue.dev/its-time-to-collect-data-on-how-you-build-software/" rel="noopener noreferrer"&gt;collects development data&lt;/a&gt; as you build, you get even more leverage. It starts to feel like the assistant is learning with you, while you also fine-tune it with your rules to match your taste.&lt;/p&gt;

&lt;p&gt;Level 3 becomes possible only after enough usage at level 2. By then you can measure something important: &lt;strong&gt;the intervention rate&lt;/strong&gt;. This is how often you still need to step in and fix the AI’s output. If the rate is high, you are not ready. But if over time the rate drops, because your rules are solid and the assistant is using project data well, then you have a system that can safely run end to end workflows without oversight.&lt;/p&gt;

&lt;p&gt;The key is that level 3 is not magic. It only works because you built the foundation through repeated use, feedback, and rules at level 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflection
&lt;/h2&gt;

&lt;p&gt;When I told Brian I was at level 2, maybe even 3 haha, I wanted to believe it. But the truth is I’m still mostly at level 1 with some bits of level 2 sprinkled in. I prompt the AI, I have a few rules, but I still clean things up often.&lt;/p&gt;

&lt;p&gt;And that’s okay. The point isn’t to climb as fast as possible. It’s to know where you are, notice your improvements, and keep building. Each level builds on the one before. The more you use AI with care, the more you prepare it — and yourself — for higher levels of maturity.&lt;/p&gt;

&lt;p&gt;The Continuous AI Maturity Model isn’t about chasing some end state. It’s about knowing where you stand, what’s working for you, and what step makes sense next.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>software</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Why Continuous AI Matters for Developers and Teams</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Thu, 11 Sep 2025 17:07:50 +0000</pubDate>
      <link>https://dev.to/babblebey/why-continuous-ai-matters-for-developers-and-teams-1h6b</link>
      <guid>https://dev.to/babblebey/why-continuous-ai-matters-for-developers-and-teams-1h6b</guid>
      <description>&lt;p&gt;“Will AI replace developers?”, it's a debate we've all heard and It’s old-tuned by now. But the sharper take is this: “&lt;em&gt;Developers who learn to harness AI will replace the ones who don’t&lt;/em&gt;”. The future isn’t about competing with AI but collaborating with it; and the clearest way that’s happening today is through Continuous AI.&lt;/p&gt;

&lt;p&gt;Picture this: You’re deep in flow, building out a new feature. The code is coming together nicely, but haha you know what’s waiting for you; tests to write, docs to update, and a PR review cycle that might drag out for days. You sigh, because while these steps are necessary, they also pull you out of the creative momentum you’re in.&lt;/p&gt;

&lt;p&gt;Now imagine instead that while you code, an AI agent is already generating the unit tests, updating the README, and lining up a polished PR draft; all without you leaving your editor. By the time you’re done, your work isn’t just complete, it’s complete with everything.&lt;/p&gt;

&lt;p&gt;That’s the kind of shift Continuous AI (CAI) brings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gains For Indie Developers: Becoming Amplified
&lt;/h2&gt;

&lt;p&gt;For individual developers, Continuous AI feels less like a tool, and more like a collaborator  in your workflow. It takes care of the repetitive and predictable tasks, leaving you free to focus on solving harder problems and exploring creative solutions &lt;a href="https://www.linkedin.com/posts/babblebey_hacktoberfest-activity-7372236261559996416-Yxf5?utm_source=dev.to/babblebey"&gt;while you grab something to eat&lt;/a&gt; ;). You move faster, with less context-switching, and with a safety net that catches the details you might otherwise miss.&lt;/p&gt;

&lt;p&gt;This idea closely ties into what’s described at &lt;a href="https://amplified.dev?utm_source=dev.to/babblebey"&gt;amplified.dev&lt;/a&gt;; the vision of transforming everyday developers into &lt;em&gt;Amplified Developers&lt;/em&gt;: professionals who don’t just write code, but orchestrate AI-powered workflows to multiply their impact. It’s a future where your productivity isn’t limited by how much you can type in a day, but by how well you design and guide these amplified systems.&lt;/p&gt;

&lt;p&gt;I personally &lt;a href="https://github.com/continuedev/amplified.dev/pull/83" rel="noopener noreferrer"&gt;endorse&lt;/a&gt; this vision and encourage you to check it out. If it resonates with you, consider joining the supporters by &lt;a href="https://github.com/continuedev/amplified.dev/edit/main/supporters.md" rel="noopener noreferrer"&gt;adding your name&lt;/a&gt; to the growing community of developers shaping what it means to be amplified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gains For Teams: Shared Intelligence at Scale
&lt;/h2&gt;

&lt;p&gt;For teams, Continuous AI doesn’t just boost individual productivity, it completely transforms collaboration. Imagine every teammate having access to the same AI-augmented workflows: consistent PR/code reviews, standardized test generation, and shared prompts that ensure everyone is on the same page. Instead of fragmented one-off uses, the whole team benefits from a &lt;strong&gt;unified intelligence layer&lt;/strong&gt; built into the project.&lt;/p&gt;

&lt;p&gt;This is already happening in two key ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inside the editor (local dev environments):&lt;/strong&gt; Tools like &lt;a href="https://continue.dev?utm_source=dev.to/babblebey"&gt;Continue&lt;/a&gt; give teams a consistent CAI experience directly in their IDEs. Teams can define custom agents with project-specific rules, shared prompts, and ensure everyone benefits from the same context-aware AI workflows. This keeps collaboration tight and reduces knowledge silos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;In the pipeline (infrastructure + actions):&lt;/strong&gt; GitHub is experimenting with &lt;a href="https://github.com/actions/ai-inference" rel="noopener noreferrer"&gt;AI-inference Actions&lt;/a&gt; by coupling &lt;strong&gt;GitHub Actions&lt;/strong&gt; with &lt;strong&gt;GitHub Models&lt;/strong&gt; and the prototype &lt;a href="https://githubnext.com/projects/agentic-workflows?utm_source=dev.to/babblebey"&gt;GitHub Agentic Workflows&lt;/a&gt;. This powers things like automated PR reviews, continuous documentation, intelligent issue triage, dependency updates and even more complex flows. The AI becomes part of the team’s CI/CD backbone, continuously working in the background to reduce manual overhead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Teams spend less time coordinating and more time building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Gain: Flow
&lt;/h2&gt;

&lt;p&gt;The biggest win isn’t just productivity; it’s &lt;strong&gt;flow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Flow is when you’re immersed in the work, ideas connect easily, and collaboration feels natural. Continuous AI removes the stop-start friction of coding, testing, documenting, and reviewing. It gives developers space for creativity, confidence, and joy in their craft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Continuous AI
&lt;/h2&gt;

&lt;p&gt;This isn’t a far-off vision, it’s already here. For indie developers, tools like &lt;a href="https://microsoft.github.io/genaiscript/" rel="noopener noreferrer"&gt;GenAIScript&lt;/a&gt; lets you write workflows and run them locally or on GitHub Actions. Tools like &lt;a href="https://continue.dev?utm_source=dev.to/babblebey"&gt;Continue&lt;/a&gt; work great for individuals and teams alike, embedding CAI directly into your editor. Larger teams can also look to GitHub’s AI-powered Actions and Models to bring intelligent automation into their CI pipelines.&lt;/p&gt;

&lt;p&gt;If you’re not sure where to begin, the &lt;a href="https://github.com/githubnext/awesome-continuous-ai" rel="noopener noreferrer"&gt;Awesome Continuous AI&lt;/a&gt; repo is a great place to discover tools, frameworks, and experiments that push the space forward. And if this idea of Continuous AI feels new, you might want to check out my first post — &lt;a href="https://dev.to/babblebey/continuous-ai-a-simple-introduction-3k10"&gt;Continuous AI: A Simple Introduction&lt;/a&gt; — for the basics before diving deeper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Start small, automate one repetitive task and let your Continuous AI journey grow from there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tooling</category>
      <category>llm</category>
      <category>github</category>
    </item>
    <item>
      <title>Continuous AI: A Simple Introduction</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Tue, 26 Aug 2025 12:37:32 +0000</pubDate>
      <link>https://dev.to/babblebey/continuous-ai-a-simple-introduction-3k10</link>
      <guid>https://dev.to/babblebey/continuous-ai-a-simple-introduction-3k10</guid>
      <description>&lt;p&gt;When &lt;strong&gt;Continuous Integration and Continuous Delivery (CI/CD)&lt;/strong&gt; first emerged, it completely transformed how teams ship software. Software releases became predictable, automated, and reliable. CI/CD didn’t just add speed; it changed the way developers thought about building and shipping altogether.&lt;/p&gt;

&lt;p&gt;We’re now at a similar turning point with AI in software development. Instead of treating AI tools as something we copy-paste into when we get stuck, we can start thinking of them as part of the continuous developer workflow, embedded directly in our editors, always aware of our context, and ready to automate repetitive tasks. This is the promise of &lt;strong&gt;Continuous AI (ContinuousAI)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Continuous AI?
&lt;/h2&gt;

&lt;p&gt;The term Continuous AI was first introduced by the &lt;a href="https://githubnext.com/projects/continuous-ai/?ref=dev.to/babblebey"&gt;GitHub Next team&lt;/a&gt; to describe a shift in how AI fits into the developer workflow. To understand it, let’s first look at how most developers use AI today:&lt;/p&gt;

&lt;p&gt;You copy a snippet of code, paste it into ChatGPT (or another AI tool), ask it for help, then copy the result back into your editor. It’s useful, but it’s fragmented; disconnected from the actual flow and context of development.&lt;/p&gt;

&lt;p&gt;Now compare that to CI/CD (Continuous Integration and Continuous Delivery). CI/CD brought automation directly into the pipeline. Every commit triggers tests, builds, and deployments automatically. Developers don’t need to leave their workflow; it’s baked into the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous AI (ContinuousAI)&lt;/strong&gt; applies that same mindset to AI. Instead of being an occasional side tool, AI becomes a continuous part of your development loop, integrated into your editor, aware of your project context, and able to run repeatable, sharable workflows.&lt;/p&gt;

&lt;p&gt;At its core, Continuous AI rests on four principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context Awareness&lt;/strong&gt; – AI doesn’t just see isolated snippets; it understands your codebase, diffs, terminal outputs, configuration and documentations to mention a few.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Seamless Integration&lt;/strong&gt; – No more copy-pasting between tools; the AI lives where you work, inside your IDE or development Pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repeatable Workflows&lt;/strong&gt; – Like CI pipelines, you can define reusable “recipes” (e.g., write tests for this diff, generate docs, fix errors, review pull requests), ensuring tasks are consistent and reproducible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extensibility&lt;/strong&gt; – You can plug in different AI models and customize rules, context providers, and prompts to fit your workflow, just like extending a CI/CD pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Continuous AI is about amplification. Just as CI/CD automated the mechanics of shipping code, Continuous AI extends that automation with intelligence. It makes AI a collaborator in the workflow, spotting issues earlier, suggesting improvements in real time, and helping teams move faster with confidence. Developers stay in the driver’s seat, now supported by a co-pilot that evolves with the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of Continuous AI in Action
&lt;/h2&gt;

&lt;p&gt;Continuous AI isn't just about one-off prompts, it’s about creating workflows that consistently run intelligent automation. Here are some great real-world use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smarter Code Reviews&lt;/strong&gt; - Instead of manually catching small issues or style inconsistencies, AI agents can review pull requests in real-time, flagging bugs, suggesting improvements, or even generating patches. Tools like &lt;a href="https://www.coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; and GitHub Copilot Code Review are already doing this by embedding into GitHub workflows to keep quality checks continuous.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End-to-End Task Automation&lt;/strong&gt; - AI agents can take on multi-step workflows like fixing a bug, running tests, updating related documentation, and opening a PR with little to no human-oversight. Frameworks like &lt;a href="https://continue.dev?utm_source=dev.to/babblebey"&gt;Continue&lt;/a&gt; and GitHub Copilot Agent show how this continuous, agentic approach reduces repetitive coordination and keeps developers focused on creative problem-solving.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Continuous Documentation&lt;/strong&gt; - Documentation doesn’t have to lag behind code. AI can keep READMEs, API references, and changelogs in sync with the codebase, reducing the common “stale docs” problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context-Aware Test Generation&lt;/strong&gt; - Instead of manually writing tests for each new feature, AI can generate them on the fly, adapting as code evolves.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These workflows elevate AI from “help me write this one time” to “help me automatically every time” by making outputs context-aware (i.e. understanding the code, files, to even issues in your repository) while also being integrated directly into pipelines so they trigger on commits, or pull requests, and ensuring they remain consistent and shareable so every developer on the team benefits from the same automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here so...
&lt;/h2&gt;

&lt;p&gt;Continuous AI moves you from those one-off AI interactions to a rhythm where AI actively supports your work, day in and day out. Instead of treating it like a quick helper, you integrate it as part your flow, handling repetitive or structured tasks with consistency. This shifts your attention from the busywork to the parts of the job that need real human creativity and decision-making.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Started with Continuous AI Tools
&lt;/h2&gt;

&lt;p&gt;The best way to understand Continuous AI is to try it out. Many tools are already available to developers, from lightweight assistants that help you debug code as you write, to more advanced frameworks that can automate repetitive workflows. Exploring these tools doesn’t require overhauling your entire setup; start small, experiment with one that aligns with your daily tasks, and see how it changes the way you work.&lt;/p&gt;

&lt;p&gt;A great place to start is the &lt;a href="https://github.com/githubnext/awesome-continuous-ai" rel="noopener noreferrer"&gt;Awesome Continuous AI&lt;/a&gt; repo, which curates a wide collection of Continuous AI tools and frameworks you can experiment with. Pick one, play around, and start carving your own path into Continuous AI.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>llm</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Building jargons.dev [#7]: The Word Editor Script</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Fri, 06 Dec 2024 13:43:26 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-7-the-word-editor-script-od0</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-7-the-word-editor-script-od0</guid>
      <description>&lt;p&gt;Remember the "&lt;a href="https://dev.to/babblebey/building-jargonsdev-3-the-word-editor-22ee"&gt;The Word Editor&lt;/a&gt;"!? Here's the script responsible that implements it's end-to-end functionalities that allows for writing changes via the user interface to a user's forked repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Functional Breakdown
&lt;/h2&gt;

&lt;p&gt;The Word Editor empowered by the script should perform two (2) functions, taking some certain steps&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write New Word - basically to add new word to the dictionary; does this in the following steps...

&lt;ul&gt;
&lt;li&gt;Get an already established word template (.md) file&lt;/li&gt;
&lt;li&gt;Fill template placeholder with collected word &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt; to create a word.mdx file in the appropriate words directory &lt;code&gt;src/pages/browse&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and commit the change to an established change branch/ref on the user's forked repository&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Edit/Update Existing Words - modify existing word in the dictionary, it does this in the following steps...

&lt;ul&gt;
&lt;li&gt;Get an existing word from the user's fork of jargons.dev (looking in the words directory &lt;code&gt;src/pages/browse&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Parse its content and make necessary edits&lt;/li&gt;
&lt;li&gt;and and commit the change to an established change branch/ref on the user's forked repository&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The breakdown inspired creation of the following helper and utility functions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;writeNewWord&lt;/code&gt; - a function that accepts the word &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt; amongst others, leveraging the user's GitHub authenticated instance, it  perform a write operation i.e. writing a new file (word.mdx) to a fork of jargons.dev on the user's account on their behalf through the "PUT /repos/{owner}/{repo}/contents/{path}" endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getExistingWord&lt;/code&gt; - a function that simply retrieves the content of an existing word file on the user's forked repository, with the aim of availing it for edit. It does this by taking the word as argument and concatenates it in the &lt;code&gt;path&lt;/code&gt; param (example &lt;code&gt;src/pages/browse/${normalizeAsUrl(word)}.mdx&lt;/code&gt;) of the request it makes to the endpoint "GET /repos/{owner}/{repo}/contents/{path}"; It is important to state that I had to make a few adjustments to the returned data from this helper for consumption reason, the adjustments are as followed&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;title&lt;/code&gt; property: the &lt;code&gt;response.data&lt;/code&gt; object which comes from the query to the endpoint "GET /repos/{owner}/{repo}/contents/{path}" doesn't have a &lt;code&gt;title&lt;/code&gt; property (this is the word itself);&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;content_decoded&lt;/code&gt; property: the &lt;code&gt;response.data.content&lt;/code&gt; property holds the main content of the retrieved word, BUT it comes in a "base64" format; so I thought it'd be nice if the functional avails it in the consumption-ready format which can be use immediately without the need to convert at consumption. These I did with code below...&lt;br&gt;
&lt;/p&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content_decoded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;updateExistingWord&lt;/code&gt; - with an initial name of &lt;code&gt;editExistingWord&lt;/code&gt; and changed to current name in &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/34/" rel="noopener noreferrer"&gt;jargonsdev/jargons.dev#34&lt;/a&gt;, this function performs similar operation with the &lt;code&gt;writeNewWord&lt;/code&gt; but it over-writes existing word content in a specific file by replacing the file with another file with updated content. This is also done via user's account on their behalf through the "PUT /repos/{owner}/{repo}/contents/{path}" endpoint.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;writeFileContent&lt;/code&gt; - this helper as implied in its name does one thing — it writes file content for words which is submitted in requests made by both &lt;code&gt;writeNewWord&lt;/code&gt; and &lt;code&gt;updateExistingWord&lt;/code&gt; to the GitHub API, it does this by taking a word title and content (i.e. word definition) as variable and generates a content from a template avail to it replacing placeholder contents in it.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  The PR
&lt;/h1&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/18" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement `word-editor` script
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#18&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/18" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 02, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull request implement the &lt;code&gt;word-editor&lt;/code&gt; script; the primary functionality of this script is to allowing adding new word, retrieve and update existing word which are individual &lt;code&gt;.mdx&lt;/code&gt; files residing in the &lt;code&gt;src/pages/browse&lt;/code&gt; directory of the project. This script avails us of all the helper functions required to perform this operations.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Implemented the &lt;code&gt;writeNewWord&lt;/code&gt; function - this function takes 3 params namely the &lt;code&gt;userOctokit&lt;/code&gt;, &lt;code&gt;forkedRepoDetails&lt;/code&gt;, and the &lt;code&gt;word&lt;/code&gt;; it leverages the &lt;code&gt;userOctokit&lt;/code&gt; instance to perform a write operation i.e. writing a new file (newWord.mdx) to a fork of our project on the user's account on behalf of the user through the &lt;code&gt;"PUT /repos/{owner}/{repo}/contents/{path}"&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;Impelemented the &lt;code&gt;getExistingWord&lt;/code&gt; function - this function helps retrieve data of existing words in the fork of our project on the user's account by calling the &lt;code&gt;"GET /repos/{owner}/{repo}/contents/{path}"&lt;/code&gt; endpoint; it returns an object which carries the following properties that we are mostly interested in...
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; - title of the the existing word - this infact is a custom appended data to the &lt;code&gt;response.data&lt;/code&gt; from the call made to the endpoint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;path&lt;/code&gt; - path to the existing word file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sha&lt;/code&gt; - unique SHA of the existing word&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;editExistingWord&lt;/code&gt; function - this function takes 3 params namely the &lt;code&gt;userOctokit&lt;/code&gt;, &lt;code&gt;forkedRepoDetails&lt;/code&gt;, and the &lt;code&gt;word&lt;/code&gt; (holds the properties: &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;sha&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt;); it leverages the &lt;code&gt;userOctokit&lt;/code&gt; instance to perform a edit operation i.e. updating the existing file on a fork of our project on the user's account on behalf of the user through thesame &lt;code&gt;"PUT /repos/{owner}/{repo}/contents/{path}"&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;Implemented &lt;code&gt;writeFileContent&lt;/code&gt; helper function - this function help write a content for our dictionary word file generating them from another added constant in the &lt;code&gt;src/lib/template/word.md.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast/Screenshot&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;too lazy to record a screencast for this one&lt;/em&gt; 😜, &lt;em&gt;but trust me&lt;/em&gt; 🤞 &lt;em&gt;the shit works&lt;/em&gt; 😮‍💨&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/18" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building jargons.dev [#6]: The Branch Script</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Tue, 19 Nov 2024 14:23:27 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-6-the-branch-script-4m3m</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-6-the-branch-script-4m3m</guid>
      <description>&lt;p&gt;This should be really short haha 😃, considering the branch script is the smallest of them (The Scripts) all.&lt;/p&gt;

&lt;p&gt;The Branch Script's primary assignment is to hold all helper functions that can be used to perform operation related to References (aka Branch) on the GitHub APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;

&lt;p&gt;Noting the primary responsibility of the branch script which is &lt;br&gt;
to create a reference/branch of repo (in this case would be the jargons.dev repo fork) on an authenticated user's account. I got to work quickly by doing the following...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Implemented the main &lt;code&gt;createBranch&lt;/code&gt; function in the branch script which accepts necessary param to perform the following operation in the stated order...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It initially fetches the forked repository main branch where we wished to create the new branch off of; it does this using the &lt;code&gt;getBranch&lt;/code&gt; helper (a function that was created in the &lt;a href="https://dev.tourl"&gt;fork script&lt;/a&gt;); this return the SHA of the branch&lt;/li&gt;
&lt;li&gt;Then makes a POST request to the endpoint "/repos/{owner}/{repo}/git/refs" to create the new branch; passing in the full name of the user's fork, the head branch SHA and the name of the new branch we wish to create.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I also took the initiative of moving the &lt;code&gt;getBranch&lt;/code&gt; helper function from the fork script to a the branch script — feels more like home 😉.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The PR
&lt;/h2&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/17" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement `branch` creation script
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#17&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/17" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 01, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull request implements the branch creation script whose primary function is to create a new branch from the forked repository for an authenticated user.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Implemented the main &lt;code&gt;createBranch&lt;/code&gt; function in the &lt;code&gt;branch&lt;/code&gt; script at &lt;code&gt;src/lib/branch.js&lt;/code&gt;; this function takes in 3 params;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;userOctokit&lt;/code&gt; - a user authenticated instance of octokit that can be used to perform action on user's behalf&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repoDetails&lt;/code&gt; - the user's forked repo details; hold the &lt;code&gt;repoFullname&lt;/code&gt; and &lt;code&gt;repoMainBranchRef&lt;/code&gt; as properties in the object type param&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;newBranchName&lt;/code&gt; - name of the branch that will be created for the user&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;createBranch&lt;/code&gt; function performs the following operation in the stated order...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It initially fetches the forked repository main branch where we wished to create the new branch off of; it does this using the &lt;code&gt;getBranch&lt;/code&gt; helper function; this return the &lt;code&gt;SHA&lt;/code&gt; of the branch&lt;/li&gt;
&lt;li&gt;Then we make a request to the endpoint &lt;code&gt;"POST /repos/{owner}/{repo}/git/refs"&lt;/code&gt; to create the new branch; passing in the &lt;code&gt;repoDetails.repoFullname&lt;/code&gt; properties, the head branch &lt;code&gt;SHA&lt;/code&gt; and the &lt;code&gt;newBranchName&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Moved the &lt;code&gt;getBranch&lt;/code&gt; function from the &lt;code&gt;fork&lt;/code&gt; script to the &lt;code&gt;branch&lt;/code&gt; script&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast/Screenshot&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/babblebey/jargons.dev/assets/25631971/62a2f9b1-7643-42e3-b91f-1427fad1c170" rel="noopener noreferrer"&gt;screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.04.01-13_54_14.webm&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/17" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Building jargons.dev [#5]: The Fork Script</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Mon, 09 Sep 2024 11:09:20 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-5-the-fork-script-558i</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-5-the-fork-script-558i</guid>
      <description>&lt;p&gt;This is the first of 4 scripts I set out to write as stated in the system architecture. Felt pumped! it was a step in the direction of creating the "wiki" experience that gets a contribution to Open source without interfacing with the GitHub UI 😁.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are these scripts?
&lt;/h2&gt;

&lt;p&gt;These are &lt;code&gt;js&lt;/code&gt; files that holds some related helper functions particularly meant to be used to interact with the GitHub APIs; they are either consumed within the same script or exported to be used to perform their base functionality elsewhere within the project. They accept an authenticated Octokit instance of a user as params out of others, this instance is used to perform actions/functions through the GitHub APIs on behalf of the authenticated user.&lt;/p&gt;

&lt;p&gt;The need to create a flow of contributing to Open source without interfacing with the GitHub UI meant that we had to automate some process - simulating every steps a user will take if they were to contribute via the GitHub UI, the steps are as follows..&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork Project Repo&lt;/li&gt;
&lt;li&gt;Create a Branch&lt;/li&gt;
&lt;li&gt;Commit Changes to the Branch (add new mdx file in &lt;code&gt;src/pages/word/&lt;/code&gt; directory for new word or edit existing ones, in our case)&lt;/li&gt;
&lt;li&gt;Create a Pull Request (Submit the word changes, in our case)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Truth worth stating
&lt;/h2&gt;

&lt;p&gt;I started writing this script right after the &lt;a href="https://dev.to/babblebey/building-jargonsdev-0-the-initial-commit-361f"&gt;initial commit&lt;/a&gt;, this was infact the PR #2, but it took a hit during the long month break 🫣 i took from the project before getting back to work on &lt;a href="https://dev.to/babblebey/building-jargonsdev-1-the-base-dictionary-3ei3"&gt;the base dictionary&lt;/a&gt; feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Script
&lt;/h2&gt;

&lt;p&gt;The task here was to create "The Fork Script" — whose end goal is to create/get a fork of the jargons.dev repo on/from a user's account. It should house every function that'll do the following.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check whether a Fork of jargons.dev already exists on a user's account

&lt;ul&gt;
&lt;li&gt;If Fork Exists

&lt;ul&gt;
&lt;li&gt;Check if the Fork is in-Sync with upstream (i.e. up-to-date with jargons.dev repo main branch); IF NOT — Update the fork&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;If NO Fork is found

&lt;ul&gt;
&lt;li&gt;Create the Fork&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Understanding the assignment, I "delved" straight into working on the script.&lt;/p&gt;

&lt;p&gt;I already I'm very used to the GitHub APIs due to my frequent consumption in my everyday work on Hearts ❤️... So I had the GitHub's &lt;a href="https://docs.github.com/en/rest/repos/forks?apiVersion=2022-11-28" rel="noopener noreferrer"&gt;Fork Documentation&lt;/a&gt; looking like a broski to me 😌...&lt;/p&gt;

&lt;h3&gt;
  
  
  The Steps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;I created a main &lt;code&gt;forkRepository&lt;/code&gt; function which was the main entry point to executing the fork functionality - it leads everywhere else&lt;/li&gt;
&lt;li&gt;I added the following functions, which mostly served as helper to  the obvious main &lt;code&gt;forkRepository&lt;/code&gt; function

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isRepositoryForked&lt;/code&gt; - this function checks whether the jargons.dev repository is already forked to the current authencated user's account &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isRepositoryForkUpdated&lt;/code&gt; - to check whether the fork (if found) is (in Sync with head repo) up-to-date with main jargons.dev repo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updateRepositoryFork&lt;/code&gt; - used to update (Sync) repository to state of main (head) jargons.dev repository&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getBranch&lt;/code&gt; - is a base utility (required at the point of writing this script) used to fetch Branch/Ref details for the jargons.dev repo and user's fork to use in the comparison that is done in the &lt;code&gt;isRepositoryForkUpdated&lt;/code&gt; helper to perform its primary function; it uses the the GitHub &lt;a href="https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28" rel="noopener noreferrer"&gt;References&lt;/a&gt; endpoint.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  My weird assumption
&lt;/h3&gt;

&lt;p&gt;Running through my mind 🤔 as I wrote this script was a thought that I held onto after reading the below quoted paragraph on the GitHub Fork Documentation&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Forking a Repository happens asynchronously. You may have to wait a short period of time before you can access the git objects. If this takes longer than 5 minutes, be sure to contact GitHub Support.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I misunderstood this and assumed that we were only going to be able to initiate a fork process, move on and surely not gonna be able to wait for a response object that returns the details of the new fork because we don't know when the fork process completes. &lt;/p&gt;

&lt;p&gt;This assumption forced me to not return any data from the main &lt;code&gt;forkRepository&lt;/code&gt;function and I was already starting to think at this point - how am I gonna get the fork details to process to the next phase of the contribution process!? Hmm, maybe I'll use webhooks 🤔!? &lt;/p&gt;

&lt;p&gt;It turned out I was overthinking it 😂, I realised later that I will infact get a response details for the fork and this led me to do a follow up PR to address returning the data required from the fork response object for consumption in the contribution process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PR
&lt;/h2&gt;

&lt;p&gt;Main: &lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/3" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement `fork` repository script
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#3&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/3" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 31, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull Request implements the &lt;code&gt;fork&lt;/code&gt; script; this script is intended to be used to programmatically fork the main project repo to a user account; It houses a main function and other helper functions it uses to perform some necessary actions in order to ensure an efficient repo fork operation.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Implemented the main &lt;code&gt;forkRepository&lt;/code&gt; function within script; this function is the main exported function that performs the main fork operation; it accepts a &lt;code&gt;userOctokit&lt;/code&gt; (a user authenticated object with permission to act on behalf of the user) instance and the project's repository details i.e. &lt;code&gt;repoDetails&lt;/code&gt; object and it does the following...
&lt;ul&gt;
&lt;li&gt;It checks whether the project repository has already been forked to the user's account using the &lt;code&gt;isRepositoryForked&lt;/code&gt; helper function; this returns the &lt;code&gt;fork&lt;/code&gt; of &lt;code&gt;null&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;If the repo has already been forked, then we perform a check whether the fork is up-to-date/in sync with main project repo using the &lt;code&gt;isRepositoryForkUpdated&lt;/code&gt; helper function; this returns the &lt;code&gt;updatedSHA&lt;/code&gt; and a boolean &lt;code&gt;isUpdated&lt;/code&gt; property that confirm whether fork is up-to-date
&lt;ul&gt;
&lt;li&gt;If fork is not up-to-date; then we perform the update by bringing it in sync with the main project repo using the &lt;code&gt;updateRepositoryFork&lt;/code&gt; helper function&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If repo is up-to-date/in sync with main project repo; we cancel out of the operation at this point with an early return;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If the project repository is not forked onto the user's account; then we proceed to initiating a fork process by calling the &lt;code&gt;"POST /repos/{owner}/{repo}/forks"&lt;/code&gt; endpoint using the &lt;code&gt;userOctokit&lt;/code&gt; instance. &lt;strong&gt;&lt;em&gt;(This starts the fork process, we do not know exactly when the process completes 🤔)&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implement the following helper functions consumed within the main &lt;code&gt;forkRepository&lt;/code&gt; function and within other helper functions too
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;updateRepositoryFork&lt;/code&gt; - used to update (Sync) repository to state of main (head) repository&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isRepositoryForkUpdated&lt;/code&gt; - used to check whether a fork is (in Sync with head repo) up-to-date with main repo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getBranch&lt;/code&gt; - used to fetch a Branch/Ref details&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isRepositoryForked&lt;/code&gt; - used to check for the presence of a specific repo in a user's fork repo list&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;getRepoParts&lt;/code&gt; to &lt;code&gt;/lib/utils&lt;/code&gt;; its a utility function that is used to resolve &lt;code&gt;repoOwner&lt;/code&gt; and &lt;code&gt;repoName&lt;/code&gt; from a repository fullname.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Related Issue&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Resolves #2&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast/Screenshot&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/babblebey/jargons.dev/assets/25631971/16221b7e-3c28-4c6c-a1f3-24d583ce7e3a" rel="noopener noreferrer"&gt;https://github.com/babblebey/jargons.dev/assets/25631971/16221b7e-3c28-4c6c-a1f3-24d583ce7e3a&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;📖&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/3" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
Follow-up: &lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/29" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: return repo `fullname` in fork script
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#29&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/29" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 04, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This PR is a follow-up to a missing step in the &lt;code&gt;fork&lt;/code&gt; script initial implementation at #3; the fork script failed to return a &lt;code&gt;repo&lt;/code&gt; which can be used to in the next step of computation. This was because of a weird assumption I had during the initial implementation. 😆See my assumption below...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I assume that the call to the &lt;code&gt;"POST /repos/{owner}/{repo}/forks"&lt;/code&gt; endpoint only assures of initiating a fork process without assuring us of a response at all. Meaning we might not exactly get a &lt;code&gt;response.data&lt;/code&gt; following the call&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;...but that wasn't true, I found out that a &lt;code&gt;response.data&lt;/code&gt; actually comes, but it might just take some time and only in cases where the repo being forked is huge.... and at the moment forking the project repo happens in less than 5secs.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Returned &lt;code&gt;fork&lt;/code&gt; repo - this is a repo fullname value returned from the &lt;code&gt;isRepositoryForked&lt;/code&gt; helper function; I hereby return it as main returned value from the &lt;code&gt;forkRepository&lt;/code&gt; function execution in the condition where the repo is already forked on a executing user's account&lt;/li&gt;
&lt;li&gt;Returned &lt;code&gt;response.data.full_name&lt;/code&gt; - this is a newly created fork repo fullname; Its a value from the response to the &lt;code&gt;"POST /repos/{owner}/{repo}/forks"&lt;/code&gt; endpoint call; I hereby return it as main retuned value from the &lt;code&gt;forkRepository&lt;/code&gt; function execution in cases where there was no fork already already found on the executing user's account&lt;/li&gt;
&lt;li&gt;Cherry picked some changes from #25 to use on here
&lt;ul&gt;
&lt;li&gt;f12f25f548a5c5836e9be7d601ed226c5269f5ee&lt;/li&gt;
&lt;li&gt;436ceea649b67812c0ec1164fde95d443ce556e0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;📖&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/29" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building jargons.dev [#4]: The Authentication System</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Mon, 26 Aug 2024 15:42:56 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-4-the-authentication-system-40o1</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-4-the-authentication-system-40o1</guid>
      <description>&lt;p&gt;As a developer, Authentication is one of the things that I've got the most respect for; In my experience doing authentication (maybe on a basic level), I've always struggled with one thing or the other especially when I've got to integrate OAuth. &lt;/p&gt;

&lt;p&gt;Prior to working on this for jargons.dev, my most recent experience doing Auth was on Hearts where I integrated GitHub OAuth.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1735788008085856746-253" src="https://platform.twitter.com/embed/Tweet.html?id=1735788008085856746"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1735788008085856746-253');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1735788008085856746&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;So yea! I also had my (traditional 😂) struggles working on this for jargons.dev too; but honestly this was only because of the differences in setup (i.e. technology) though — My experience on Hearts was integrating GitHub OAuth with Server Actions in NextJS meanwhile on jargons.dev, I'm integrating GitHub OAuth with Astro.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Iterations
&lt;/h2&gt;

&lt;p&gt;As I currently write, the authentication system has gone through 3 Iterations, with more planned (details on next iteration in this &lt;a href="https://github.com/jargonsdev/jargons.dev/issues/30" rel="noopener noreferrer"&gt;issue #30&lt;/a&gt;); these iterations over the weeks of development have implemented improvements or refactored a thing or two due to some uncovered limitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Iteration
&lt;/h3&gt;

&lt;p&gt;This iteration implemented in base authentication functionality that allows initiation of a GitHub OAuth flow, response handling that exchanges the authentication code for an accessToken that we securely store on user's cookies.&lt;/p&gt;

&lt;p&gt;The imperative changes worth stating about this iteration is that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I integrated a GitHub App OAuth wich uses permissions with its fine-grained token offering; this promises a short-lived accessToken with a refreshToken.&lt;/li&gt;
&lt;li&gt;I implemented 2 API route for handling the Auth related requests

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;api/github/oauth/callback&lt;/code&gt; - which handles the response from the OAuth flow by redirecting to a specific path the request was made from with the flow authorization &lt;code&gt;code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api/github/oauth/authorize&lt;/code&gt;- a route called from the redirect path, served with the flow authorization &lt;code&gt;code&lt;/code&gt;, exchanges the &lt;code&gt;code&lt;/code&gt; for access token and returns it as response.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;I implemented the first &lt;code&gt;action&lt;/code&gt; (not related to the new and experimental Astro Server Actions, I done this long before the announcement 😏) — this is a term I just made up to call functions that are ran on the server-side of Astro "before the page loads", you shall know it by its naming convension: &lt;code&gt;doAction&lt;/code&gt;, and its style of taking the &lt;code&gt;astroGlobal&lt;/code&gt; object as the only parameter, it's usually async function that returns a response object.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;doAuth&lt;/code&gt; - this action integrates on any page I wish to protect, it checks for the presence of an access token in cookie; — if present: it exchanges that for user data, returns a boolean value &lt;code&gt;isAuthed&lt;/code&gt; alongside it to confirm authentication for protected page; — if no token is found: it checks the presence of the oath flow authorization &lt;code&gt;code&lt;/code&gt; in url search params, exchanges that for access token (by calling the &lt;code&gt;api/github/oauth/authorize&lt;/code&gt; route) and saves it secure to cookies, then uses the cookie appropriately; now in cases where no accessToken is found in cookies and no auth &lt;code&gt;code&lt;/code&gt; is in url search params, then the returned value &lt;code&gt;isAuthed&lt;/code&gt; is false and it will be used on the protected page to redirect to the login page. &lt;/p&gt;

&lt;blockquote&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;isAuthed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;authedData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/login?return_to=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pathname&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;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;...this &lt;code&gt;doAuth&lt;/code&gt; action also returns a utility function &lt;code&gt;getAuthUrl&lt;/code&gt; that is used to generate a GitHub OAuth flow url which is in turn  added as link to the "Connect with GitHub" on the login page and once clicked, it starts an OAuth flow&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1773811079145009311-351" src="https://platform.twitter.com/embed/Tweet.html?id=1773811079145009311"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1773811079145009311-351');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1773811079145009311&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;See PR: &lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/8" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement auth (with github oauth)
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#8&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/8" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 29, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull request implement the authentication feature in the project; using the github oauth, our primary goal is to get and hold users github accessToken in cookies for performing specific functionality. It is important to state that this feature does not take store this user's accessToken to any remote server, this token and any other information that was retrieved using the token are all saved securely on the users' end through usage of cookies.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Implemented the github oauth callback handler at &lt;code&gt;/api/github/oauth/callback&lt;/code&gt; - this handler's main functionality is to receive github's authorization &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;state&lt;/code&gt; to perform either of the following operations&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redirect to the path stated in the &lt;code&gt;state&lt;/code&gt; params with the authorization &lt;code&gt;code&lt;/code&gt; concatenated to it using the &lt;code&gt;Astro.context.redirect&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;or If a &lt;code&gt;redirect=true&lt;/code&gt; value if found in the &lt;code&gt;state&lt;/code&gt; param, then we redirect to the the path stated in the &lt;code&gt;state&lt;/code&gt; params with the authorization &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;redirect=true&lt;/code&gt; value concatenated to it using &lt;code&gt;Astro.context.redirect&lt;/code&gt; method&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implemented the github oauth authorization handler at  &lt;code&gt;/api/github/oauth/authorization&lt;/code&gt; - this handler is a helper that primarily exchanges the authorization &lt;code&gt;code&lt;/code&gt; for &lt;code&gt;tokens&lt;/code&gt; and returns it in a json object.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Created a singleton instance of our github &lt;code&gt;app&lt;/code&gt; at &lt;code&gt;lib/octokit/app&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added a new &lt;code&gt;crypto&lt;/code&gt; util function which provides &lt;code&gt;encrypt&lt;/code&gt; and &lt;code&gt;decrypt&lt;/code&gt; helper function has exports; it is intended to be used for securing the users related &lt;code&gt;cookies&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implemented the &lt;code&gt;doAuth&lt;/code&gt; action function - this function take the &lt;code&gt;Astro&lt;/code&gt; global object as argument and performs the operations stated below&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;/**&lt;/span&gt;
&lt;span class="pl-c"&gt; * Authentication action with GitHub OAuth&lt;/span&gt;
&lt;span class="pl-c"&gt; * &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {import("astro").AstroGlobal} astroGlobal &lt;/span&gt;
&lt;span class="pl-c"&gt; */&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-k"&gt;default&lt;/span&gt; &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;doAuth&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;astroGlobal&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;url&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt; searchParams &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; cookies &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;astroGlobal&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;code&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;searchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"code"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;accessToken&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;cookies&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"jargons.dev:token"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-en"&gt;decode&lt;/span&gt;: &lt;span class="pl-s1"&gt;value&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-en"&gt;decrypt&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;value&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

  &lt;span class="pl-c"&gt;/**&lt;/span&gt;
&lt;span class="pl-c"&gt;   * Generate OAuth Url to start authorization flow&lt;/span&gt;
&lt;span class="pl-c"&gt;   * &lt;a class="mentioned-user" href="https://dev.to/todo"&gt;@todo&lt;/a&gt; make the `parsedState` data more predictable (order by path, redirect)&lt;/span&gt;
&lt;span class="pl-c"&gt;   * &lt;a class="mentioned-user" href="https://dev.to/todo"&gt;@todo&lt;/a&gt; improvement: store `state` in cookie for later retrieval in `github/oauth/callback` handler for cleaner url&lt;/span&gt;
&lt;span class="pl-c"&gt;   * &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {{ path?: string, redirect?: boolean }} state &lt;/span&gt;
&lt;span class="pl-c"&gt;   */&lt;/span&gt;
  &lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;getAuthUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;parsedState&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;String&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-v"&gt;Object&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;keys&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s"&gt;":"&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-s1"&gt;key&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;join&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"|"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; url &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;app&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;oauth&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;getWebFlowAuthorizationUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;state&lt;/span&gt;: &lt;span class="pl-s1"&gt;parsedState&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;url&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;

  &lt;span class="pl-k"&gt;try&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-c1"&gt;!&lt;/span&gt;&lt;span class="pl-s1"&gt;accessToken&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-s1"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-c1"&gt;GET&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;astroGlobal&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
      &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;responseData&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;json&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  
      &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;accessToken&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;refreshToken&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-s1"&gt;cookies&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;set&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"jargons.dev:token"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;accessToken&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
          &lt;span class="pl-c1"&gt;expires&lt;/span&gt;: &lt;span class="pl-en"&gt;resolveCookieExpiryDate&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;expiresIn&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
          &lt;span class="pl-en"&gt;encode&lt;/span&gt;: &lt;span class="pl-s1"&gt;value&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-en"&gt;encrypt&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;value&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
        &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
        &lt;span class="pl-s1"&gt;cookies&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;set&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"jargons.dev:refresh-token"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;refreshToken&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
          &lt;span class="pl-c1"&gt;expires&lt;/span&gt;: &lt;span class="pl-en"&gt;resolveCookieExpiryDate&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;refreshTokenExpiresIn&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
          &lt;span class="pl-en"&gt;encode&lt;/span&gt;: &lt;span class="pl-s1"&gt;value&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-en"&gt;encrypt&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;value&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
        &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
      &lt;span class="pl-kos"&gt;}&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;
  
    &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;userOctokit&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;app&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;oauth&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;getUserOctokit&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;token&lt;/span&gt;: &lt;span class="pl-s1"&gt;accessToken&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;value&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; data &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;userOctokit&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;request&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"GET /user"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      getAuthUrl&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;isAuthed&lt;/span&gt;: &lt;span class="pl-c1"&gt;true&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;authedData&lt;/span&gt;: &lt;span class="pl-s1"&gt;data&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;catch&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;error&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      getAuthUrl&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;isAuthed&lt;/span&gt;: &lt;span class="pl-c1"&gt;false&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;authedData&lt;/span&gt;: &lt;span class="pl-c1"&gt;null&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;it provides (in its returned object) a helper function that can be used to generate a new github oauth  url, this helper consumes our github &lt;code&gt;app&lt;/code&gt; instance and it accepts a &lt;code&gt;state&lt;/code&gt; object with &lt;code&gt;path and &lt;/code&gt;redirect&lt;code&gt;property to build out the&lt;/code&gt;state` value that is held within the oauth url&lt;/li&gt;
&lt;li&gt;it sets &lt;code&gt;cookies&lt;/code&gt; data for &lt;code&gt;tokens&lt;/code&gt; - it does this when it detects the presence of the authorization &lt;code&gt;code&lt;/code&gt; in the &lt;code&gt;Astro.url.searchParams&lt;/code&gt; and reads the absense no project related &lt;code&gt;accessToken&lt;/code&gt; in cookie; this assumes that there's a  new oauth flow going through it; It performs this operation by first calling the github oauth authorization handler at  &lt;code&gt;/api/github/oauth/authorization&lt;/code&gt; where it gets the &lt;code&gt;tokens&lt;/code&gt; data that it adds to &lt;code&gt;cookie&lt;/code&gt; and ensure its securely store by running the &lt;code&gt;encrypt&lt;/code&gt; helper to encode it value&lt;/li&gt;
&lt;li&gt;In cases where there's no authorization &lt;code&gt;code&lt;/code&gt; in the &lt;code&gt;Astro.url.searchParams&lt;/code&gt; and finds a project related &lt;code&gt;token&lt;/code&gt; in &lt;code&gt;cookie&lt;/code&gt;, It fetches users's data and provides it in its returned object for consumptions; it does this by getting the users octokit instance from our github &lt;code&gt;app&lt;/code&gt; instance using the &lt;code&gt;getUserOctokit&lt;/code&gt; method and the user's neccesasry &lt;code&gt;tokens&lt;/code&gt; present in cookie; this users octokit instance is then used to request for user's data which is in turn returned&lt;/li&gt;
&lt;li&gt;It also returns a boolean &lt;code&gt;isAuthed&lt;/code&gt; property that can be used to determine whether a user is authenticated; this property is a statically computed property that only always returns turn when all operation reaches final execution point in the &lt;code&gt;try&lt;/code&gt; block of the &lt;code&gt;doAuth&lt;/code&gt; action function and it returns false when an &lt;code&gt;error&lt;/code&gt; occurs anywhere in the operation to trigger the &lt;code&gt;catch&lt;/code&gt; block of the &lt;code&gt;doAuth&lt;/code&gt; action function&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added the &lt;code&gt;login&lt;/code&gt; page which stands as place where where unauthorised users witll be redirected to; this page integrates the &lt;code&gt;doAuth&lt;/code&gt; action, destruing out the &lt;code&gt;getAuthUrl&lt;/code&gt; helper and the &lt;code&gt;isAuthed&lt;/code&gt; property, it uses them as follows&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; getAuthUrl&lt;span class="pl-kos"&gt;,&lt;/span&gt; isAuthed &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;doAuth&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-v"&gt;Astro&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;isAuthed&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-en"&gt;redirect&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;searchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"redirect"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;authUrl&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;getAuthUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;path&lt;/span&gt;: &lt;span class="pl-s1"&gt;searchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"redirect"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;redirect&lt;/span&gt;: &lt;span class="pl-c1"&gt;true&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isAuthed&lt;/code&gt; - this property is check on the server-side on the page to check if a user is already authenticated from within the &lt;code&gt;doAuth&lt;/code&gt; and redirects to the value stated the page's &lt;code&gt;Astro.url.searchParams.get("redirect")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When a user is not authenticated, it uses the &lt;code&gt;getAuthUrl&lt;/code&gt; to generate a new github oauth url and imperatively set the argument &lt;code&gt;state.redirect&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implemented a new &lt;code&gt;user&lt;/code&gt; store with a Map store value &lt;code&gt;$userData&lt;/code&gt; to store user data to state&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Integration Demo: Protect &lt;code&gt;/sandbox&lt;/code&gt; page&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;// pages/sandbox.astro&lt;/span&gt;

&lt;span class="pl-c1"&gt;--&lt;/span&gt;&lt;span class="pl-c1"&gt;-&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;BaseLayout&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"../layouts/base.astro"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;doAuth&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"../lib/actions/do-auth.js"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;$userData&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"../stores/user.js"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;url&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt; pathname &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; redirect &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Astro&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; isAuthed&lt;span class="pl-kos"&gt;,&lt;/span&gt; authedData &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;doAuth&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-v"&gt;Astro&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-c1"&gt;!&lt;/span&gt;&lt;span class="pl-s1"&gt;isAuthed&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-en"&gt;redirect&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;`/login?redirect=&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;pathname&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;`&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-s1"&gt;$userData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;set&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;authedData&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-c1"&gt;--&lt;/span&gt;&lt;span class="pl-c1"&gt;-&lt;/span&gt;

&lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;BaseLayout&lt;/span&gt; &lt;span class="pl-c1"&gt;pageTitle&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Dictionary"&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;main&lt;/span&gt; &lt;span class="pl-c1"&gt;class&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"flex flex-col max-w-screen-lg p-5 justify-center mx-auto min-h-screen"&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-c1"&gt;class&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"w-fit p-4 ring-2 rounded-full ring-gray-500 m-auto flex items-center space-x-3"&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;img&lt;/span&gt; &lt;span class="pl-c1"&gt;class&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"w-10 h-10 p-1 rounded-full ring-2 ring-gray-500"&lt;/span&gt; 
        &lt;span class="pl-c1"&gt;src&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;authedData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;avatar_url&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt; 
        &lt;span class="pl-c1"&gt;alt&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-s1"&gt;authedData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;login&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;
      &lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;p&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;Hello, &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;authedData&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;login&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-c1"&gt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;p&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;      
    &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-c1"&gt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-c1"&gt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;main&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-c1"&gt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;BaseLayout&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Explainer&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We destructure &lt;code&gt;isAuthed&lt;/code&gt; and &lt;code&gt;authedData&lt;/code&gt; from the &lt;code&gt;doAuth&lt;/code&gt; action&lt;/li&gt;
&lt;li&gt;Check whether a user is not authenticated? and do a redirect to &lt;code&gt;login&lt;/code&gt; page stating the current &lt;code&gt;pathname&lt;/code&gt; as value for the &lt;code&gt;redirect&lt;/code&gt; search param (a data used in state to dictate where to redirect to after authentication complete) if no user is authenticated&lt;/li&gt;
&lt;li&gt;or Proceed to consuming the &lt;code&gt;authedData&lt;/code&gt; which will be available when a &lt;code&gt;isAuthed&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;. by setting it to the &lt;code&gt;$userData&lt;/code&gt; map store property&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast/Screenshot&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/babblebey/jargons.dev/assets/25631971/4063a038-bcff-4e19-91ba-6561c8880410" rel="noopener noreferrer"&gt;screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.03.29-20_36_15.webm&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Note&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Added new node package &lt;a href="https://www.npmjs.com/package/@astrojs/node" rel="nofollow noopener noreferrer"&gt;https://www.npmjs.com/package/@astrojs/node&lt;/a&gt; for SSR adapter intergation&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/8" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Second Iteration
&lt;/h3&gt;

&lt;p&gt;This iteration implements improvements by making making the &lt;code&gt;parsedState&lt;/code&gt; derived from the &lt;code&gt;getAuthUrl&lt;/code&gt; function call more predictable removing the chances of an error in the &lt;code&gt;api/github/oauth/callback&lt;/code&gt; route; it also renames some terms used in the search params and implements the the &lt;code&gt;encodeURIComponent&lt;/code&gt; to make our redirect urls look less weird &lt;/p&gt;

&lt;p&gt;See PR: &lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/28" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement `auth` (second iteration) improvements
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#28&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/28" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 04, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This PR implements some improvement to mark the second iteration of the &lt;code&gt;auth&lt;/code&gt; feature in the project. Follow-up to #8&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Addressed "todo make the &lt;code&gt;parsedState&lt;/code&gt; data more predictable (order by path, redirect)" by implementing more predicatable manner of generating the &lt;code&gt;state&lt;/code&gt; string for the oauth url; this is done by individually looking for the required &lt;code&gt;state&lt;/code&gt; object &lt;code&gt;key&lt;/code&gt; to fill in the &lt;code&gt;parsedState&lt;/code&gt; string in required order. Leaving the new &lt;code&gt;getAuthUrl&lt;/code&gt; helper function looking like so...&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;getAuthUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;parsedState&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;""&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

  &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-c1"&gt;!&lt;/span&gt;&lt;span class="pl-en"&gt;isObjectEmpty&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;path&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-s1"&gt;parsedState&lt;/span&gt; &lt;span class="pl-c1"&gt;+=&lt;/span&gt; &lt;span class="pl-s"&gt;`path:&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;path&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;`&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;otherStates&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;String&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-v"&gt;Object&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;keys&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
      &lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;filter&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;!==&lt;/span&gt; &lt;span class="pl-s"&gt;"path"&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;!==&lt;/span&gt; &lt;span class="pl-s"&gt;"redirect"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
      &lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s"&gt;":"&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;state&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-s1"&gt;key&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;join&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"|"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;otherStates&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;length&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-s1"&gt;parsedState&lt;/span&gt; &lt;span class="pl-c1"&gt;+=&lt;/span&gt; &lt;span class="pl-s"&gt;`|&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;otherStates&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;`&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;

  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; url &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;app&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;oauth&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;getWebFlowAuthorizationUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;state&lt;/span&gt;: &lt;span class="pl-s1"&gt;parsedState&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

  &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;url&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Implemented a new utility function &lt;code&gt;isObjectEmpty&lt;/code&gt; to check if an object has value or not&lt;/li&gt;
&lt;li&gt;Removed usage of &lt;code&gt;redirect&lt;/code&gt; property in state object; its redundant 😮‍💨&lt;/li&gt;
&lt;li&gt;Renamed login redirect path params property name to &lt;code&gt;return_to&lt;/code&gt; from &lt;code&gt;redirect&lt;/code&gt; for readability reasons&lt;/li&gt;
&lt;li&gt;Implemented &lt;code&gt;encodeURIComponent&lt;/code&gt; in login redirect path params value to stop its part on the url from looking like weird 😃;
&lt;ul&gt;
&lt;li&gt;Takes the example url from looking like this... &lt;code&gt;/login?return_to=/editor&lt;/code&gt; to looking like so... &lt;code&gt;/login?return_to=%2Feditor&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Related Isssue&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Resolves #15&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/28" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Third Iteration
&lt;/h3&gt;

&lt;p&gt;This iteration refactors the most parts of the implementation in the "First Iteration" because of a certain limitation that surfaced during the work I was doing on another script. &lt;/p&gt;

&lt;p&gt;At this point in time I was working on the "Submit Word" script; this script leverages the GitHub API and create a Pull Request to merge changes made from the currently authenticated user's fork branch to the base (jargons.dev) main branch. This ofcourse is made possible by the access token of the user saved to cookies which is used in the request headers as "Authorization Bearer token" by the SDK i.e. Octokit that facilitates our interaction with the GitHub APIs. &lt;/p&gt;

&lt;h4&gt;
  
  
  The Limitation
&lt;/h4&gt;

&lt;p&gt;During a test moment as I gave the submit word script a whirl, I was met by the error...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error: Resource not accessible by integration &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...this quickly became a blocker and I consulted &lt;a class="mentioned-user" href="https://dev.to/gr2m"&gt;@gr2m&lt;/a&gt; with whom we quickly uncovered the limitation which related to my integrations of GitHub App.&lt;/p&gt;

&lt;p&gt;As initially stated, the GitHub App uses "Permissions" with a fine-grained token - a new token type that GitHub encourages for some very good &lt;a href="https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app#using-a-github-app-instead-of-an-oauth-app" rel="noopener noreferrer"&gt;reasons&lt;/a&gt; with the below quoted one as one that concerns us here...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub Apps provide more control over what the app can do. Instead of the broad scopes that OAuth apps use, GitHub Apps use fine-grained permissions. For example, if your app needs to read the contents of a repository, an OAuth app would require the repo &lt;code&gt;scope&lt;/code&gt;, which would also let the app edit the repository contents and settings. A GitHub App can request read-only access to repository contents, which will not let the app take more privileged actions like editing the repository contents or settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...this means that when using "Permissions" (i.e. fine-grained permissions), a user must have write access to the upstream/base repository which in this case is our jargons.dev repository; as stated in the GitHub &lt;a href="https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request" rel="noopener noreferrer"&gt;Create a Pull Request docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Say what!? Nope!!! &lt;/p&gt;

&lt;p&gt;It was at that point that we found plain old &lt;code&gt;scope&lt;/code&gt; to be exactly what we need; In order to be able to access the required resource, the &lt;code&gt;public_repo&lt;/code&gt; scope was everything.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Swap for GitHub's OAuth App from GitHub App
&lt;/h4&gt;

&lt;p&gt;In order to move forward, I had to switch from "permissions" to "&lt;a href="https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps" rel="noopener noreferrer"&gt;scope&lt;/a&gt;" and where we found that was in the GitHub's "&lt;a href="https://docs.github.com/en/apps/oauth-apps/using-oauth-apps" rel="noopener noreferrer"&gt;OAuth App&lt;/a&gt;"; this was the basis on which the third iteration was patched.&lt;/p&gt;

&lt;p&gt;So this iteration mainly focused on exchanging the GitHub OAuth integration, also ensuring that the implemented helpers/functions/api in this iteration resemble the ones that was made available by the GitHub App to reduces the amount of changes I was going to make across the entire codebase in acknowledgement of the new implementation.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Trade Offs
&lt;/h4&gt;

&lt;p&gt;GitHub App is great, I must acknowledge that I still have it in my mind for the future if we end-up finding a solution to the &lt;code&gt;Error: Resource not accessible by integration&lt;/code&gt; error, but the functionality to create a Pull Request performed by the &lt;code&gt;submit-word&lt;/code&gt; script is an imperative part of the project, so you bet we gotta make sure it works. &lt;/p&gt;

&lt;p&gt;It's important to state that there was some trade-offs that I had to settle for in favor of the functionality...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No More Short-live token - the GitHub App provides an &lt;code&gt;accessToken&lt;/code&gt; that expires after a certain period and a &lt;code&gt;refreshToken&lt;/code&gt; to refresh this token; this is very good for security; Unlike OAuth App that provides an &lt;code&gt;accessToken&lt;/code&gt; that never expires at all, and doesn't provide a &lt;code&gt;refreshToken&lt;/code&gt; ofcourse&lt;/li&gt;
&lt;li&gt;No More token that works only through jargons.dev - I understood (important to state) that &lt;code&gt;accessToken&lt;/code&gt; generated via OAuth flow initiated through jargons.dev can only be used to make request through jargons.dev, making it not-possible to take this token to use as authorization elsewhere; Unlike OAuth App that provides &lt;code&gt;accessToken&lt;/code&gt; that can be grabbed and used any where else like you would use a normal personal access token generated from your GitHub accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Workarounds&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No More Short-live tokens - I intentionally added an 8 hours expiry for the &lt;code&gt;accessToken&lt;/code&gt; when saving it to cookie to ensure that it atleast gets deleted from cookie, hence triggering a new OAuth flow to ensure a new &lt;code&gt;accessToken&lt;/code&gt; (if that truly is the case) is generated from the flow.&lt;/li&gt;
&lt;li&gt;No More token that works only through jargons.dev - Haha, it is imperative to state that at the point when we save the &lt;code&gt;accessToken&lt;/code&gt; to cookie, it get encrypted, which means it is less-likely that the encrypted token is useful anywhere else because they'd need to decrypt something that we have encrypted. So you can say we have placed a lock on the token that only jargons.dev can unlock.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See PR: &lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/33" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        refactor(auth): replace `github-app-oauth` with classic `oauth` app
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#33&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/33" rel="noopener noreferrer"&gt;&lt;time&gt;Apr 07, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull request refactors the authentication system, replacing the usage of &lt;code&gt;github-app-oauth&lt;/code&gt; with classic github &lt;code&gt;oauth&lt;/code&gt; app. This decision was taken because of the limitations discovered using the Pull Request endpoint (implementation in #25); the &lt;code&gt;github-app-oauth&lt;/code&gt; uses &lt;code&gt;permissions&lt;/code&gt; which requires a user to have &lt;code&gt;write&lt;/code&gt; access to the &lt;code&gt;upstream&lt;/code&gt; (i.e. &lt;code&gt;write&lt;/code&gt; access to atleast &lt;code&gt;pull-requests&lt;/code&gt; on our/this project repo) before a pull request can created from their forked repo branch to the main project repo.&lt;/p&gt;
&lt;p&gt;This PR goes to implement classis &lt;code&gt;oauth&lt;/code&gt; app, which uses &lt;code&gt;scopes&lt;/code&gt; and allows user access to create the pull request to &lt;code&gt;upstream&lt;/code&gt; repo on the &lt;code&gt;public_repo&lt;/code&gt; scope. The changes made in this PR was done to mimic the normal &lt;code&gt;Octokit.App&lt;/code&gt;'s methods/apis as close as possible to allow compatibility with the implementation in #8 and #28 (or for cases when we revert back to using the &lt;code&gt;github-app-oauth&lt;/code&gt; in the future --- &lt;em&gt;maybe we end up finding a solution because honestly I really prefer the &lt;code&gt;github-app-oauth&lt;/code&gt;&lt;/em&gt; 😉).&lt;/p&gt;
&lt;p&gt;It is also important to state that this &lt;code&gt;oauth&lt;/code&gt; app option doesn't offer a short lived token (hence we only have an &lt;code&gt;accessToken&lt;/code&gt; without expiry and No &lt;code&gt;refreshToken&lt;/code&gt;), but I have configured the token to expire out of cookie in 8hours; even though we might be getting exactly thesame token back from github after this expires and we re-authorize the flow, I just kinda like that feeling of the cookies expiring after some hours and asking user to re-auth.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Initialized a new &lt;code&gt;app&lt;/code&gt; object that returns few methods and objects
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;octokit&lt;/code&gt; - the main octokit instance of the &lt;code&gt;oauth&lt;/code&gt; app&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;/**&lt;/span&gt;
&lt;span class="pl-c"&gt; * OAuth App's Octokit instance&lt;/span&gt;
&lt;span class="pl-c"&gt; */&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;octokit&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;Octokit&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;authStrategy&lt;/span&gt;: &lt;span class="pl-s1"&gt;createOAuthAppAuth&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;auth&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;clientId&lt;/span&gt;: &lt;span class="pl-k"&gt;import&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;meta&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;env&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;GITHUB_OAUTH_APP_CLIENT_ID&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;clientSecret&lt;/span&gt;: &lt;span class="pl-k"&gt;import&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;meta&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;env&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;GITHUB_OAUTH_APP_CLIENT_SECRET&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;oauth&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getWebFlowAuthorizationUrl&lt;/code&gt; - method that generates the oauth flow url&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;/**&lt;/span&gt;
&lt;span class="pl-c"&gt; * Generate a Web Flow/OAuth authorization url to start an OAuth flow&lt;/span&gt;
&lt;span class="pl-c"&gt; * &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {import("@octokit/oauth-authorization-url").OAuthAppOptions} options&lt;/span&gt;
&lt;span class="pl-c"&gt; * @returns &lt;/span&gt;
&lt;span class="pl-c"&gt; */&lt;/span&gt;
&lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;getWebFlowAuthorizationUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;state&lt;span class="pl-kos"&gt;,&lt;/span&gt; scopes &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-s"&gt;"public_repo"&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; ...&lt;span class="pl-s1"&gt;options&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-en"&gt;oauthAuthorizationUrl&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;clientId&lt;/span&gt;: &lt;span class="pl-k"&gt;import&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;meta&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;env&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;GITHUB_OAUTH_APP_CLIENT_ID&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    state&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    scopes&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    ...&lt;span class="pl-s1"&gt;options&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;exchangeWebFlowCode&lt;/code&gt; - method that exchanges oauth web flow returned &lt;code&gt;code&lt;/code&gt; for &lt;code&gt;accessToken&lt;/code&gt;; this functionality was extracted from the &lt;code&gt;github/oauth/authorize&lt;/code&gt; endpoint to have all auth related function packed in one place&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;/**&lt;/span&gt;
&lt;span class="pl-c"&gt; * Exchange Web Flow Authorization `code` for an `access_token` &lt;/span&gt;
&lt;span class="pl-c"&gt; * &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {string} code &lt;/span&gt;
&lt;span class="pl-c"&gt; * @returns {Promise&amp;lt;{access_token: string, scope: string, token_type: string}&amp;gt;}&lt;/span&gt;
&lt;span class="pl-c"&gt; */&lt;/span&gt;
&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;exchangeWebFlowCode&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;queryParams&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;URLSearchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-s1"&gt;queryParams&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"code"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-s1"&gt;queryParams&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"client_id"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;meta&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;env&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;GITHUB_OAUTH_APP_CLIENT_ID&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-s1"&gt;queryParams&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"client_secret"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;meta&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;env&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;GITHUB_OAUTH_APP_CLIENT_SECRET&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;fetch&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"https://github.com/login/oauth/access_token"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;method&lt;/span&gt;: &lt;span class="pl-s"&gt;"POST"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;body&lt;/span&gt;: &lt;span class="pl-s1"&gt;queryParams&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;responseText&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;text&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;responseData&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;URLSearchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;responseText&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

  &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;responseData&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getUserOctokit&lt;/code&gt; - method that gets an octokit instance of a user.&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;/**&lt;/span&gt;
&lt;span class="pl-c"&gt; * Get a User's Octokit instance&lt;/span&gt;
&lt;span class="pl-c"&gt; * &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {Omit&amp;lt;OctokitOptions, "auth"&amp;gt; &amp;amp; { token: string }} options&lt;/span&gt;
&lt;span class="pl-c"&gt; * @returns {Octokit}&lt;/span&gt;
&lt;span class="pl-c"&gt; */&lt;/span&gt;
&lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;getUserOctokit&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt; token&lt;span class="pl-kos"&gt;,&lt;/span&gt; ...&lt;span class="pl-s1"&gt;options&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;Octokit&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;auth&lt;/span&gt;: &lt;span class="pl-s1"&gt;token&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    ...&lt;span class="pl-s1"&gt;options&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Integrated the &lt;code&gt;app.oauth.exchangeWebFlowCode&lt;/code&gt; method into the &lt;code&gt;github/oauth/authorize&lt;/code&gt; endpoint handler&lt;/li&gt;
&lt;li&gt;Removed the &lt;code&gt;refreshToken&lt;/code&gt; and &lt;code&gt;refreshTokenExpiresIn&lt;/code&gt; from &lt;code&gt;github/oauth/authorize&lt;/code&gt; endpoint response object.&lt;/li&gt;
&lt;li&gt;Modified &lt;code&gt;doAuth&lt;/code&gt; actions
&lt;ul&gt;
&lt;li&gt;Removed &lt;code&gt;jargons.dev:refresh_token&lt;/code&gt; value set to cookie;&lt;/li&gt;
&lt;li&gt;Corrected computation of &lt;code&gt;userOctokit&lt;/code&gt; to use &lt;code&gt;app.getUserOctokit&lt;/code&gt; from &lt;code&gt;app.oauth.getUserOctokit&lt;/code&gt; (even though I could just move the &lt;code&gt;getUserOctokit&lt;/code&gt; method to the &lt;code&gt;app.oauth&lt;/code&gt; object in the new implmentation, I just prefer it this way 😉).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;📖&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/babblebey/jargons.dev/assets/25631971/ef976b05-8ebd-4cca-a3b3-9943d9e5273d" rel="noopener noreferrer"&gt;screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.04.07-07_37_31.webm&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/33" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Building jargons.dev [#3]: The Word Editor</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Thu, 22 Aug 2024 16:26:11 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-3-the-word-editor-22ee</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-3-the-word-editor-22ee</guid>
      <description>&lt;p&gt;In no time, we're here!&lt;/p&gt;

&lt;p&gt;The editor is surely a part of the important piece of this project, the part that facilitates user's contribution; the dreamy 😊 interface where a user's can basically write/edit jargons and submit with the click of a button and boom that's a contribution to Open source that also gets a green square without interfacing with the GitHub UI or any code editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan
&lt;/h2&gt;

&lt;p&gt;Right from the initial commit, I had found and readied a library that I wanted to integrate for this Editor feature, this was a project that I found during my search for a visual editor tool that builds down to markdown — I needed this tool at a certain point to write some issue with some complex table structure on the Hearts repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  It is MDXEditor
&lt;/h3&gt;

&lt;p&gt;MDXEditor; quoting from the project &lt;a href="https://mdxeditor.dev/" rel="noopener noreferrer"&gt;website&lt;/a&gt;..&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;is an open-source React component that allows users to author markdown documents naturally. Just like in Google docs or Notion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...and I'll add that it's a beautiful tool in itself, with their live demo being of great use to me.&lt;/p&gt;

&lt;p&gt;So, MDXEditor had the finest selling point and trust me I was just sold 🤤.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change of Plan
&lt;/h2&gt;

&lt;p&gt;Unfortunately at the point of integration, I encountered an error; a commonJS related error in &lt;a href="https://github.com/facebook/lexical" rel="noopener noreferrer"&gt;lexical&lt;/a&gt; — a text editor framework integrated in MDXEditor itself. I did some findings and documented source of error in the tweet below..&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1772939694029832414-497" src="https://platform.twitter.com/embed/Tweet.html?id=1772939694029832414"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1772939694029832414-497');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1772939694029832414&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I tried a few workarounds to get it working; but in order for the workaround to work, the lexical's integration version in MDXEditor needs to be updated.&lt;/p&gt;

&lt;p&gt;I also immediately sourced for alternatives, and I found some but I wasn't feeling them at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Resolution
&lt;/h3&gt;

&lt;p&gt;I ended resolving to make a custom minimal markdown editor &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1772939881276203492-305" src="https://platform.twitter.com/embed/Tweet.html?id=1772939881276203492"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1772939881276203492-305');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1772939881276203492&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;...and for a resolution to the error I encountered in my attempt to integrate MDXEditor, we still gotta do this for Open Source.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1772946130784923929-450" src="https://platform.twitter.com/embed/Tweet.html?id=1772946130784923929"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1772946130784923929-450');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1772946130784923929&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  The Editor
&lt;/h2&gt;

&lt;p&gt;I got working, with the goal of relying on little to no dependency to patch the feature; I ended up consuming just &lt;a href="https://github.com/remarkjs/react-markdown" rel="noopener noreferrer"&gt;react-markdown&lt;/a&gt; as new dependency and I was able to conclude the very first iteration of the editor in a 6 stages step.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1773392644128813245-171" src="https://platform.twitter.com/embed/Tweet.html?id=1773392644128813245"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1773392644128813245-171');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1773392644128813245&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h3&gt;
  
  
  The Stages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;I added a new map type &lt;code&gt;$dictionaryEditor&lt;/code&gt; state to the &lt;code&gt;dictionary&lt;/code&gt; store and a custom &lt;code&gt;useDictionaryEditor&lt;/code&gt; hook which wraps around the store so I can use it in the next stage.&lt;/li&gt;
&lt;li&gt;I added a boilerplate &lt;code&gt;word-editor&lt;/code&gt; island with react integration. Integrated the island to with &lt;code&gt;client:load&lt;/code&gt; directive on a newly created &lt;code&gt;editor&lt;/code&gt; page/route.&lt;/li&gt;
&lt;li&gt;I Implemented the &lt;code&gt;Editor&lt;/code&gt; component within the &lt;code&gt;word-editor&lt;/code&gt; island with &lt;code&gt;textarea&lt;/code&gt; form element. I integrated the &lt;code&gt;useDictionaryEditor&lt;/code&gt; to set dictionary store value for editor.&lt;/li&gt;
&lt;li&gt;I implemented the &lt;code&gt;Preview&lt;/code&gt; component within the &lt;code&gt;word-editor&lt;/code&gt; island. Where I consumed a &lt;code&gt;react-markdown&lt;/code&gt; as markdown converter to HTML for the preview purpose.&lt;/li&gt;
&lt;li&gt;I created and integrated a &lt;code&gt;DummyPreviewNavbar&lt;/code&gt; component into the &lt;code&gt;Preview&lt;/code&gt;component to enable the preview tab of the editor look exactly like word looks like when seen in the word layout on the web app.&lt;/li&gt;
&lt;li&gt;I implemented the editor &lt;code&gt;title&lt;/code&gt; field; added a non-functional "Publish" button.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The PR
&lt;/h2&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/6" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement dictionary word editor
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#6&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/6" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 26, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull Request implements the first iteration of the visual word editor for adding new or modifying existing words in the dictionary.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Added a new page/route &lt;code&gt;/editor&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;word-editor&lt;/code&gt; island with react integration and integrated this island on the &lt;code&gt;editor&lt;/code&gt; page with a &lt;code&gt;client:load&lt;/code&gt; directive&lt;/li&gt;
&lt;li&gt;Added a new map store &lt;code&gt;$wordEditor&lt;/code&gt; to &lt;code&gt;stores/dictionary&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implemented a custom hook &lt;code&gt;useWordEditor&lt;/code&gt; which serves as a wrapper to perform &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt; operation on the &lt;code&gt;$wordEditor&lt;/code&gt; store&lt;/li&gt;
&lt;li&gt;Implement 2 component to the &lt;code&gt;word-editor&lt;/code&gt; island integrate within the main island default exported component
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Editor&lt;/code&gt; - the entry field component for editing word entry to the dictionary, it implements the &lt;code&gt;title&lt;/code&gt; field of input type &lt;code&gt;text&lt;/code&gt; and a &lt;code&gt;content&lt;/code&gt; field of &lt;code&gt;textarea&lt;/code&gt; which receives (Markdown) text content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Preview&lt;/code&gt; - the preview UI that mimics the view of word as it's going to look on the dictionary live view;
&lt;ul&gt;
&lt;li&gt;this component also integrate another &lt;code&gt;DummyPreviewNavbar&lt;/code&gt; component to enhance the live preview idea&lt;/li&gt;
&lt;li&gt;it also implements the &lt;code&gt;Markdown&lt;/code&gt; component from &lt;code&gt;react-markdown&lt;/code&gt; node module integration to parse of the Markdown content received from the &lt;code&gt;Editor&lt;/code&gt;'s &lt;code&gt;content&lt;/code&gt; to &lt;code&gt;html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;this component also uses the &lt;code&gt;prose&lt;/code&gt; utility class name from &lt;code&gt;@tailwind/typography&lt;/code&gt; plug-in to style the parsed &lt;code&gt;html&lt;/code&gt; content enhance the preview idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;DummyPreviewNavbar&lt;/code&gt; component in within the &lt;code&gt;word-editor&lt;/code&gt; island for integration in the &lt;code&gt;Preview&lt;/code&gt; component&lt;/li&gt;
&lt;li&gt;Implement a stateless/non-functional &lt;code&gt;Publish&lt;/code&gt; button on &lt;code&gt;editor&lt;/code&gt; page&lt;/li&gt;
&lt;li&gt;Extracted &lt;code&gt;nav&lt;/code&gt; element on &lt;code&gt;layout/base&lt;/code&gt; to standalone &lt;code&gt;Navbar&lt;/code&gt; component&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;📖&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast/Screenshot&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/babblebey/jargons.dev/assets/25631971/173501ae-a77e-4b8a-8b45-c65df20da351" rel="noopener noreferrer"&gt;screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.03.28-16_14_26.webm&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Note&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Added new node module: &lt;code&gt;react-markdown&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/6" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building jargons.dev [#2]: The Dictionary Search Engine</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Mon, 19 Aug 2024 13:18:25 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-2-the-dictionary-search-engine-1i7l</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-2-the-dictionary-search-engine-1i7l</guid>
      <description>&lt;p&gt;What is a Dictionary without a search engine or ummm the search feature!? &lt;/p&gt;

&lt;p&gt;During the implementation of &lt;a href="https://dev.to/babblebey/building-jargonsdev-1-the-base-dictionary-3ei3"&gt;the base dictionary&lt;/a&gt;, I had created these static Search forms (one on the homepage and the other on the Navbar used on the word layout) in preparation for this particular feature.&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%2Foml9qbesu1lrwlhgkm18.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%2Foml9qbesu1lrwlhgkm18.png" alt="Static Homepage Search Form Trigger" width="800" height="445"&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%2Fle3a55ggwyptxwjh07xk.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%2Fle3a55ggwyptxwjh07xk.png" alt="Static Navbar Search Form Trigger" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just needed to pick-up from right there and get it working, easy work — if only that was true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Something from the past
&lt;/h2&gt;

&lt;p&gt;It is important to re-iterate that my initial plan was to build jargons.dev with Nextra as I admitted in the &lt;a href="https://dev.to/babblebey/building-jargonsdev-0-the-initial-commit-361f"&gt;initial commit&lt;/a&gt; that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...Nextra (this was infact my knight in shiny armor, I was looking to build with Nextra).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am a React ⚛️ fan boy, big on Next.js; Nextra is a content-focus web framework that is built on Next.js. So I guess you can just understand why Nextra sounded like a that knight. During my initial exploration of Nextra, one feature stood out to me; the full-text search — I drooled 🤤over this one (I must confess).&lt;/p&gt;

&lt;p&gt;The feature was powered by &lt;a href="https://github.com/nextapps-de/flexsearch" rel="noopener noreferrer"&gt;flexsearch&lt;/a&gt; — a zero-deps full-text search library; ooh boy I'm a big fan of lightweight and no/low dependencies. I dug into how Nextra uses this to index content at build-time for search; it was interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  So!?
&lt;/h3&gt;

&lt;p&gt;I found myself hacking with flexsearch during my early encounter with Astro; as I followed the astro doc's &lt;a href="https://docs.astro.build/en/tutorial/0-introduction/" rel="noopener noreferrer"&gt;build a blog tutorial&lt;/a&gt;, I went a notch further to implement a search feature very easily.&lt;/p&gt;

&lt;p&gt;So, the experience from this implementation; I passed down to the search engine for jargons.dev.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Search Engine
&lt;/h2&gt;

&lt;p&gt;The task was pretty simple, I needed to..&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get access or call it reference to all files inside of the dictionary directory of words - at this point it was the &lt;code&gt;src/pages/word&lt;/code&gt; directory &lt;/li&gt;
&lt;li&gt;Get these files content indexed with flexsearch &lt;/li&gt;
&lt;li&gt;Plug in the search form and boom 😉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looks very easy! Maybe for the search indexing and actual searching, it was; but there was some lot of stuffs that went into getting there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating the first "Island" in jargons.dev
&lt;/h3&gt;

&lt;p&gt;Astro by default takes a server-first approach, meaning it builds your site's HTML/CSS on server removing all client-side javascript (JS) automatically (unless you state other wise). Removing all JS assures performance improvement, but No JS means no interactivity; But if you want interactivity, &lt;a href="https://docs.astro.build/en/concepts/islands/" rel="noopener noreferrer"&gt;Astro Island&lt;/a&gt; is one of the ways to go. I need the interactivity for the Search Engine so Island it is!&lt;/p&gt;

&lt;h4&gt;
  
  
  What's an "Island" though!?
&lt;/h4&gt;

&lt;p&gt;I will put simply that an Island is an isolated interactive piece of component on a web page, whose HTML/CSS are rendered on the server-side and/but it's client-side javaScript are (hydrated) also bundled with it - NOT removed.&lt;/p&gt;

&lt;p&gt;I gave a talk about Island at TILConf'24, &lt;a href="https://www.youtube.com/live/__Sup-HoBBI?t=14290s" rel="noopener noreferrer"&gt;Check it out to learn more&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Astro's offering
&lt;/h4&gt;

&lt;p&gt;Astro offered support for integrating Islands out the box with my favorite UI library (yea, you guessed it, React) of many others. This allowed me build my static Search forms into a functional stuff.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stuffs I did
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;I started by adding the integration module (&lt;a href="https://docs.astro.build/en/guides/integrations-guide/react/" rel="noopener noreferrer"&gt;@astrojs/react&lt;/a&gt;) for the Island I needed to integrate; done pretty easily with the &lt;code&gt;npx astro add react&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;I transferred all the static search forms into single React component (these are two differently sized forms); configured the component to render these at required size based on given props.&lt;/li&gt;
&lt;li&gt;I also implemented some sub-component within that are only consumed locally in the-same search component, these are...

&lt;ul&gt;
&lt;li&gt;The SearchDialog - main component where the search operation is carried out&lt;/li&gt;
&lt;li&gt;The SearchResult component, etc...&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I implemented some custom keyboard shortcuts and keybinds that enables interactions with the search component (I'd like to call this "Search Island" from now on), these are...

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CTRL+K&lt;/code&gt; or &lt;code&gt;⌘K&lt;/code&gt; to start search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ESC&lt;/code&gt; to close search&lt;/li&gt;
&lt;li&gt;...and base required navigation buttons to navigate within search results&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I also added a few custom hooks to allow smooth sailing in the working of the search island, these are...

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useLockBody&lt;/code&gt; - a hook that disables scrolling once the search dialog is opened&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useRouter&lt;/code&gt; - a hook that I made as wrapper around some &lt;code&gt;window.location&lt;/code&gt; methods, making them to feel like the known router libs in React, this is a hook I particularly consumed on the &lt;code&gt;ENTER&lt;/code&gt; button click handler in the navigation button keybind on search results component in the search island. &lt;/li&gt;
&lt;li&gt;and &lt;code&gt;useIsMacOS&lt;/code&gt; - which checks whether a machine is MacOS in order to determine the appropriate description text to render on the search form trigger; i.e. CTRL+K or ⌘K&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I added the imperative module - &lt;a href="https://github.com/nextapps-de/flexsearch" rel="noopener noreferrer"&gt;flexsearch&lt;/a&gt;; &lt;/li&gt;

&lt;li&gt;I retrieved access to files the directory of words using the &lt;code&gt;Astro.glob()&lt;/code&gt; function super easily (too bad I couldn't talk about how powerful this function is; how glad I am it existed out-the-box in Astro and how much ease it brought into the flow of getting this search engine up and running) and plugged the returned array of word objects into a &lt;code&gt;$dictionary&lt;/code&gt; state (maybe I should call this a store) powered by &lt;a href="https://github.com/nanostores/nanostores" rel="noopener noreferrer"&gt;nanostore&lt;/a&gt; (another beautiful stuff right there)&lt;/li&gt;

&lt;li&gt;This &lt;code&gt;$dictionary&lt;/code&gt; are then indexed with flexsearch, prepping them for later search.&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Another Imperative feature: The Recent Searches
&lt;/h4&gt;

&lt;p&gt;This is another imperative feature that I must talk about; This feature keeps track of searched items and stores them in &lt;code&gt;localstorage&lt;/code&gt; to persist them on page reload; these store searched items are then render in a list on the homepage of the dictionary.&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%2Fip05ug26uzujt483b2sz.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%2Fip05ug26uzujt483b2sz.png" alt="Recent Search Feature" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also took integration as an Island, couple with a holding the value in a &lt;a href="https://github.com/nanostores/nanostores" rel="noopener noreferrer"&gt;nanostore&lt;/a&gt; powered &lt;code&gt;$recentSearches&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;My implementation of this feature isn't exactly perfect, and here are a list of some Issues that needed fix (at time of writing) to take it a step further down that route (even though we can never reach perfection, YEA for sure)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add Loading Component to Recent Searches Island - &lt;a href="https://github.com/devjargons/jargons.dev/issues/31" rel="noopener noreferrer"&gt;https://github.com/devjargons/jargons.dev/issues/31&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bug: Search Operation Performed with Search Form in Navbar Overwrites LocalStorage - &lt;a href="https://github.com/devjargons/jargons.dev/issues/10" rel="noopener noreferrer"&gt;https://github.com/devjargons/jargons.dev/issues/10&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Enhancement: Word Editor - Second Iteration Features - &lt;a href="https://github.com/devjargons/jargons.dev/issues/9" rel="noopener noreferrer"&gt;https://github.com/devjargons/jargons.dev/issues/9&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The PR
&lt;/h2&gt;

&lt;p&gt;This is some long read now, I wish to keep this reads short... Here's the PR&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/5" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement dictionary search engine
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#5&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/5" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 25, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull Request implement the search functionality to the dictionary project. It uses &lt;code&gt;@astro/react&lt;/code&gt; integration to power the Islands coupled with &lt;code&gt;nanostore&lt;/code&gt; for state-management and &lt;code&gt;flexsearch&lt;/code&gt; as text search library.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Added the following astrojs integration and lib required to text search
&lt;ul&gt;
&lt;li&gt;@astrojs/react&lt;/li&gt;
&lt;li&gt;@nanostores/react&lt;/li&gt;
&lt;li&gt;flexsearch&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;Search&lt;/code&gt; Island (a react component) within where other sub components are implemented for internal usage
&lt;ul&gt;
&lt;li&gt;Implemented the &lt;code&gt;SearchTrigger&lt;/code&gt; component which render a search field of two different sizes and used in two different places on the web page...
&lt;ul&gt;
&lt;li&gt;size &lt;code&gt;md&lt;/code&gt; - used on the main page of the web app&lt;/li&gt;
&lt;li&gt;size &lt;code&gt;sm&lt;/code&gt; - used on the dictionary &lt;code&gt;word&lt;/code&gt; layout navigation section&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;SearchDialog&lt;/code&gt; component, which renders only when the &lt;code&gt;SearchTrigger&lt;/code&gt; is clicked&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;SearchInfo&lt;/code&gt; component, renders as default placeholder when no search term has been inputted in form field&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;SearchResult&lt;/code&gt; component, renders either search results or a message for a search results not found&lt;/li&gt;
&lt;li&gt;Implemented keybinds within &lt;code&gt;Search&lt;/code&gt; island to allow the following operation with the stated keyboard shortcuts
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CTRL+K&lt;/code&gt; or &lt;code&gt;⌘K&lt;/code&gt; to open the search dialog without clicking on the search tigger&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ArrowUp&lt;/code&gt;, &lt;code&gt;ArrowDown&lt;/code&gt; and &lt;code&gt;Enter&lt;/code&gt; to allow navigation on the Search results list&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ESC&lt;/code&gt; to allow closure of search dialog&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added the custom hooks for consumption on the &lt;code&gt;Search&lt;/code&gt; island
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useIsMacOS&lt;/code&gt; - check whether current user is browsing the web app with a MacOS machine; this is used to determine the appropriate short to render on the search trigger; i.e. &lt;code&gt;CTRL+K&lt;/code&gt; or &lt;code&gt;⌘K&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useLockBody&lt;/code&gt; - used to disable current viewport from scrolling when search dialog is opened&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useRouter&lt;/code&gt; - (instead of adding &lt;code&gt;react-router&lt;/code&gt; to deps) this hook wraps around &lt;code&gt;window.location&lt;/code&gt; and uses the &lt;code&gt;assign&lt;/code&gt; object as &lt;code&gt;push&lt;/code&gt;; mainly used in the &lt;code&gt;SearchResult&lt;/code&gt; component to route to selected/clicked result page&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented &lt;code&gt;searchIndex&lt;/code&gt;ing on &lt;code&gt;Search&lt;/code&gt; island with &lt;code&gt;flexsearch&lt;/code&gt;'s &lt;code&gt;Document&lt;/code&gt; method as preferred option
&lt;ul&gt;
&lt;li&gt;Added a new &lt;code&gt;search&lt;/code&gt; store for managing the search-related states with &lt;code&gt;nanostores&lt;/code&gt; and &lt;code&gt;@nanostores/react&lt;/code&gt; integration&lt;/li&gt;
&lt;li&gt;Added the following store values and actions
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$isSearchOpen&lt;/code&gt; - global state for managing the state of &lt;code&gt;SearchDialog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$recentSearches&lt;/code&gt; - state for keeping track of recently searched words; it works in collab with the &lt;code&gt;localStorage&lt;/code&gt; to persist its value even after tab reloads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$addToRecentSearchesFn&lt;/code&gt; - a store action that adds new item to &lt;code&gt;$recentSearches&lt;/code&gt; store value&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;$dictionary&lt;/code&gt; store for managing the entire &lt;code&gt;dictionary&lt;/code&gt; entries; keeping it accesible to the client and used as value for &lt;code&gt;searchIndex&lt;/code&gt; in the &lt;code&gt;Search&lt;/code&gt; island
&lt;ul&gt;
&lt;li&gt;Computed value for the &lt;code&gt;dictionary&lt;/code&gt; store as early as possible from the &lt;code&gt;layout/base&lt;/code&gt; with the &lt;code&gt;Astro.glob()&lt;/code&gt; method indexing the entire dictionary directory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added the &lt;code&gt;RecentSearches&lt;/code&gt; island which reads the value from the &lt;code&gt;$recentSearches&lt;/code&gt; store and renders it on the homepage&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screencast&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Full Demo&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/babblebey/dictionry/assets/25631971/26fc1016-f7ed-4e98-9c26-1524351e903b" rel="noopener noreferrer"&gt;screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.03.25-13_32_30.webm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;📖&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/5" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>opensource</category>
      <category>buildinpublic</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building jargons.dev [#1]: The Base Dictionary</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Tue, 13 Aug 2024 14:53:39 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-1-the-base-dictionary-3ei3</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-1-the-base-dictionary-3ei3</guid>
      <description>&lt;p&gt;Welcome to the second installment of our series on jargons.dev! &lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get to it!
&lt;/h2&gt;

&lt;p&gt;Following the &lt;a href="https://dev.to/babblebey/building-jargonsdev-0-the-initial-commit-361f"&gt;initial commit&lt;/a&gt;, I started working on "the fork script" (wondering what that is?? You'll find out later in the series 😉) but I must confess and as you'll find in the commit history, that I took a long break (3 months+) from working on jargons.dev. Within these times I've had some opportunity to do some subconscious reflection that was great for the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflection Opportunity
&lt;/h2&gt;

&lt;p&gt;I stopped work on jargons.dev for the while, not intentionally but because I was so embedded in the work I was doing on Hearts, that I didn't even think about jargons.dev. Well, over the course of those months, the new year came (with new goals of-course), I've also experienced and gotten exposed to some new technologies. One technology stood out to me and that was Astro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Astro Resonating with jargons.dev
&lt;/h3&gt;

&lt;p&gt;In January, I had a goal of "learning new technologies with docs", this was a challenge that got me started with Astro after hearing great stuff about it.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1744263459867410930-653" src="https://platform.twitter.com/embed/Tweet.html?id=1744263459867410930"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1744263459867410930-653');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1744263459867410930&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Fast forward March, I found myself working on another entirely different side project (&lt;a href="https://github.com/babblebey/wp-theme" rel="noopener noreferrer"&gt;wp-theme&lt;/a&gt;), I was watching a Eddie Jaoude &lt;a href="https://youtube.com/live/ZI7tSRR9q8w" rel="noopener noreferrer"&gt;YT Stream&lt;/a&gt; where I made this known to Eddie but his response would end up pushing me back to working on jargons.dev&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1768395047462981963-11" src="https://platform.twitter.com/embed/Tweet.html?id=1768395047462981963"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1768395047462981963-11');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1768395047462981963&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You have quite a few side projects... I don't know which one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This statement got me to think real hard, hence I decided to halt all the lots of side projects and focus on some that mattered right away, jargons.dev easily came back to mind.&lt;/p&gt;

&lt;p&gt;At this point, I was already somewhat familiar with Astro, — being a framework for content-driven web apps, with a super simple file system, i18n-ready, SSG with great SEO (important for the project), performant, support for other frontend libraries like ReactJS with islands (I love this one especially); it was a tool made in heaven to build jargons.dev with.&lt;/p&gt;

&lt;p&gt;Well, I quickly got to work the next available weekend I had to work on the base dictionary part of the project.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1771908483828363760-160" src="https://platform.twitter.com/embed/Tweet.html?id=1771908483828363760"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1771908483828363760-160');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1771908483828363760&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  The Base Dictionary
&lt;/h2&gt;

&lt;p&gt;I initialized a new Astro project for this one, as simple as running the command below and follow the prompts...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I also added the tailwindcss integration for styling; mdx integration for content; this was also super easy to config by just running the command respectively&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx astro add tailwind
npx astro add mdx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I went on and completed the following tasks&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a boilerplate homepage with static search form&lt;/li&gt;
&lt;li&gt;Temporarily resolved to have the &lt;code&gt;src/pages/word&lt;/code&gt; directory as the directory that should hold each word in the dictionary as a &lt;code&gt;mdx&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Implemented the &lt;code&gt;word.astro&lt;/code&gt; layout, which serves as the frame within where all &lt;code&gt;.mdx&lt;/code&gt; files' content for words inside of the &lt;code&gt;src/pages/word/&lt;/code&gt; directory can be rendered using &lt;a href="https://docs.astro.build/en/guides/markdown-content/#frontmatter-layout" rel="noopener noreferrer"&gt;frontmatter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Also added a static mini search form to the word layout navbar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this feature, we are already able to view dictionary word on the &lt;code&gt;jargons.dev/word/[word]&lt;/code&gt;route. This means when the file &lt;code&gt;tuple.mdx&lt;/code&gt; is present in the &lt;code&gt;src/pages/word/&lt;/code&gt;directory, we will be able to reach the page to get see the dictionary word with a visit to &lt;code&gt;jargons.dev/word/tuple&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  The PR
&lt;/h4&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/4" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: implement base dictionary
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#4&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/4" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 24, 2024&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This Pull request implements the base dictionary app with AstroJS&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Changes Made&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Started new astro project&lt;/li&gt;
&lt;li&gt;Created homepage&lt;/li&gt;
&lt;li&gt;Implemented 2 layouts
&lt;ul&gt;
&lt;li&gt;Base - main primary wrapper for all pages and layouts&lt;/li&gt;
&lt;li&gt;Word - layout to be used on the word pages&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented static search form triggers on homepage and in &lt;code&gt;Word&lt;/code&gt; layout&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Screenshots&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Homepage&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/babblebey/dictionry/assets/25631971/e0540497-e96e-4f87-97ff-6781efcf49cc"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fbabblebey%2Fdictionry%2Fassets%2F25631971%2Fe0540497-e96e-4f87-97ff-6781efcf49cc" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Word page&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/babblebey/dictionry/assets/25631971/bbf6a348-d4be-4fe3-90d8-6bf4b3bf8d3a"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fbabblebey%2Fdictionry%2Fassets%2F25631971%2Fbbf6a348-d4be-4fe3-90d8-6bf4b3bf8d3a" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/4" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>opensource</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Building jargons.dev [#0]: The Initial Commit</title>
      <dc:creator>Olabode Lawal-Shittabey</dc:creator>
      <pubDate>Mon, 12 Aug 2024 13:46:56 +0000</pubDate>
      <link>https://dev.to/babblebey/building-jargonsdev-0-the-initial-commit-361f</link>
      <guid>https://dev.to/babblebey/building-jargonsdev-0-the-initial-commit-361f</guid>
      <description>&lt;p&gt;Yo, my first blog post! I don't even know how to start this haha, but I will just get started 🫣.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;This is the "init" part of a blog series documenting my journey behind building jargons.dev. When I first ideated jargons.dev, I wanted to build it openly, sharing every step with the community. But I was also working on another project called "Hearts ❤️" which required my full attention. To manage both effectively, I decided to work on jargons.dev in a private repository.&lt;/p&gt;

&lt;p&gt;Even though it wasn't built entirely in public, I've sort'a documented my thought process and decisions behind imperative pull requests. This series will take you behind the scenes, sharing the challenges, solutions, and lessons learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bits about me 🤭 (or skip to save the read)
&lt;/h2&gt;

&lt;p&gt;My name is Olabode, nothing much to know about me other than I've been in tech for quite a while but not exactly very seriously IMO until I decided and took the biggest decision in my life by quitting my (6-figure paying) 9-5 back in September 2021 to focus on tech (now I wanna be serious 😤). This happened after some thought of how I started, got to where I was at and where I just wanna be (a story for another blogpost for sure). Fast forward June 2023, after the learnings, struggles and all, I discovered "Open Source" which became a thing for me since then to the point it earned me an opportunity to work on the project called "Hearts ❤️ (a recognition tool for open source)" where I have just gathered some &lt;em&gt;how-it-should-be-done&lt;/em&gt; product building experience. Prior to this (in my past) I had always built stuffs differently, (yea, a not-so-good kind of different 😂); So with my experience working on "Hearts ❤️", I set out to build something (not just anything) in the &lt;em&gt;how-it-should-be-done&lt;/em&gt; way ☝🏾.&lt;/p&gt;

&lt;h2&gt;
  
  
  About jargons.dev
&lt;/h2&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1816783659325661250-279" src="https://platform.twitter.com/embed/Tweet.html?id=1816783659325661250"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1816783659325661250-279');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1816783659325661250&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;See, I'm an idealistic (that wasn't supposed to sound bad 🫣) person, I just get the weirdest ideas, which typically comes from a place of needing to scratch an itch and I surely do well to write these ideas down. &lt;/p&gt;

&lt;p&gt;So jargons.dev (aka DevJargons) is an idea that came from the perspective of a dude called "Ola" (I can't promise you that's not me 😉); Ola is a super dumb learner who's mostly caught-up in a Technical Jargons hell; when Ola tries to learn/get definition to a technical term, he gets met by another term within, that requires definition and another and another. It's also not very easy to google search this terms especially because some of these terms likely shares context with fields/industries other than tech or software engineering. jargons.dev was just going to scratch this itch by being that one-stop dictionary that offers clear, easy-to-understand definitions to these terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Initial Commit
&lt;/h2&gt;

&lt;p&gt;Hold-up, we cannot go further without talking about "the notepad"&lt;/p&gt;

&lt;h3&gt;
  
  
  The Notepad
&lt;/h3&gt;

&lt;p&gt;What about this!? Yea, this is one of my oldest Notepad where I write these weird ideas. Sometime in november 2023, I wrote the tweet below, it was "the Notepad" posing with a work laptop (a Microsoft Surface Laptop 2) I had just got as tiny upgrade from my 6years old Lenovo IdeaPad 100.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1724832031345889535-897" src="https://platform.twitter.com/embed/Tweet.html?id=1724832031345889535"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1724832031345889535-897');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1724832031345889535&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;So yea, this Laptop was super fast and I was pumped, I just wanna build stuffs. Hence, I picked the smallest and easiest to work on idea from the notepad; that was jargons.dev. I started cooking right away and its imperative to state that I started to do it in the &lt;em&gt;how-it-should-be-done&lt;/em&gt; way.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1726259841256268036-466" src="https://platform.twitter.com/embed/Tweet.html?id=1726259841256268036"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1726259841256268036-466');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1726259841256268036&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h3&gt;
  
  
  Approaching the initial commit
&lt;/h3&gt;

&lt;p&gt;I stated that it was important for me to do this in the &lt;em&gt;how-it-should-be-done&lt;/em&gt; way, so I leveraged my experience working on hearts by starting with developing a system architecture and a concept note for how the project would work. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Concept Note and System Architecture: &lt;a href="https://persistent-lan-c5e.notion.site/dictionry-a12ac63f23a645a9b2275d829adbae25" rel="noopener noreferrer"&gt;https://persistent-lan-c5e.notion.site/dictionry-a12ac63f23a645a9b2275d829adbae25&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point for jargons.dev, I had muted that it will be an Open Source dictionary that can accept word contribution, it will not require a server, it will rely on GitHub as backend, using a bunch of md files similar to &lt;a href="https://github.com/TheOdinProject/curriculum" rel="noopener noreferrer"&gt;The Odin Project&lt;/a&gt; and doc sites implemented like &lt;a href="https://nextra.site/" rel="noopener noreferrer"&gt;Nextra&lt;/a&gt; (this was infact my knight in shiny armor, I was looking to build jargons.dev with Nextra) but I also want to make contributing to the dictionary fun, and lovable with a streamlined contribution experience. &lt;/p&gt;

&lt;p&gt;Having worked (for 2 months already) on "Hearts" at that point where I've consumed the GitHub APIs heavily, I found that I could leverage some GitHub endpoints to create a "wiki"-like experience 🤔 by providing a UI where contributors can add new or edit existing word on the dictionary which ends up as a Pull request — a contribution to Open Source, without interfacing with the GitHub UI or any IDE for word contribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Commit
&lt;/h3&gt;

&lt;p&gt;Now I understood the assignment and I got to work, I initialized the repository under the initial name "dictionry", a name that was initially a typo but stuck with me... &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jargonsdev/jargons.dev/commit/8dc21f0fb6036563f826199ea39490bf77c4b34d" rel="noopener noreferrer"&gt;https://github.com/jargonsdev/jargons.dev/commit/8dc21f0fb6036563f826199ea39490bf77c4b34d&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...and I merged the first PR to add &lt;a href="https://github.com/octokit/octokit.js" rel="noopener noreferrer"&gt;octokit&lt;/a&gt; as the first node module to the project...&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/1" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        feat: initialize dictionry
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#1&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F25631971%3Fv%3D4" alt="babblebey avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/babblebey" rel="noopener noreferrer"&gt;babblebey&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/jargonsdev/jargons.dev/pull/1" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 17, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;PR adds &lt;code&gt;octokit&lt;/code&gt; javascript SDK for consuming GitHub api&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jargonsdev/jargons.dev/pull/1" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This was the start of something fun 🤌🏾&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
