<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Uncategorized | Nerdpress.org</title>
	<atom:link href="https://nerdpress.org/category/uncategorized/feed/" rel="self" type="application/rss+xml" />
	<link>https://nerdpress.org</link>
	<description>...dev, tech problems and solutions.</description>
	<lastBuildDate>Sat, 28 Feb 2026 14:46:50 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>A WordPress-like More Tag in Astro</title>
		<link>https://nerdpress.org/2026/02/28/a-wordpress-like-more-tag-in-astro/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Sat, 28 Feb 2026 14:46:49 +0000</pubDate>
				<category><![CDATA[Astro]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Wordpress]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3470</guid>

					<description><![CDATA[<p>When migrating from WordPress to Astro, one encounters various challenges.One is handling the WordPress &#8220;More&#8221; tag, which originates from the More Element in the WordPress Block Editor. After converting the WordPress export to Markdown, you will find `&#60;!&#8211; more &#8211;>` tags in your content. These tags serve as delimiters in WordPress, allowing you to define &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2026/02/28/a-wordpress-like-more-tag-in-astro/" class="more-link">Continue reading<span class="screen-reader-text"> "A WordPress-like More Tag in Astro"</span></a></p>
The post <a href="https://nerdpress.org/2026/02/28/a-wordpress-like-more-tag-in-astro/">A WordPress-like More Tag in Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>When migrating from WordPress to Astro, one encounters various challenges.<br />One is handling the WordPress &#8220;More&#8221; tag, which originates from the More Element in the WordPress Block Editor.</p>



<p>After converting the WordPress export to Markdown, you will find `&lt;!&#8211; more &#8211;>` tags in your content. These tags serve as delimiters in WordPress, allowing you to define where the excerpt ends on listing pages.</p>



<span id="more-3470"></span>



<h2 class="wp-block-heading"><strong>The Challenge</strong></h2>



<p>Astro does not provide a built-in mechanism to handle these tags. I also could not find a suitable plugin that would manage this functionality.</p>



<p>My first approach was to use a Remark plugin like <a href="https://www.npmjs.com/package/remark-excerpt" target="_blank" rel="noopener" title="">remark-excerpt</a>. However, this did not work as expected. Instead of adding an Excerpt property to the frontmatter or to the data object of the content collection, it simply replaced the Content with the Excerpt.</p>



<p>Writing a custom Remark plugin proved challenging as well. While extracting the excerpt markdown is straightforward, rendering it to HTML is not. Since Astro does not expose its renderer (yet*), you would need to create your own renderer or use Astro&#8217;s internal `astrojs/markdown-remark`. Both approaches are rather hacky and not really recommended.</p>



<p>* <em>There are ongoing discussions about making the renderer API public: <a href="https://github.com/withastro/roadmap/discussions/1094" target="_blank" rel="noopener" title="">Astro Roadmap Discussion #1094.</a></em></p>



<h2 class="wp-block-heading"><strong>The Solution</strong></h2>



<p>The current best way to handle the WordPress More Tag in Astro is to split the rendered HTML content directly.</p>



<p>On the list page where you want to display the excerpt, you can map over the entries, split the rendered HTML at the <code>&lt;!--more--></code> tag, and add the excerpt to the return object:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> previewPosts = <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(
	posts.map(<span class="hljs-keyword">async</span> (post) =&gt; {
		<span class="hljs-keyword">const</span> fullHtml = post.rendered?.html || <span class="hljs-string">''</span>;
		<span class="hljs-keyword">const</span> moreIndex = fullHtml.indexOf(<span class="hljs-string">'&lt;!--more--&gt;'</span>);
		<span class="hljs-keyword">const</span> excerptHtml = moreIndex !== <span class="hljs-number">-1</span> ? fullHtml.slice(<span class="hljs-number">0</span>, moreIndex) : fullHtml;
		<span class="hljs-keyword">const</span> hasMore = moreIndex !== <span class="hljs-number">-1</span>;
		<span class="hljs-keyword">return</span> { post, hasMore, excerptHtml };
	}),
);

{previewPosts.map(<span class="hljs-function">(<span class="hljs-params">{ post, hasMore, Content }</span>) =&gt;</span> (
	<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"preview-content"</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">Content</span> /&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
	{hasMore &amp;&amp; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"read-more"</span>&gt;</span>Read more...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>}
))}</code></span></pre>


<p>Straightforward and effective :)</p>



<p></p>The post <a href="https://nerdpress.org/2026/02/28/a-wordpress-like-more-tag-in-astro/">A WordPress-like More Tag in Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Dynamic OpenGraph Images in Astro</title>
		<link>https://nerdpress.org/2025/12/23/dynamic-opengraph-images-in-astro/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Tue, 23 Dec 2025 13:13:06 +0000</pubDate>
				<category><![CDATA[Astro]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[OpenGraph]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3457</guid>

					<description><![CDATA[<p>If you&#8217;ve ever shared a link on social media, you know how critical OpenGraph (OG) images are. They&#8217;re the first thing people see &#8211; often before they even click.Static OG images are fine as a start, but what if you want custom images for every blog post or content collection item? For Astro there is &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2025/12/23/dynamic-opengraph-images-in-astro/" class="more-link">Continue reading<span class="screen-reader-text"> "Dynamic OpenGraph Images in Astro"</span></a></p>
The post <a href="https://nerdpress.org/2025/12/23/dynamic-opengraph-images-in-astro/">Dynamic OpenGraph Images in Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>If you&#8217;ve ever shared a link on social media, you know how critical OpenGraph (OG) images are. They&#8217;re the first thing people see &#8211; often before they even click.<br />Static OG images are fine as a start, but what if you want <strong>custom images for every blog post or content collection item</strong>?</p>



<p>For Astro there is <a href="https://github.com/delucis/astro-og-canvas" target="_blank" rel="noopener" title="">astro-og-canvas</a>, a nice and useful Astro plugin that utilizes Canvas to create dynamic OG images.</p>



<p>In this post, I&#8217;ll walk you through how to generate dynamic OG images for your Astro site, inspired by <a href="https://aidankinzett.com/blog/astro-open-graph-image/" target="_blank" rel="noopener" title="">Aidan Kinzett&#8217;s excellent post</a>.<br />I&#8217;ll also share some odds and learned lessons.</p>



<span id="more-3457"></span>



<p><strong>Step 1: Set Up `astro-og-canvas`</strong></p>



<p>First, install the package:</p>



<p><code>npm install astro-og-canvas</code></p>



<p>Create a new file at <code>src/pages/og/[...routes].ts</code>.<br />This will dynamically generate images for your content.<br />Here&#8217;s how I set it up for my newsletter collection:</p>


<pre class="wp-block-code"><span><code class="hljs language-typescript"><span class="hljs-keyword">import</span> { OGImageRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro-og-canvas"</span>;
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:content"</span>;

<span class="hljs-comment">// Fetch all newsletter entries</span>
<span class="hljs-keyword">const</span> newsletters = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">"newsletter"</span>);

<span class="hljs-comment">// Map newsletters to OG image configurations</span>
<span class="hljs-keyword">const</span> pages = {
  ...Object.fromEntries(
    newsletters.map(<span class="hljs-function">(<span class="hljs-params"><span class="hljs-params">newsletter</span></span>) =&gt;</span> &#91;
      newsletter.data.slug,
      {
        data: {
          title: newsletter.data.title,
          description: newsletter.data.newsletter.parts
            .map(<span class="hljs-function">(<span class="hljs-params"><span class="hljs-params">part</span></span>) =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${part.emoji.icon}</span> <span class="hljs-subst">${part.title}</span>`</span>)
            .join(<span class="hljs-string">" • "</span>),
        },
        slug: newsletter.data.slug,
      },
    ])
  ),
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { getStaticPaths, GET } = OGImageRoute({
  param: <span class="hljs-string">"route"</span>,
  pages,
  getImageOptions: <span class="hljs-keyword">async</span> (_, { data, slug }) =&gt; ({
    title: <span class="hljs-string">`Ivo's Ecotainment Newsletter: <span class="hljs-subst">${data.title}</span>`</span>,
    description: data.description,
    bgGradient: &#91;
      &#91;<span class="hljs-number">255</span>, <span class="hljs-number">221</span>, <span class="hljs-number">0</span>], <span class="hljs-comment">// Yellow (from logo)</span>
      &#91;<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>], <span class="hljs-comment">// White</span>
    ],
    logo: {
      path: <span class="hljs-string">"./public/Ivos-Ecotainment-Newsletter_Salamander.png"</span>,
      size: &#91;<span class="hljs-number">1080</span>],
    },
    border: {
      color: &#91;<span class="hljs-number">83</span>, <span class="hljs-number">174</span>, <span class="hljs-number">90</span>], <span class="hljs-comment">// Green accent</span>
      width: <span class="hljs-number">2</span>,
      side: <span class="hljs-string">"inline-start"</span>,
    },
    font: {
      title: {
        size: <span class="hljs-number">42</span>,
        weight: <span class="hljs-string">"Bold"</span>,
        families: &#91;<span class="hljs-string">"Noto Sans"</span>, <span class="hljs-string">"Noto Color Emoji"</span>],
        color: &#91;<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],
      },
      description: {
        size: <span class="hljs-number">20</span>,
        weight: <span class="hljs-string">"Normal"</span>,
        families: &#91;<span class="hljs-string">"Noto Sans"</span>, <span class="hljs-string">"Noto Color Emoji"</span>],
        color: &#91;<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],
        lineHeight: <span class="hljs-number">1.4</span>,
      },
    },
    fonts: &#91;
      <span class="hljs-string">"./public/fonts/Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf"</span>,
      <span class="hljs-string">"./public/fonts/Noto_Color_Emoji/NotoColorEmoji-Regular.ttf"</span>,
    ],
    padding: <span class="hljs-number">50</span>,
  }),
});</code></span></pre>


<p><br /><strong>Some learned lessons:</strong><br />1. Logos: SVGs won&#8217;t work here. Use PNGs instead.<br />2. Fonts: Add font files, if you use fonts.<br />3. Emojis: If you use emojis in your content, include an emoji font (like &#8220;Noto Color Emoji&#8221;) to ensure they render correctly.</p>



<p><strong>Step 2: Integrate with astro-seo</strong></p>



<p>I use the <a href="https://github.com/jonasmerlin/astro-seo" target="_blank" rel="noopener" title="">astro-seo</a> component in my main Layout component to handle SEO metadata.</p>



<p>For the OG image I add a prop to be passed from the respective page:</p>


<pre class="wp-block-code"><span><code class="hljs language-typescript"><span class="hljs-keyword">const</span> { title, description, ogImage } = Astro.props;

&lt;SEO
  title={title}
  description={description}
  openGraph={{
    basic: {
      title,
      <span class="hljs-keyword">type</span>: <span class="hljs-string">"website"</span>,
      image: ogImage || <span class="hljs-string">`<span class="hljs-subst">${Astro.site}</span>Ivos-Ecotainment-Newsletter_big.png`</span>, <span class="hljs-comment">// Fallback</span>
    },
  }}
/&gt;</code></span></pre>


<p><strong>In Your Content Pages</strong>:<br />Construct the OG image path and pass it to your layout:</p>


<pre class="wp-block-code"><span><code class="hljs language-typescript"><span class="hljs-keyword">const</span> ogImage = <span class="hljs-string">`<span class="hljs-subst">${Astro.site?.href || <span class="hljs-string">""</span>}</span>og/<span class="hljs-subst">${entry.data.slug}</span>.png`</span>;

&lt;Layout title={entry.data.title} description={description} ogImage={ogImage}&gt;
  {<span class="hljs-comment">/* Post content */</span>}
&lt;<span class="hljs-regexp">/Layout&gt;</span></code></span></pre>


<p><strong>Astro Odds</strong>:</p>



<p>A bit odd is if your <code>astro.config.mjs</code> has <code>trailingSlash: "always"</code>, you&#8217;ll need to add a trailing slash to OG image URLs <strong>in development</strong>:<br /><code>http://localhost:4321/og/${entry.data.slug}.png/</code></p>



<p>In prod (no trailing slash needed) so you can just pass it as: <code>http://localhost:4321/og/${entry.data.slug}.png</code></p>



<p></p>



<p><strong>3. Testing OG Images</strong></p>



<p>As recommend in the Blog post of Aidan Kinzett: Use <a href="https://www.opengraph.xyz" target="_blank" rel="noopener" title="">opengraph.xyz</a>  to preview your OG images before sharing links.<br />Very good tool!</p>



<p>Here is how the OG image looks like with logo, title and description with emojis:</p>



<figure class="wp-block-image size-large"><a href="https://ivos-ecotainment-newsletter.info/archiv/pt58-party-parrot/"><img fetchpriority="high" decoding="async" width="1024" height="538" src="https://nerdpress.org/wp-content/uploads/2025/12/pt58-party-parrot-1024x538.png" alt="" class="wp-image-3462" srcset="https://nerdpress.org/wp-content/uploads/2025/12/pt58-party-parrot-1024x538.png 1024w, https://nerdpress.org/wp-content/uploads/2025/12/pt58-party-parrot-300x158.png 300w, https://nerdpress.org/wp-content/uploads/2025/12/pt58-party-parrot-768x403.png 768w, https://nerdpress.org/wp-content/uploads/2025/12/pt58-party-parrot.png 1200w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>Happy hacking!</p>



<p></p>The post <a href="https://nerdpress.org/2025/12/23/dynamic-opengraph-images-in-astro/">Dynamic OpenGraph Images in Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>5 Essential Plugins for Yazi File Manager</title>
		<link>https://nerdpress.org/2025/11/08/5-essential-plugins-for-yazi-file-manager/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Sat, 08 Nov 2025 10:35:27 +0000</pubDate>
				<category><![CDATA[TUI]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[tui]]></category>
		<category><![CDATA[yazi]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3442</guid>

					<description><![CDATA[<p>Yazi is my preferred terminal file manager, and these are my five essential plugins that improve my workflow. The Yazi package manager ya will be used to install the plugins mentioned below, which is shipped with Yazi. For the installation of necessary terminal tools, I will use brew, since I am currently on OSX. For &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2025/11/08/5-essential-plugins-for-yazi-file-manager/" class="more-link">Continue reading<span class="screen-reader-text"> "5 Essential Plugins for Yazi File Manager"</span></a></p>
The post <a href="https://nerdpress.org/2025/11/08/5-essential-plugins-for-yazi-file-manager/">5 Essential Plugins for Yazi File Manager</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>Yazi is my preferred terminal file manager, and these are my five essential plugins that improve my workflow.</p>



<p>The Yazi package manager <strong>ya</strong> will be used to install the plugins mentioned below, which is shipped with Yazi.</p>



<p>For the installation of necessary terminal tools, I will use <strong>brew</strong>, since I am currently on OSX. For Linux use the equivalents like <strong>apt</strong>.</p>



<span id="more-3442"></span>



<p><strong> 1. <a href="https://github.com/boydaihungst/mediainfo.yazi" target="_blank" rel="noopener" title="">MediaInfo</a></strong></p>



<p>The MediaInfo plugin adds detailed information to the preview of media files such as JPG, PNG, and other media formats.</p>



<p>Especially Width, Height and Size is very useful.</p>



<figure class="wp-block-image size-large"><a href="https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-scaled.png"><img decoding="async" width="1024" height="665" src="https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-1024x665.png" alt="Terminal view of MediaInfo plugin for Yazi rendering a png file" class="wp-image-3443" srcset="https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-1024x665.png 1024w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-300x195.png 300w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-768x499.png 768w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-1536x998.png 1536w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-mediainfo-preview-2048x1330.png 2048w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>Installation:</p>



<ol class="wp-block-list">
<li>Install MediaInfo terminal tool using Brew:<code> brew install mediainfo</code></li>



<li>Add the plugin to Yazi: <code>ya pack -a boydaihungst/mediainfo</code></li>



<li>Add the necessary configuration to your <strong>yazi.toml</strong> file.</li>
</ol>



<p><strong>2. <a href="https://github.com/wylie102/duckdb.yazi" target="_blank" rel="noopener" title="">DuckDB</a></strong></p>



<p>DuckDB.yazi is a plugin for handling CSV, TSV and Parquet files. <br />It uses <a href="https://duckdb.org/" target="_blank" rel="noopener" title="">DuckDB</a> under the hood for adhoc rendering of the data files.</p>



<p>It can also render JSON files as tables, but I have not enabled this, because I want json files preview to just render json as is.<br />So I just added the viewers for tsv and csv in yazi.toml:</p>



<p><code>{ name = "*.csv", run = "duckdb" },<br />{ name = "*.tsv", run = "duckdb" },</code></p>



<figure class="wp-block-image size-large"><a href="https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-scaled.png"><img decoding="async" width="1024" height="665" src="https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-1024x665.png" alt="Terminal view of DuckDB plugin for Yazi rendering a csv file" class="wp-image-3445" srcset="https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-1024x665.png 1024w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-300x195.png 300w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-768x499.png 768w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-1536x998.png 1536w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-duckdb-csv_preview-2048x1330.png 2048w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p><strong>3. <a href="https://github.com/Reledia/glow.yazi" target="_blank" rel="noopener" title="">Glow</a></strong></p>



<p>Glow.yazi is a plugin for previewing Markdown files.<br />I uses terminal markdown renderer <a href="https://github.com/charmbracelet/glow" target="_blank" rel="noopener" title="">Glow</a>.</p>



<figure class="wp-block-image size-large"><a href="https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-scaled.png"><img decoding="async" width="1024" height="665" src="https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-1024x665.png" alt="Terminal view of Glow plugin for Yazi rendering a markdown file" class="wp-image-3447" srcset="https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-1024x665.png 1024w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-300x195.png 300w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-768x499.png 768w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-1536x998.png 1536w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-glow-md-preview-2048x1330.png 2048w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>Installation: </p>



<ol class="wp-block-list">
<li>Install Glow: <code>brew install glow</code></li>



<li> Add the plugin to Yazi: <code>ya pack -a Reledia/glow.yazi</code></li>
</ol>



<p><strong>Note</strong>: the plugin is deprecated by now in favour of the new <a href="https://github.com/yazi-rs/plugins/tree/main/piper.yazi#piperyazi" target="_blank" rel="noopener" title="">Piper</a> plugin, a general-purpose previewer.<br />I might check this out as well in the near future.</p>



<p><strong>4. <a href="https://github.com/ndtoan96/ouch.yazi" target="_blank" rel="noopener" title="">Ouch.yazi</a></strong></p>



<p>This plugin uses <a href="https://github.com/ouch-org/ouch" target="_blank" rel="noopener" title="">Ouch</a> for handling zip, tar, and other archive formats.</p>



<figure class="wp-block-image size-large"><a href="https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-scaled.png"><img decoding="async" width="1024" height="665" src="https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-1024x665.png" alt="Terminal view of Ouch plugin for Yazi rendering a zip file" class="wp-image-3449" srcset="https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-1024x665.png 1024w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-300x195.png 300w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-768x499.png 768w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-1536x998.png 1536w, https://nerdpress.org/wp-content/uploads/2025/11/yazi-ouch-zip-preview-2048x1330.png 2048w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>Installation:</p>



<ol class="wp-block-list">
<li>Install Ouch and 7-zip using Brew: <code>brew install ouch 7-zip</code></li>



<li>Add the plugin to Yazi: <code>ya pack -a ndtoan96/ouch</code></li>



<li>Add the &#8220;C&#8221; to your `keymap.toml` file to compress files:<br /><code>[[mgr.prepend_keymap]]<br />on = ["C"]<br />run = "plugin ouch"<br />desc = "Compress with ouch"</code></li>
</ol>



<p>Simply press &#8220;Enter&#8221; on an archive file should uncompress the archive.</p>



<p><strong>5. <a href="https://github.com/Ape/open-with-cmd.yazi" target="_blank" rel="noopener" title="">OpenWithCmd</a></strong></p>



<p>OpenWithCmd is a plugin for opening files with specific commands, such as opening a file in LibreOffice. In Finder one would usually use right click &#8220;Open with&#8221; dialog.</p>



<p>Installation:</p>



<ol class="wp-block-list">
<li>Add the plugin to Yazi: <code>ya pack -a Ape/open-with-cmd.yazi</code></li>



<li>Add the &#8220;o&#8221; and &#8220;O&#8221; to your `keymap.toml` file to open the OpenWith dialog:<br /><code>[[mgr.prepend_keymap]]<br />on = "o"<br />run = "plugin open-with-cmd --args=block"<br />desc = "Open with command in the terminal"<br />[[mgr.prepend_keymap]]<br />on = "O"<br />run = "plugin open-with-cmd"<br />desc = "Open with command</code>&#8220;</li>
</ol>



<p>To open a file in LibreOffice from Yazi, type &#8220;o&#8221; and enter <code>calc type:soffice --calc</code> in the dialog.</p>



<p>Note that the window might not focus directly, and you may need to check if it is opened in the background.</p>



<p>This plugin overrides the `o/O` keymap, but you can still use `Enter` or `Shift+Enter` to open files directly in the default app.</p>The post <a href="https://nerdpress.org/2025/11/08/5-essential-plugins-for-yazi-file-manager/">5 Essential Plugins for Yazi File Manager</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ObfuscateLink Web Component in Astro</title>
		<link>https://nerdpress.org/2025/02/07/obfuscatelink-web-component-in-astro/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Fri, 07 Feb 2025 09:36:02 +0000</pubDate>
				<category><![CDATA[Astro]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Web components]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3393</guid>

					<description><![CDATA[<p>Using Web Components in Astro wasn&#8217;t as straightforward as expected. Here&#8217;s an example showing how to integrate the obfuscate-link web component into an Astro project.&#160; First, add the Obfuscate-Link web component to the project: Now register ObfuscateLink with customElements:&#160; This occurs in the Layout.astro component, making it available across all pages. The registration happens within &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2025/02/07/obfuscatelink-web-component-in-astro/" class="more-link">Continue reading<span class="screen-reader-text"> "ObfuscateLink Web Component in Astro"</span></a></p>
The post <a href="https://nerdpress.org/2025/02/07/obfuscatelink-web-component-in-astro/">ObfuscateLink Web Component in Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>Using Web Components in <a href="https://astro.build/" title="">Astro</a> wasn&#8217;t as straightforward as expected. Here&#8217;s an example showing how to integrate the <a href="https://github.com/ivoba/obfuscate-wc" title="">obfuscate-link</a> web component into an Astro project.&nbsp;</p>



<p>First, add the Obfuscate-Link web component to the project:</p>


<pre class="wp-block-code"><span><code class="hljs">npm install obfuscate-link-web-component</code></span></pre>


<span id="more-3393"></span>



<p>Now register ObfuscateLink with customElements:&nbsp;</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">import</span> { ObfuscateLink } <span class="hljs-keyword">from</span> <span class="hljs-string">'obfuscate-link-web-component'</span>;
      <span class="hljs-comment">// Only define the custom element if it hasn't been defined yet</span>
      <span class="hljs-keyword">if</span> (!customElements.get(<span class="hljs-string">'obfuscate-link'</span>)) {
        customElements.define(<span class="hljs-string">'obfuscate-link'</span>, ObfuscateLink);
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></code></span></pre>


<p>This occurs in the Layout.astro component, making it available across all pages. The registration happens within a script tag in the HTML to execute only on the client side, avoiding it execution while SSR rendering performed by Astro beforehand.</p>



<p>Next, create an Astro component that wraps the web-component and handles the obfuscation by encoding the passed prop.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">---
type BaseProps = {
  id?: string;
}
type EmailProps = BaseProps &amp; {
  <span class="hljs-attr">email</span>: string;
  tel?: never;
  sms?: never;
  facetime?: never;
  href?: never;
}
type TelProps = BaseProps &amp; {
  email?: never;
  tel: string;
  sms?: never;
  facetime?: never;
  href?: never;
}
type SmsProps = BaseProps &amp; {
  email?: never;
  tel?: never;
  sms: string;
  facetime?: never;
  href?: never;
}
type FacetimeProps = BaseProps &amp; {
  email?: never;
  tel?: never;
  sms?: never;
  facetime: string;
  href?: never;
}
type HrefProps = BaseProps &amp; {
  email?: never;
  tel?: never;
  sms?: never;
  facetime?: never;
  href: string;
}
<span class="hljs-keyword">export</span> type Props = EmailProps | TelProps | SmsProps | FacetimeProps | HrefProps;
<span class="hljs-keyword">const</span> props = Astro.props;
<span class="hljs-keyword">const</span> { id } = props;
<span class="hljs-comment">// Find the active prop (email, tel, sms, facetime, or href)</span>
<span class="hljs-keyword">const</span> activeProp = <span class="hljs-built_in">Object</span>.entries(props).find(<span class="hljs-function">(<span class="hljs-params">&#91;key, value]</span>) =&gt;</span> 
  key !== <span class="hljs-string">'id'</span> &amp;&amp; value !== <span class="hljs-literal">undefined</span>
);
<span class="hljs-keyword">let</span> attribute = <span class="hljs-string">''</span>;
<span class="hljs-keyword">let</span> value = <span class="hljs-string">''</span>;
<span class="hljs-keyword">if</span> (activeProp) {
  &#91;attribute, value] = activeProp;
  <span class="hljs-keyword">const</span> orgValue = value;
  value = Buffer.from(value).toString(<span class="hljs-string">'base64'</span>);
}
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">obfuscate-link</span>
  <span class="hljs-attr">id</span>=<span class="hljs-string">{id}</span>
  {<span class="hljs-attr">...</span>{&#91;<span class="hljs-attr">attribute</span>]<span class="hljs-attr">:</span> <span class="hljs-attr">value</span>}}
&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">obfuscate-link</span>&gt;</span></span></code></span></pre>


<p>Finally, you can utilize both the Astro component and the Web component throughout your Astro project.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">
---
<span class="hljs-keyword">import</span> ObfuscateLink <span class="hljs-keyword">from</span> <span class="hljs-string">'./ObfuscateLink.astro'</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
    Email: <span class="hljs-tag">&lt;<span class="hljs-name">ObfuscateLink</span> <span class="hljs-attr">email</span>=<span class="hljs-string">"info@example.com"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span></code></span></pre>


<p>The complete code can be seen in this Gist: </p>



<p><a href="https://gist.github.com/ivoba/222d5a49ad4542392772195c5e5ad032" target="_blank" rel="noopener" title="">https://gist.github.com/ivoba/222d5a49ad4542392772195c5e5ad032</a></p>The post <a href="https://nerdpress.org/2025/02/07/obfuscatelink-web-component-in-astro/">ObfuscateLink Web Component in Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Deploy local build with Deployer7</title>
		<link>https://nerdpress.org/2024/11/08/deploy-local-build-with-deployer7/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Fri, 08 Nov 2024 12:38:46 +0000</pubDate>
				<category><![CDATA[Deployment]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Deployer]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3347</guid>

					<description><![CDATA[<p>Deployer is a great tool to deploy your PHP Project. Deployer executes a set of commands on the target server to build your project and enable the newly built version. A typical deployment process with Deployer involves SSHing into the target machine, where it performs a Git checkout of the project, installs dependencies via Composer, &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2024/11/08/deploy-local-build-with-deployer7/" class="more-link">Continue reading<span class="screen-reader-text"> "Deploy local build with Deployer7"</span></a></p>
The post <a href="https://nerdpress.org/2024/11/08/deploy-local-build-with-deployer7/">Deploy local build with Deployer7</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p><br /><a href="https://deployer.org/" target="_blank" rel="noopener" title="">Deployer</a> is a great tool to deploy your PHP Project.</p>



<p>Deployer executes a set of commands on the target server to build your project and enable the newly built version. A typical deployment process with Deployer involves SSHing into the target machine, where it performs a Git checkout of the project, installs dependencies via Composer, runs build commands, and possibly triggers some database migrations. When everything is successful, it will symlink the webroot to the new release.</p>



<p>On some servers, however, there are limitations that make this process unfeasible. For instance, you can&#8217;t install Composer, Git isn&#8217;t available, the CLI PHP version is different and can&#8217;t be changed, or certain asset-building processes aren&#8217;t possible because Node.js isn&#8217;t installed. This is often the case with shared hosting.</p>



<span id="more-3347"></span>



<p>The strategy, therefore, is to build the project locally and upload it to the target machine. <br />In Deployer 6 there was a buildIn mechanism for building your project on the local host.<br />However with Deployer version 7 this has been removed and building your project locally has become a bit more fiddly. <br /><br />With Deployer 6 you could just use the <code>local</code>() method on a task and set the <code>deploy_path</code> to a local dir.</p>


<pre class="wp-block-code"><span><code class="hljs language-php">task(<span class="hljs-string">'build'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
    set(<span class="hljs-string">'deploy_path'</span>, <span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">'/.build'</span>);
    invoke(<span class="hljs-string">'deploy:prepare'</span>);
    invoke(<span class="hljs-string">'deploy:release'</span>);
    invoke(<span class="hljs-string">'deploy:update_code'</span>);
    invoke(<span class="hljs-string">'deploy:vendors'</span>);
    invoke(<span class="hljs-string">'deploy:clear_paths'</span>);
    invoke(<span class="hljs-string">'deploy:symlink'</span>);
})-&gt;local();</code></span></pre>


<p>Then you could combine this with an upload task in a deploy task an you are done.</p>


<pre class="wp-block-code"><span><code class="hljs language-php">task(<span class="hljs-string">'upload'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
     upload(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/.build/current/"</span>, <span class="hljs-string">'{{release_path}}'</span>, &#91;<span class="hljs-string">'--links'</span>]);
 });

 task(<span class="hljs-string">'deploy'</span>, &#91;
     <span class="hljs-string">'build'</span>,
     <span class="hljs-string">'upload'</span>,
     <span class="hljs-string">'cleanup'</span>,
     <span class="hljs-string">'success'</span>
 ]);</code></span></pre>


<p>See here for the docs of Deployer 6: <br /><a href="https://deployer.org/docs/6.x/advanced/deploy-strategies#build-server" target="_blank" rel="noopener" title="">https://deployer.org/docs/6.x/advanced/deploy-strategies#build-server</a></p>



<p>Since Deployer 7, however, the <code>local()</code> method was removed. Instead, there is now a <code>localhost()</code> method, which defines a host for local builds. Additionally, you still need to define the remote host as the target destination for the upload. <br />However, you cannot specify a single task to run on a specific host, and therefore you cannot combine tasks for <code>localhost</code> and the remote host within a single task.</p>



<p>To build the project, we now need two separate tasks. <br />I managed to make it work with two tasks and two hosts: one for local build and one that takes the fresh local build and uploads it to the remote host.</p>


<pre class="wp-block-code"><span><code class="hljs language-php">localhost(<span class="hljs-string">'local'</span>)-&gt;set(<span class="hljs-string">'deploy_path'</span>, <span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">'/.build'</span>);

host(<span class="hljs-string">'remote'</span>)
    -&gt;setHostname(<span class="hljs-string">'server-host.com'</span>)
    -&gt;set(<span class="hljs-string">'remote_user'</span>, <span class="hljs-string">'user'</span>)
    -&gt;set(<span class="hljs-string">'deploy_path'</span>, <span class="hljs-string">'/www/blog'</span>)
    -&gt;set(<span class="hljs-string">'http_user'</span>, <span class="hljs-string">'user'</span>);
....

task(<span class="hljs-string">'build'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// build it</span>
})-&gt;once();

task(<span class="hljs-string">'upload'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
    upload(<span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">"/.build/current/"</span>, <span class="hljs-string">'{{deploy_path}}'</span>, &#91;<span class="hljs-string">'--links'</span>]);
});</code></span></pre>


<p>Then I simply run two separate commands: one for building on the local host and one for uploading to the remote host.</p>


<pre class="wp-block-code"><span><code class="hljs">vendor/bin/dep build local
vendor/bin/dep upload remote</code></span></pre>


<p>Not as convenient as previously but it works. :)</p>



<p>See this issue for context:  <a href="https://github.com/deployphp/deployer/issues/2838" target="_blank" rel="noopener" title="">https://github.com/deployphp/deployer/issues/2838</a></p>The post <a href="https://nerdpress.org/2024/11/08/deploy-local-build-with-deployer7/">Deploy local build with Deployer7</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Testing PDF creation with headless chrome and PHP</title>
		<link>https://nerdpress.org/2023/07/28/testing-pdf-creation-with-headless-chrome-and-php/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Fri, 28 Jul 2023 09:56:50 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Minicli]]></category>
		<category><![CDATA[pdf]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3249</guid>

					<description><![CDATA[<p>I had the task the other day to use a headless chrome to generate PDF files from websites in a PHP app. The plan was to use chrome-php with a headless chrome to generate the PDF. Usually you would install chrome/chromium on a linux server via apt and just run chrome from the PATH with &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2023/07/28/testing-pdf-creation-with-headless-chrome-and-php/" class="more-link">Continue reading<span class="screen-reader-text"> "Testing PDF creation with headless chrome and PHP"</span></a></p>
The post <a href="https://nerdpress.org/2023/07/28/testing-pdf-creation-with-headless-chrome-and-php/">Testing PDF creation with headless chrome and PHP</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>I had the task the other day to use a headless chrome to generate PDF files from websites in a PHP app.</p>



<p>The plan was to use <a href="https://github.com/chrome-php/chrome" target="_blank" rel="noopener" title="">chrome-php</a> with a headless chrome to generate the PDF.</p>



<p>Usually you would install chrome/chromium on a linux server via <em>apt</em> and just run chrome from the PATH with chrome.<br />Since i was on shared hosting i was not sure if this was possible since i was not allowed to run <em>apt</em> commands.<br />So i tried to use Puppeteer which ships a headless chrome executable and use just this directly.<br />I installed Puppeteer with <em>npm</em> locally and uploaded the chrome executable to the shared hosting.<br />Puppeteer will place the headless chrome in the .cache dir in your home directory, f.e.:</p>



<p><code>~/.cache/puppeteer/chrome/linux-113.0.5672.63/chrome-linux64/chrome</code></p>



<p>Upload:</p>



<p><code>scp -r ~/.cache/puppeteer/chrome/linux-113.0.5672.63/chrome-linux64 me@sharedhosting:/usr/home/test</code></p>



<span id="more-3249"></span>



<p>I also created a nice little command line tool to create the pdf with chrome-php.<br />I used <a href="https://github.com/minicli/minicli" target="_blank" rel="noopener" title="">minicli</a> which is a &#8220;minimalist, dependency-free framework for building CLI-centric PHP applications&#8221;.<br />So you can create cli commands fast and without all the clutter, exactly what i needed for this small test.<br />You can find the code on Github: <a href="https://github.com/ivoba/chrome-php-minicli" target="_blank" rel="noopener" title="">https://github.com/ivoba/chrome-php-minicli</a><br />The command is here: <a href="https://github.com/ivoba/chrome-php-minicli/blob/main/command.php" target="_blank" rel="noopener" title="">https://github.com/ivoba/chrome-php-minicli/blob/main/command.php</a></p>



<p>Then i tried to generate the pdf:<br /><code>CHROME_PATH="/usr/home/test/chrome" php command.php pdf url=https://ivo-bathke.name</code></p>



<p>However this failed because the server lacked required dependencies to run the chrome executable.<br />You can check this by running this command:</p>



<p><code>me@sharedhosting:/usr/home/test$ ldd chrome-linux64/chrome | grep not<br />libatk-1.0.so.0 =&gt; not found<br />libatk-bridge-2.0.so.0 =&gt; not found<br />libxkbcommon.so.0 =&gt; not found<br />libatspi.so.0 =&gt; not found<br />libXcomposite.so.1 =&gt; not found<br />libXrandr.so.2 =&gt; not found<br />libgbm.so.1 =&gt; not found</code></p>



<p>Ok, so it wont work with the uploaded chrome executable.<br />What a pity but still a finding.</p>



<p>In the end i contacted the support and fortunatly they were willing and able to install chromium via <em>apt</em>. Nice!<br /><br />Eventually it worked with:<br /><code>CHROME_PATH="chromium" php command.php pdf url=https://ivo-bathke.name</code></p>



<p>Pdf created :)</p>



<p></p>The post <a href="https://nerdpress.org/2023/07/28/testing-pdf-creation-with-headless-chrome-and-php/">Testing PDF creation with headless chrome and PHP</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>10 reasons to replace your CMS with Astro</title>
		<link>https://nerdpress.org/2023/03/23/10-reasons-to-replace-your-cms-with-astro/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Thu, 23 Mar 2023 16:45:57 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Astro]]></category>
		<category><![CDATA[static site generator]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3234</guid>

					<description><![CDATA[<p>I replaced another CMS powered site with an Astro powered static site the other day.Here is why: This is based on of a twitter thread of mine and crossposted here for the sake of owning my content. :)</p>
The post <a href="https://nerdpress.org/2023/03/23/10-reasons-to-replace-your-cms-with-astro/">10 reasons to replace your CMS with Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>I replaced another CMS powered site with an <a href="https://astro.build/" target="_blank" rel="noopener" title="">Astro</a> powered static site the other day.<br />Here is why:</p>



<span id="more-3234"></span>



<ol class="wp-block-list">
<li>Content changed rarely and when, authors forgot how to do it and asked me anyway.</li>



<li>Content is now part of development. <br />The &#8220;paid&#8221; support time is now rather spent for adding content than for helping the authors adding their content.<br />They will just send me an email.</li>



<li>Content is now versioned in git.</li>



<li>Hosting is cheaper, no need for the PHP Mysql package.</li>



<li>A static site is easier to host. No forced PHP updates by the hoster.</li>



<li>No security attack vectors on the site. Try this with WordPress ;)</li>



<li><a href="https://astro.build" target="_blank" rel="noopener" title="">Astro</a> comes with topnotch frontend tooling and green lighthouse audit by default. Try this with your CMS ;)</li>



<li>Contact forms are overrated. Mailto links work. If a contact form is your only dynamic requirement, ditch it and consider to go static.</li>



<li>A static site loads faster and does not need to run the compute cycles of a dynamic language or a database.</li>



<li>Last but not least: A static site presumably has a lower carbon footprint out of the box because of less computing and optimized frontend tooling.</li>
</ol>



<p>This is based on of a <a href="https://twitter.com/ivobathke/status/1632665077307305986" target="_blank" rel="noopener" title="">twitter thread</a> of mine and crossposted here for the sake of owning my content. :)</p>The post <a href="https://nerdpress.org/2023/03/23/10-reasons-to-replace-your-cms-with-astro/">10 reasons to replace your CMS with Astro</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Import data into MySql with docker-compose</title>
		<link>https://nerdpress.org/2023/02/24/import-data-into-mysql-with-docker-compose/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Fri, 24 Feb 2023 10:04:31 +0000</pubDate>
				<category><![CDATA[docker-compose]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[PHPStorm]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3221</guid>

					<description><![CDATA[<p>Having a docker-compose setup which involves a Database like Mysql or MariaDB, then at some point you might want to import data into those Databases. There are several ways to import the data in your docker-compose setup. So let&#8217;s see how we can do this: 1. Using a volume for import data This applies to &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2023/02/24/import-data-into-mysql-with-docker-compose/" class="more-link">Continue reading<span class="screen-reader-text"> "Import data into MySql with docker-compose"</span></a></p>
The post <a href="https://nerdpress.org/2023/02/24/import-data-into-mysql-with-docker-compose/">Import data into MySql with docker-compose</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>Having a docker-compose setup which involves a Database like Mysql or MariaDB, then at some point you might want to import data into those Databases.</p>



<p>There are several ways to import the data in your docker-compose setup.</p>



<ol class="wp-block-list">
<li>Using a volume for import data</li>



<li>Using mysql client from commandline with docker-compose exec</li>



<li>Using phpmyadmin in docker-compose setup</li>



<li>Using a mysql GUI client on the host and connect to the DB in the Docker container</li>
</ol>



<p>So let&#8217;s see how we can do this:</p>



<span id="more-3221"></span>



<h2 class="wp-block-heading">1. Using a volume for import data</h2>



<p>This applies to both official <a href="https://hub.docker.com/_/mysql/" target="_blank" rel="noopener" title="">Mysql</a><em> and <a href="https://hub.docker.com/_/mariadb/" target="_blank" rel="noopener" title="">MariaDB</a></em> official images.<br />These have builtin import mechanism to import data.<br />To quote from the docs:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory</p>
</blockquote>



<p>So all dumps that are found in the <em>/docker-entrypoint-initdb.d</em> directory of the image will be imported unless the database already contains data.</p>



<p>So let&#8217;s add this volume to our docker-compose.yaml</p>



<p><code>volumes: - ./database_dump.sql:/docker-entrypoint-initdb.d/datadump.sql</code></p>



<p>On next <code>docker-compose up</code> the data will be imported in your empty database.<br />You will see something like this in your docker-compose logs:</p>



<p>2023-02-06 15:40:16+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/database_dump.sql</p>



<p>Note: if there is already data in the database you need to clear the data first by f.e. clearing the volume, otherwise the import will not start:<br />You can clear all volumes of your docker-compose setup with (caution: this will clear <em>all</em> volumes, not just the database volume):</p>



<p><code>docker-compose down -v</code></p>



<h2 class="wp-block-heading">2. Using mysql client from commandline with docker-compose exec</h2>



<p>With this one liner you can import a SQL dump from a docker image that has a MySql/MariaDB client installed and are linked to the DB container:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">docker-compose exec my-app bash -c <span class="hljs-string">"mysql -u root -h mysql --password=root database &lt; database_dump.sql"</span></code></span></pre>


<p>This will execute the application image and import the data with the mysql client which connects to the host <code>mysql</code> which is the linked mysql container.</p>



<h2 class="wp-block-heading">3. Use phpmyadmin in docker-compose setup</h2>



<p>If the above is too consolish then you also can just add a phpmyaadmin container in your docker-compose setup and administer the database with a GUI from the browser.</p>


<pre class="wp-block-code"><span><code class="hljs">  myproject_phpmyadmin:
	image: phpmyadmin/phpmyadmin:latest
	ports:
	  - 8080:80
	environment:
	  PMA_HOST: myproject_mysql</code></span></pre>


<p>Then you can just open <em>http://localhost:8080</em> in the browser and log into to Phpmyadmin.<br />To import data go to the import tab and upload the dump file.</p>



<figure class="wp-block-image size-full"><a href="https://nerdpress.org/wp-content/uploads/2023/02/phpmyadmin_import_tab.png"><img decoding="async" width="481" height="51" src="https://nerdpress.org/wp-content/uploads/2023/02/phpmyadmin_import_tab.png" alt="" class="wp-image-3222" srcset="https://nerdpress.org/wp-content/uploads/2023/02/phpmyadmin_import_tab.png 481w, https://nerdpress.org/wp-content/uploads/2023/02/phpmyadmin_import_tab-300x32.png 300w" sizes="(max-width: 481px) 100vw, 481px" /></a><figcaption class="wp-element-caption">PhpMyAdmin Import tab</figcaption></figure>



<h2 class="wp-block-heading">4. Using a mysql GUI client on the host and connect to the DB in the Docker container</h2>



<p>For this approach you just need to open a port in the database container to the outside and use this port with localhost in the settings of your database client.</p>


<pre class="wp-block-code"><span><code class="hljs language-php">  myproject_mysql:
    image: mysql:<span class="hljs-number">5.7</span>
    hostname: mysql.${DOMAIN}
    container_name: ${CONTAINER_NAME}.mysql
      volumes:
	- ${PWD}/data/mysql/:/<span class="hljs-keyword">var</span>/lib/mysql
	- ./container/mysql/my.cnf:/etc/mysql/conf.d/z_my.cnf
    ports: <span class="hljs-comment"># open the mysql port to the host</span>
      - <span class="hljs-string">"3306:3306"</span>
    environment:
  ...</code></span></pre>


<p>See this screenshot from IntellIj&#8217;s PHPStorm Database tool:</p>



<figure class="wp-block-image size-full"><a href="https://nerdpress.org/wp-content/uploads/2023/02/PHPStorm_Database_property.png"><img decoding="async" width="807" height="491" src="https://nerdpress.org/wp-content/uploads/2023/02/PHPStorm_Database_property.png" alt="" class="wp-image-3223" srcset="https://nerdpress.org/wp-content/uploads/2023/02/PHPStorm_Database_property.png 807w, https://nerdpress.org/wp-content/uploads/2023/02/PHPStorm_Database_property-300x183.png 300w, https://nerdpress.org/wp-content/uploads/2023/02/PHPStorm_Database_property-768x467.png 768w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a><figcaption class="wp-element-caption">PHPStorm DB settings</figcaption></figure>



<p>From my experience these clients are very good to check data but not so good for import, at least I had rather poor experience in the PHPStorm DB Tool.<br />So going consolish on imports is recommended :)</p>The post <a href="https://nerdpress.org/2023/02/24/import-data-into-mysql-with-docker-compose/">Import data into MySql with docker-compose</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Nerdpress on Mastodon</title>
		<link>https://nerdpress.org/2023/01/06/nerdpress-on-mastodon/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 06 Jan 2023 12:31:18 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Mastodon]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3202</guid>

					<description><![CDATA[<p>We follow the PHP community and are now also Mastodon! Yay!Follow us here: https://phpc.social/@nerdpress_orgAll our Blog posts will be announced there as on Twitter. Since we blog a lot about PHP in general and Symfony in particular https://phpc.social seems like a good choice for us as our Mastodon instance. If you are a PHP nerd &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2023/01/06/nerdpress-on-mastodon/" class="more-link">Continue reading<span class="screen-reader-text"> "Nerdpress on Mastodon"</span></a></p>
The post <a href="https://nerdpress.org/2023/01/06/nerdpress-on-mastodon/">Nerdpress on Mastodon</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>We follow the PHP community and are now also Mastodon! Yay!<br />Follow us here: <a href="https://phpc.social/@nerdpress_org" target="_blank" rel="noreferrer noopener">https://phpc.social/@nerdpress_org</a><br />All our Blog posts will be announced there as on Twitter.</p>



<p>Since we blog a lot about PHP in general and Symfony in particular <a href="https://phpc.social" target="_blank" rel="noreferrer noopener">https://phpc.social</a> seems like a good choice for us as our Mastodon instance.</p>



<span id="more-3202"></span>



<p>If you are a PHP nerd you might consider to join this instance as well.</p>



<p>Other fits could be:</p>



<ul class="wp-block-list">
<li><a href="https://phparch.social" target="_blank" rel="noreferrer noopener">https://phparch.social</a> the Mastodon instance of php[architect] Magazine</li>



<li><a href="https://fosstodon.org" target="_blank" rel="noreferrer noopener">https://fosstodon.org</a> it is a broader community around all OpenSource Software</li>



<li><a href="https://uiuxdev.social" target="_blank" rel="noreferrer noopener">https://uiuxdev.social</a> a more frontend resp. web development focused Mastodon community</li>
</ul>



<p>If you wonder how to find well know figures from the PHP scene on Mastodon Spatie&#8217;s Freek Van der Herten got you covered: <a href="https://freek.dev/2384-laravel-and-php-developers-to-follow-on-mastodon" target="_blank" rel="noreferrer noopener">https://freek.dev/2384-laravel-and-php-developers-to-follow-on-mastodon</a></p>



<p>Happy Tooting!</p>The post <a href="https://nerdpress.org/2023/01/06/nerdpress-on-mastodon/">Nerdpress on Mastodon</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Symfony deprecation log channel</title>
		<link>https://nerdpress.org/2022/09/13/symfony-deprecation-log-channel/</link>
		
		<dc:creator><![CDATA[Ivo Bathke]]></dc:creator>
		<pubDate>Tue, 13 Sep 2022 13:53:02 +0000</pubDate>
				<category><![CDATA[Symfony]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[Monolog]]></category>
		<guid isPermaLink="false">https://nerdpress.org/?p=3170</guid>

					<description><![CDATA[<p>Are you annoyed of too many deprecation warnings in you logs of your symfony app?Probably yes because it is really a lot of noise.However deprecation logs are still useful to ensure future compatibility of your app. So since version 5.1 symfony will log deprecations to a dedicated log channel when it exists and ships with &#8230; </p>
<p class="link-more"><a href="https://nerdpress.org/2022/09/13/symfony-deprecation-log-channel/" class="more-link">Continue reading<span class="screen-reader-text"> "Symfony deprecation log channel"</span></a></p>
The post <a href="https://nerdpress.org/2022/09/13/symfony-deprecation-log-channel/">Symfony deprecation log channel</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></description>
										<content:encoded><![CDATA[<p>Are you annoyed of too many deprecation warnings in you logs of your symfony app?<br />Probably yes because it is really a lot of noise.<br />However deprecation logs are still useful to ensure future compatibility of your app.</p>



<p>So since version 5.1 symfony will log deprecations to a dedicated log channel when it exists and ships with this monolog config:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">monolog:
    channels:
        - deprecation <span class="hljs-comment"># Deprecations are logged in the dedicated "deprecation" channel when it exists</span></code></span></pre>


<p>This is added already in the <a href="https://github.com/symfony/recipes/blob/main/symfony/monolog-bundle/3.1/config/packages/prod/deprecations.yaml" target="_blank" rel="noreferrer noopener">recipe</a> and ships when installing symfony.</p>



<p>Ok, but the handler for this deprecation channel is not configured, so you have to do this yourself.<br />How? Add this to your monolog config:</p>



<span id="more-3170"></span>


<pre class="wp-block-code"><span><code class="hljs language-javascript">...
when@dev:
   monolog:
       handlers:
           main:
	       type: stream
	       <span class="hljs-attr">path</span>: <span class="hljs-string">"%kernel.logs_dir%/%kernel.environment%.log"</span>
	       <span class="hljs-attr">level</span>: debug
	       <span class="hljs-attr">channels</span>: &#91;<span class="hljs-string">"!event"</span>, <span class="hljs-string">"!deprecation"</span>]
	   <span class="hljs-attr">deprecation</span>:
	       type: rotating_file
	       <span class="hljs-attr">path</span>: <span class="hljs-string">"%kernel.logs_dir%/%kernel.environment%.deprecations.log"</span>
	       <span class="hljs-attr">max_files</span>: <span class="hljs-number">2</span>
	       <span class="hljs-attr">channels</span>: &#91;deprecation]
	   <span class="hljs-attr">console</span>:
	       type: <span class="hljs-built_in">console</span>
	       <span class="hljs-attr">process_psr_3_messages</span>: <span class="hljs-literal">false</span>
               <span class="hljs-attr">channels</span>: &#91;<span class="hljs-string">"!event"</span>, <span class="hljs-string">"!doctrine"</span>, <span class="hljs-string">"!console"</span>, <span class="hljs-string">"!deprecation"</span>]
...
</code></span></pre>


<p>What is happening here?<br />We declare a new handler for deprecations named <code>deprecation</code> when in dev environment.<br />This handler listens to the deprecation channel.<br />We declare it as a rotating file since the assumption is we dont need to keep this log entries for longer.<br />The other handlers will ignore the deprecation channel by adding a !deprecation to the channel directive.</p>



<p>The deprecation channel was already created and so all deprecations are now not in the main log file but in a dedicated deprecation log file.</p>



<p>For production we dont need to edit configuration since the log level here is info and therefore deprecations will not be considered anyway.</p>The post <a href="https://nerdpress.org/2022/09/13/symfony-deprecation-log-channel/">Symfony deprecation log channel</a> first appeared on <a href="https://nerdpress.org">Nerdpress.org</a>.]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
