<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Bushbaby]]></title><description><![CDATA[Bas Kamer, freelance developer]]></description><link>https://bushbaby.nl/</link><image><url>https://bushbaby.nl/favicon.png</url><title>Bushbaby</title><link>https://bushbaby.nl/</link></image><generator>Ghost 5.87</generator><lastBuildDate>Mon, 06 Apr 2026 14:52:20 GMT</lastBuildDate><atom:link href="https://bushbaby.nl/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Launch of iSole 3D corporate website to the public]]></title><description><![CDATA[iSole 3D website went live...]]></description><link>https://bushbaby.nl/announcement-launch-of-isole-3d-with-new-corporate-website-to-the-public/</link><guid isPermaLink="false">667e8dcf109ca80001dc89d3</guid><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Fri, 28 Jun 2024 11:00:31 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2024/06/9A1D5A62-9E88-4F93-BE8C-3D08C6C11982_1_105_c-1.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://bushbaby.nl/content/images/2024/06/9A1D5A62-9E88-4F93-BE8C-3D08C6C11982_1_105_c-1.jpeg" alt="Launch of iSole 3D corporate website to the public"><p>I am pleased to be able to announce the launch of iSole 3D, a state-of-the-art 3D printer specifically designed for insole production, on our - now publicly available - corporate website.</p><p>As lead software developer, I was responsible for developing the advanced software stack, including a user-friendly interface for easy printer management. iSole 3D brings precision, customisation, and efficiency to insole production, allowing you to produce high-quality insoles tailored to specific needs.</p><p>Visit our the website at <a href="http://isole3d.com/?ref=bushbaby.nl">isole3d.com</a> to explore some of the features of iSole 3D and learn how it can improve your insole production process.</p><p>Thank you for your support!</p><p>Bas Kamer<br>Lead Software Developer<br>iSole 3D</p>]]></content:encoded></item><item><title><![CDATA[Streamlining Development with `nestjs-ember-static`]]></title><description><![CDATA[Introducing `nestjs-ember-static`, a module designed to seamlessly integrate EmberJS SPAs with NestJS. Enjoy features like live reloading, config injection, and perfect compatibility for mono repositories. Streamline your development process!]]></description><link>https://bushbaby.nl/a-bridge-between-nestjs-and-emberjs/</link><guid isPermaLink="false">667d34f5109ca80001dc89c1</guid><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Thu, 27 Jun 2024 09:54:28 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2024/06/DALL-E-2024-06-27-11.56.31---A-vibrant-and-modern-poster-showcasing-the--nestjs-ember-static--module.-The-poster-should-feature-a-NestJS-logo-and-an-EmberJS-logo-interconnected--s.webp" medium="image"/><content:encoded><![CDATA[<img src="https://bushbaby.nl/content/images/2024/06/DALL-E-2024-06-27-11.56.31---A-vibrant-and-modern-poster-showcasing-the--nestjs-ember-static--module.-The-poster-should-feature-a-NestJS-logo-and-an-EmberJS-logo-interconnected--s.webp" alt="Streamlining Development with `nestjs-ember-static`"><p>When working on multiple projects, I kept running into the same challenge: serving EmberJS SPAs within a NestJS backend. The existing <code>@nestjs/serve-static</code> module didn&apos;t quite cut it, so I created <code>nestjs-ember-static</code>.</p><h3 id="why-nestjs-ember-static">Why <code>nestjs-ember-static</code>?</h3><p><strong>Live Reloading:</strong><br>Keep your development smooth with live reloading, so you see changes instantly.</p><p><strong>Config Injection:</strong><br>Inject configuration directly into the <code>index.html</code> file, allowing dynamic adjustments.</p><p><strong>Perfect for Mono Repositories:</strong><br>Easily manage both front-end and back-end codebases in a single repository.</p><h3 id="easy-setup">Easy Setup</h3><p><strong>Basic Configuration:</strong></p><pre><code class="language-typescript">import { Module } from &apos;@nestjs/common&apos;;
import { join } from &apos;path&apos;;
import { EmberModule } from &apos;nestjs-ember-static&apos;;

@Module({
  imports: [
    EmberModule.forRoot({
      rootPath: join(__dirname, &apos;../frontend/dist&apos;),
    }),
  ],
})
export class AppModule {}
</code></pre><p><strong>Advanced Configuration:</strong></p><pre><code class="language-typescript">import { Module } from &apos;@nestjs/common&apos;;
import { join } from &apos;path&apos;;
import { EmberModule } from &apos;nestjs-ember-static&apos;;
import { ConfigService } from &apos;@nestjs/config&apos;;

@Module({
  imports: [
    EmberModule.forRootAsync({
      useFactory: async (configService: ConfigService) =&gt; {
        const env = configService.get(&apos;env&apos;, { infer: true });
        return {
          rootPath: env[&apos;name&apos;] === &apos;development&apos;
            ? join(env[&apos;appRoot&apos;], &apos;../frontend/dist&apos;)
            : join(env[&apos;appRoot&apos;], &apos;client&apos;),
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
</code></pre><h3 id="custom-inject-helpers">Custom Inject Helpers</h3><p>Make custom changes to your HTML with inject helpers:</p><pre><code class="language-typescript">export abstract class AbstractInjectionHelper {
  public abstract process(htmlElement: HTMLElement): void;
}

export class TitleInjectionHelper extends AbstractInjectionHelper {
  constructor(private title: string) {
    super();
  }

  public process(htmlElement: HTMLElement): void {
    htmlElement.querySelector(&apos;title&apos;)!.textContent = this.title;
  }
}
</code></pre><h3 id="conclusion">Conclusion</h3><p><code>nestjs-ember-static</code> makes developing with EmberJS and NestJS easier by providing live reloading, config injection, and is perfect for mono repositories. Give it a try to streamline your development process!</p><p>Check it out on <a href="https://www.npmjs.com/package/nestjs-ember-static?ref=bushbaby.nl">npm</a>.</p>]]></content:encoded></item><item><title><![CDATA[Transitioning from Docker-Compose to Docker Swarm in Production]]></title><description><![CDATA[I transitioned from Docker-Compose to Docker Swarm for production deployments. Docker Swarm provides network isolation, scalability, load balancing, and rolling updates without downtime. It's built into Docker, making it a simpler, more reliable choice for managing services.]]></description><link>https://bushbaby.nl/docker-swarm-forwards/</link><guid isPermaLink="false">63396c2bbccf96000118771c</guid><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Sun, 23 Jun 2024 22:00:00 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2024/06/DALL-E-2024-06-24-11.57.33---A-visually-appealing-poster-showcasing-the-transition-from-Docker-Compose-to-Docker-Swarm-in-production.-The-background-should-feature-a-seamless-blen.webp" medium="image"/><content:encoded><![CDATA[<img src="https://bushbaby.nl/content/images/2024/06/DALL-E-2024-06-24-11.57.33---A-visually-appealing-poster-showcasing-the-transition-from-Docker-Compose-to-Docker-Swarm-in-production.-The-background-should-feature-a-seamless-blen.webp" alt="Transitioning from Docker-Compose to Docker Swarm in Production"><p>In the recent years, I made a significant shift in my deployment strategy by fully adopting Dockerized deployments. Previously, my deployments were automated using Ansible, which was effective and reliable. However, as my application evolved and became fully containerized, I transitioned to using <code>docker-compose up</code> for deployments, a method I was already accustomed to from my development workflow.</p><p>Using Docker Compose for deployments initially seemed like a natural progression. It allowed for straightforward service definitions and easy orchestration on a single host. However, as my application grew and I needed to deploy multiple versions&#x2014;such as production and staging environments&#x2014;on the same infrastructure, I encountered a significant limitation. Docker Compose does not provide isolated networks for each deployment, leading to port conflicts when running multiple instances of the same service stack. This required me to manage port configurations manually through environment variables (.env files), which became increasingly cumbersome and error-prone.</p><p>Recognizing the need for a more robust solution, I decided it was time to move my orchestration to Docker Swarm. Docker Swarm offers several advantages over Docker Compose, particularly in a production environment with multiple service instances. Here are some key benefits and considerations that drove this decision:</p><h2 id="benefits-of-docker-swarm">Benefits of Docker Swarm</h2><p>One of the main benefits of moving to Docker Swarm is its automatic handling of network isolation for different service stacks. This feature means that I can deploy multiple environments (production, staging, etc.) without worrying about port conflicts. Each stack operates within its own isolated network, simplifying configuration and reducing the risk of errors. This network isolation is, for me, the primary advantage and a significant improvement over Docker Compose.</p><p>Another key benefit is scalability. Swarm mode enables easy scaling of services. I can increase or decrease the number of replicas for each service with a single command, allowing my application to handle varying loads efficiently. This scalability is essential for maintaining performance during peak usage times.</p><p>Even for single node deployments, I have found Docker Swarm to be effective because of its other benefits. However, horizontally scaling can be achieved without any configuration changes just by adding some worker nodes. Note that I have found adding nodes for applications using volumes remains challenging, and I have not yet identified a good solution for deploying to Digital Ocean infrastructure, where ideally, you would want block storage mounts to follow containers.</p><p>Swarm includes an internal load balancer that distributes traffic evenly across service replicas. This ensures that no single instance becomes a bottleneck, improving the overall responsiveness and reliability of my application.</p><p>With Docker Swarm, I can perform rolling updates to my services without downtime. This allows me to deploy new versions of my application gradually, reducing the risk of introducing errors and ensuring a smooth transition for users.</p><p>Finally, since Docker Swarm is built into Docker, there is no need to install additional software. I already have Docker installed, which comes with Swarm, and I am familiar with the Docker CLI tools. This familiarity reduces the learning curve and makes the transition smoother.</p><h2 id="transitioning-to-docker-swarm">Transitioning to Docker Swarm</h2><p>The transition from Docker Compose to Docker Swarm involves several steps, but the process is straightforward:</p><h3 id="motivation-local-vs-remote-deployment">Motivation: Local vs. Remote Deployment</h3><p>Before diving into the steps, it&apos;s essential to understand the motivation behind transitioning to Docker Swarm, especially when managing local versus remote deployments. Managing deployments locally using Docker Compose is convenient and straightforward, but when it comes to deploying applications to remote servers, the complexity increases. Docker Swarm simplifies this process by allowing seamless management of multiple environments through Docker contexts. This means I can easily switch between local and remote deployments, ensuring that I am deploying to the correct environment without the risk of mistakes. Setting up Docker contexts helps streamline the workflow and provides a more structured approach to handling various deployment environments.</p><h3 id="1-set-up-docker-context">1. <strong>Set Up Docker Context</strong></h3><p>Before deploying, it&apos;s advantageous to set up Docker contexts. Docker contexts allow you to easily switch between different Docker environments (e.g., development, staging, production). This step simplifies managing multiple clusters and ensures you are deploying to the correct environment. To create and use Docker contexts, follow these steps:</p><pre><code class="language-bash">docker context create &lt;context_name&gt; --description &quot;&lt;description&gt;&quot; --docker &quot;host=ssh://user@hostname&quot;
docker context use &lt;context_name&gt;
</code></pre><p>After switching to a context, you gain the ability to execute the Docker CLI in a local directory as if it is on a remote server. This is really like magic and very useful.</p><h3 id="2-initialize-swarm-mode">2. <strong>Initialize Swarm Mode</strong></h3><p>On your manager node, initialize Swarm mode:</p><pre><code class="language-bash">docker swarm init
</code></pre><h3 id="3-create-a-docker-stack-file">3. <strong>Create a Docker Stack File</strong></h3><p>Convert your <code>docker-compose.yml</code> file to a Swarm stack file (e.g., <code>stack.yml</code>). The syntax is mostly the same, but with some additional Swarm-specific configurations, such as resource limits, deploy strategies, and scaling limits.</p><h3 id="4-deploy-the-stack">4. <strong>Deploy the Stack</strong></h3><p>Deploy your stack using the <code>docker stack deploy</code> command:</p><pre><code class="language-bash">docker stack deploy -c stack.yml &lt;stack_name&gt;
</code></pre><h3 id="5-manage-services">5. <strong>Manage Services</strong></h3><p>Use Swarm commands to manage your services, such as scaling and updating:</p><pre><code class="language-bash">docker service scale &lt;service_name&gt;=&lt;replica_count&gt;
docker service update --image &lt;new_image&gt; &lt;service_name&gt;
</code></pre><h2 id="conclusion">Conclusion</h2><p>Transitioning from Docker Compose to Docker Swarm for production deployments offers numerous benefits, including network isolation, scalability, single node effectiveness, load balancing, and seamless rolling updates. These features are essential for maintaining a robust and efficient production environment, especially as your application and its deployment needs grow. By leveraging Docker Swarm, I can simplify the deployment process, reduce configuration overhead, and enhance the reliability of my application services.</p><p>Embracing Docker Swarm has been a game-changer for my projects, and I highly recommend it for anyone facing similar challenges with Docker Compose in a production setting.</p><p><em>Footnote: While Docker Swarm has lost some traction compared to Kubernetes, it remains a simpler system to manage. Kubernetes has a steep learning curve and is often overkill for smaller projects. I follow the recommendation that for small clusters and teams, Docker Swarm is still a great and cost-effective solution.</em></p>]]></content:encoded></item><item><title><![CDATA[Ember Electron and Typescript]]></title><description><![CDATA[<p>I&apos;ve been working on modernizing a somewhat older application. It consists of a Electron application through the ember-electron addon for Ember.</p><p>My goal was to upgrade both the Ember as well as the Electron part to a typescripted project.</p><h3 id="prerequisites">Prerequisites</h3><pre><code class="language-bash">$ ember -v                                                  
ember-cli: 4.12.1
node: 18.</code></pre>]]></description><link>https://bushbaby.nl/ember-electron-and-typescript/</link><guid isPermaLink="false">64773bb665ebca0001de59af</guid><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Wed, 31 May 2023 12:39:23 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;ve been working on modernizing a somewhat older application. It consists of a Electron application through the ember-electron addon for Ember.</p><p>My goal was to upgrade both the Ember as well as the Electron part to a typescripted project.</p><h3 id="prerequisites">Prerequisites</h3><pre><code class="language-bash">$ ember -v                                                  
ember-cli: 4.12.1
node: 18.16.0
os: darwin arm64

$ yarn -v
1.22.18
</code></pre><h3 id="creating-a-typescript-emberjs-application-with-electron">Creating a typescript EmberJS application with Electron</h3><p>We will start by creating an Ember application setup as typescript.</p><pre><code class="language-bash">$ ember new --directory ember-app ember-electron-app --typescript --yarn --lang en
</code></pre><h3 id="add-electron-to-the-application">Add Electron to the application</h3><p>We then add ember-electron to the application. This &apos;converts&apos; our project into a projects that can run our EmberJS application as an Electron application.</p><pre><code class="language-bash">$ cd ember-app
$ ember install ember-electron

yarn: Installed ember-electron
installing ember-electron-postinstall

Run &apos;ember g ember-electron&apos; to complete ember-electron setup
</code></pre><p>As per instruction;</p><pre><code class="language-bash">$ ember g ember-electron

installing ember-electron
  create testem-electron.js
Updating config/environment.js
Creating electron-forge project at &apos;./electron-app&apos;
&#x2714; Locating custom template: &quot;ember-electron/forge/template&quot;
&#x2714; Initializing directory
&#x2714; Preparing template
&#x2714; Initializing template
&#x2714; Installing template dependencies

&#x2728;  Running &quot;lint:fix&quot; script...
</code></pre><p>Let&apos;s test the app as is</p><pre><code class="language-bash">$ ember electron
</code></pre><p>An error appears: Failed to install Devtron. But let&apos;s ignore that.</p><p>You should see &quot;Congratulations, you made it!&quot; in a native window.</p><h3 id="creating-a-typescript-electron-application">Creating a typescript Electron application</h3><p>On this <a href="https://www.electronforge.io/templates/typescript-+-webpack-template?ref=bushbaby.nl">page</a> I found instructions on how to create an Electron application configured as typescript. We will do that and use some of it&apos;s setup.</p><pre><code class="language-bash">$ cd ..
$ yarn create electron-app electron-app --template=webpack-typescript
</code></pre><p>Let&apos;s start that app.</p><pre><code class="language-bash">$ cd electron-app
$ yarn electron-forge start</code></pre><p>You should see a native window with &quot;Hello World! Welcome to your Electron application.&quot;</p><h3 id="integrate-the-electron-app-into-our-ember-electron">Integrate the Electron app into our ember-electron </h3><p>Inside our ember-app is a directory &apos;electron-app&apos; that has the capability to run the EmberJS application. However that is in plain javascript and not typescript. To convert it we need to copy and change some things;</p><h4 id="adding-some-files-from-the-electron-template">Adding some files from the electron template</h4><pre><code class="language-bash">cd ..
cp electron-app/*.ts ember-app/electron-app/
cp electron-app/src/* ember-app/electron-app/src/
cp electron-app/tsconfig.json ember-app/electron-app/
rm ember-app/electron-app/forge.config.js
rm electron-app/src/handle-file-urls.js
rm ember-app/electron-app/index.js
</code></pre><h4 id="entry-point">Entry point</h4><p>We also must to change the &apos;main&apos; entry point inside <code>ember-app/electron-app/package.json</code>.</p><p>change it from</p><pre><code class="language-json">&quot;main&quot;: &quot;src/index.js&quot;,
</code></pre><p>to</p><pre><code class="language-json">&quot;main&quot;: &quot;.webpack/main&quot;,
</code></pre><h4 id="dependencies-of-the-electron-app">Dependencies of the electron-app</h4><pre><code class="language-bash">$ cd ember-app/electron-app
$ yarn add -D @electron-forge/plugin-webpack @typescript-eslint/eslint-plugin @typescript-eslint/parser @vercel/webpack-asset-relocator-loader css-loader eslint eslint-plugin-import fork-ts-checker-webpack-plugin node-loader style-loader ts-loader ts-node typescript
</code></pre><p>At this point you should be able to successfully run the electron app from <em>within</em> the electron-app directory</p><pre><code class="language-bash">$ yarn electron-forge start
</code></pre><figure class="kg-card kg-image-card"><img src="https://bushbaby.nl/content/images/2023/05/image-2.png" class="kg-image" alt loading="lazy" width="1824" height="1424" srcset="https://bushbaby.nl/content/images/size/w600/2023/05/image-2.png 600w, https://bushbaby.nl/content/images/size/w1000/2023/05/image-2.png 1000w, https://bushbaby.nl/content/images/size/w1600/2023/05/image-2.png 1600w, https://bushbaby.nl/content/images/2023/05/image-2.png 1824w" sizes="(min-width: 720px) 720px"></figure><p>However running it through Ember will give you errors or a stall at &quot;Starting Electron...&quot;</p><pre><code class="language-bash">$ cd ..
$ ember electron</code></pre><h4 id="fixing-failed-to-load">Fixing &quot;failed to load&quot;</h4><p>When you start the app with ember electron now you might see an error</p><pre><code>Failed to load: /.../ember-app/electron-app/forge.config.ts
Cannot use import statement outside a module
</code></pre><p>Add <code>&quot;module&quot;: &quot;commonjs&quot;,</code> to the compilerOptions and <code>&quot;electron-app/**/*.ts&quot;</code> as additional include entry. My <code>ember-app/tsconfig.json</code> looks like;</p><pre><code class="language-json">{
  &quot;extends&quot;: &quot;@tsconfig/ember/tsconfig.json&quot;,
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;skipLibCheck&quot;: true,
    &quot;noEmitOnError&quot;: false,
    &quot;resolveJsonModule&quot;: true,

    // The combination of `baseUrl` with `paths` allows Ember&apos;s classic package
    // layout, which is not resolvable with the Node resolution algorithm, to
    // work with TypeScript.
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;ember-electron-app/tests/*&quot;: [&quot;tests/*&quot;],
      &quot;ember-electron-app/*&quot;: [&quot;app/*&quot;],
      &quot;*&quot;: [&quot;types/*&quot;]
    }
  },
  &quot;include&quot;: [&quot;app/**/*&quot;, &quot;tests/**/*&quot;, &quot;types/**/*&quot;, &quot;electron-app/**/*.ts&quot;]
}

</code></pre><h4 id="dependencies-of-the-ember-app">Dependencies of the ember-app</h4><p>One reason why you still get errors or stalls is because of the way ember-electron starts the electron application in our workspace. It does this via the exposed api of <a href="https://www.npmjs.com/package/@electron-forge/core?ref=bushbaby.nl">@electron-forge/core</a> package and not - as you might think - via a process such as <code>electron-forge start</code>. I believe that means it will also not see any installed dependencies that are defined inside <code>package.json</code> <em>inside</em> the electron-app directory.</p><p>I found installing the same dependencies again to our root directory solves these issues and the webpack stuff can be correctly loaded and executed. (Not sure if <em>all</em> these packages are required).</p><pre><code>cd ember-app
yarn add -D @electron-forge/plugin-webpack @typescript-eslint/eslint-plugin @typescript-eslint/parser @vercel/webpack-asset-relocator-loader css-loader eslint eslint-plugin-import fork-ts-checker-webpack-plugin node-loader style-loader ts-loader ts-node typescript
</code></pre><h4 id="path-resolutions">Path resolutions</h4><p>Running <code>ember electron</code> will now succesfully start the Electron application. However there is one more issue that pops up. You will see a (native) window with an error like so;</p><pre><code>Compiled with problems:

ERROR Module not found: Error: Can&apos;t resolve &apos;./src/renderer.ts&apos; in &apos;/.../ember-app&apos;&quot;
</code></pre><p>This can be easely fixed by using absolute paths inside <code>ember-app/electron-app/forge.config.ts</code></p><pre><code>entryPoints: [
	{
		html: __dirname + &apos;/src/index.html&apos;,
       js: __dirname + &apos;/src/renderer.ts&apos;,
       name: &apos;main_window&apos;,
       preload: {
	       js: __dirname + &apos;/src/preload.ts&apos;,
       },
	},
],
</code></pre><p><code>ember electron</code> now works and you should greeted with &quot;Hello World! Welcome to your Electron application.&quot;</p><h4 id="switch-to-loading-the-emberjs-application">Switch to loading the EmberJS application</h4><p>To get our Ember application to show up we must change the entry point again to the following. Remember ember-electron creates our application inside the <code>ember-app/electron-app/ember-dist</code> directory.</p><pre><code class="language-json">entryPoints: [
  {
    html: __dirname + &apos;/ember-dist/index.html&apos;,
    ...
  },
],</code></pre><h4 id="recreate-the-ember-electron-magic">Recreate the ember-electron magic.</h4><p>We also need to recreate the magic ember-electron does by adding these two files;</p><p><code>ember-app/electron-app/src/handle-file-urls.ts</code></p><pre><code class="language-ts">import { fileURLToPath } from &apos;url&apos;;
import path from &apos;path&apos;;
import fs from &apos;fs&apos;;
import { promisify } from &apos;util&apos;;

const access = promisify(fs.access);

//
// Patch asset loading -- Ember apps use absolute paths to reference their
// assets, e.g. `&lt;img src=&quot;/images/foo.jpg&quot;&gt;`. When the current URL is a `file:`
// URL, that ends up resolving to the absolute filesystem path `/images/foo.jpg`
// rather than being relative to the root of the Ember app. So, we intercept
// `file:` URL request and look to see if they point to an asset when
// interpreted as being relative to the root of the Ember app. If so, we return
// that path, and if not we leave them as-is, as their absolute path.
//
export async function getAssetPath(emberAppDir: string, url: string) {
  let urlPath = fileURLToPath(url);
  // Get the root of the path -- should be &apos;/&apos; on MacOS or something like
  // &apos;C:\&apos; on Windows
  let { root } = path.parse(urlPath);
  // Get the relative path from the root to the full path
  let relPath = path.relative(root, urlPath);
  // Join the relative path with the Ember app directory
  let appPath = path.join(emberAppDir, relPath);
  try {
    await access(appPath);
    return appPath;
  } catch (e) {
    return urlPath;
  }
}

export default function handleFileURLs(emberAppDir: string) {
  const { protocol } = require(&apos;electron&apos;);

  protocol.interceptFileProtocol(
    &apos;file&apos;,
    async ({ url }: { url: string }, callback: Function) =&gt; {
      callback(await getAssetPath(emberAppDir, url));
    }
  );
}
</code></pre><p>And a modified <code>ember-app/electron-app/src/index.ts</code> to mimic the original index.js</p><pre><code class="language-ts">/* eslint-disable no-console */

// This allows TypeScript to pick up the magic constants that&apos;s auto-generated by Forge&apos;s Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you&apos;re running in development or production).
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;

const {
  default: installExtension,
  EMBER_INSPECTOR,
} = require(&apos;electron-devtools-installer&apos;);
const { pathToFileURL } = require(&apos;url&apos;);
const { app, BrowserWindow } = require(&apos;electron&apos;);
import path from &apos;path&apos;;
import isDev from &apos;electron-is-dev&apos;;
import handleFileUrls from &apos;./handle-file-urls&apos;;

const emberAppDir = path.resolve(__dirname, &apos;../..&apos;, &apos;ember-dist&apos;);
const emberAppURL = pathToFileURL(
  path.join(emberAppDir, &apos;index.html&apos;)
).toString();

// Uncomment the lines below to enable Electron&apos;s crash reporter
// For more information, see http://electron.atom.io/docs/api/crash-reporter/
// electron.crashReporter.start({
//     productName: &apos;YourName&apos;,
//     companyName: &apos;YourCompany&apos;,
//     submitURL: &apos;https://your-domain.com/url-to-submit&apos;,
//     autoSubmit: true
// });

app.on(&apos;window-all-closed&apos;, () =&gt; {
  if (process.platform !== &apos;darwin&apos;) {
    app.quit();
  }
});

app.on(&apos;ready&apos;, async () =&gt; {
  if (isDev) {
    try {
      require(&apos;devtron&apos;).install();
    } catch (err) {
      console.log(&apos;Failed to install Devtron: &apos;, err);
    }
    try {
      await installExtension(EMBER_INSPECTOR, {
        loadExtensionOptions: { allowFileAccess: true },
      });
    } catch (err) {
      console.log(&apos;Failed to install Ember Inspector: &apos;, err);
    }
  }

  await handleFileUrls(emberAppDir);

  let mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
    },
  });

  // If you want to open up dev tools programmatically, call
  mainWindow.openDevTools();

  // Load the ember application
  mainWindow.loadURL(emberAppURL);

  // If a loading operation goes wrong, we&apos;ll send Electron back to
  // Ember App entry point
  mainWindow.webContents.on(&apos;did-fail-load&apos;, () =&gt; {
    mainWindow.loadURL(emberAppURL);
  });

  mainWindow.webContents.on(
    &apos;render-process-gone&apos;,
    (_event: any, details: any) =&gt; {
      if (details.reason === &apos;killed&apos; || details.reason === &apos;clean-exit&apos;) {
        return;
      }
      console.log(
        &apos;Your main window process has exited unexpectedly -- see https://www.electronjs.org/docs/api/web-contents#event-render-process-gone&apos;
      );
      console.log(&apos;Reason: &apos; + details.reason);
    }
  );

  mainWindow.on(&apos;unresponsive&apos;, () =&gt; {
    console.log(
      &apos;Your Ember app (or other code) has made the window unresponsive.&apos;
    );
  });

  mainWindow.on(&apos;responsive&apos;, () =&gt; {
    console.log(&apos;The main window has become responsive again.&apos;);
  });

  mainWindow.on(&apos;closed&apos;, () =&gt; {
    mainWindow = null;
  });
});

// Handle an unhandled error in the main thread
//
// Note that &apos;uncaughtException&apos; is a crude mechanism for exception handling intended to
// be used only as a last resort. The event should not be used as an equivalent to
// &quot;On Error Resume Next&quot;. Unhandled exceptions inherently mean that an application is in
// an undefined state. Attempting to resume application code without properly recovering
// from the exception can cause additional unforeseen and unpredictable issues.
//
// Attempting to resume normally after an uncaught exception can be similar to pulling out
// of the power cord when upgrading a computer -- nine out of ten times nothing happens -
// but the 10th time, the system becomes corrupted.
//
// The correct use of &apos;uncaughtException&apos; is to perform synchronous cleanup of allocated
// resources (e.g. file descriptors, handles, etc) before shutting down the process. It is
// not safe to resume normal operation after &apos;uncaughtException&apos;.
process.on(&apos;uncaughtException&apos;, (err) =&gt; {
  console.log(&apos;An exception in the main thread was not handled.&apos;);
  console.log(
    &apos;This is a serious issue that needs to be handled and/or debugged.&apos;
  );
  console.log(`Exception: ${err}`);
});
</code></pre><p>Now, when you run <code>ember electron</code> you should see the Ember welcome page inside a native window and everything is typescripted.</p><p>Enjoy!</p><figure class="kg-card kg-image-card"><img src="https://bushbaby.nl/content/images/2023/05/image.png" class="kg-image" alt loading="lazy" width="1824" height="1424" srcset="https://bushbaby.nl/content/images/size/w600/2023/05/image.png 600w, https://bushbaby.nl/content/images/size/w1000/2023/05/image.png 1000w, https://bushbaby.nl/content/images/size/w1600/2023/05/image.png 1600w, https://bushbaby.nl/content/images/2023/05/image.png 1824w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[(Untitled)]]></title><link>https://bushbaby.nl/untitled/</link><guid isPermaLink="false">6339726186af9d0001de6549</guid><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Sun, 02 Oct 2022 11:14:37 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2022/10/8d1b5c6d-2056-4ff0-bb2a-efe1b10e1999.jpg" medium="image"/><content:encoded/></item><item><title><![CDATA[Prooph Snapshot Serializer]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently version many of prooph&apos;s components were updated to a new mayor release. One of its components is the <a href="https://github.com/prooph/snapshot-store?ref=bushbaby.nl">prooph/snapshot-store</a> able to serialize, and persist aggregates so they do not have to be reconstituted from the event stream when they are needed again.</p>
<p>A missing feature was</p>]]></description><link>https://bushbaby.nl/snapshots-serializer/</link><guid isPermaLink="false">63396c2bbccf960001187719</guid><category><![CDATA[prooph]]></category><category><![CDATA[prooph-snapshots]]></category><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Fri, 07 Apr 2017 15:05:09 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2022/10/pexels-pixabay-302743.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://bushbaby.nl/content/images/2022/10/pexels-pixabay-302743.jpg" alt="Prooph Snapshot Serializer"><p>Recently version many of prooph&apos;s components were updated to a new mayor release. One of its components is the <a href="https://github.com/prooph/snapshot-store?ref=bushbaby.nl">prooph/snapshot-store</a> able to serialize, and persist aggregates so they do not have to be reconstituted from the event stream when they are needed again.</p>
<p>A missing feature was the ability to use a different serializer/deserializer. It turned out to be relatively simple to implement it in such a way with backward compatible. All you have to do to change the following;</p>
<p>Both the <a href="https://github.com/prooph/pdo-snapshot-store?ref=bushbaby.nl"><code>PdoSnapshotStore</code></a> and <a href="https://github.com/prooph/mongodb-snapshot-store?ref=bushbaby.nl"><code>MongoSnapshotStore</code></a> are now capable of consuming a Serializer which takes care of serialization/unserialization;</p>
<pre><code class="language-php">interface Serializer
{
    public function serialize($data): string;
    public function unserialize(string $data);
}
</code></pre>
<p>A default <code>Serializer</code> is implemented as <code>CallbackSerializer</code> which will use the build-on serialize/unserialize method of PHP. Simply instantiate it with different callbacks to change it&apos;s default behavior.</p>
<pre><code class="language-php">$connection = new PDO(...);
$tableMap = [];
$tableName = &apos;snapshots&apos;;
$serializer = new CallbackSerializer(&apos;igbinary_serialize&apos;, &apos;igbinary_unserialize&apos;);

$snapshotStore =  new PdoSnapshotStore(
    $connection,
    $tableMap,
    $tableName,
    $serializer
);
</code></pre>
<p>If you prefer to use a <code>psr/container</code> driven application you simply need to create a factory for the Serializer interface;</p>
<pre><code class="language-php">class SerializerFactory
{
    public function __invoke(ContainerInterface $container): Serializer
    {
        return new CallbackSerializer(function ($data) {
            return gzdeflate(serialize($data), 9);
        }, function ($data) {
            return unserialize(gzinflate($data));
        });
    }
}
</code></pre>
<p>the declare a dependency for the factory;</p>
<pre><code class="language-php">return [
    &apos;dependencies&apos; =&gt; [
        &apos;factories&apos; =&gt; [
            Serializer::class =&gt; SerializerFactory::class,
        ],
    ], 
</code></pre>
<p>and add a key to the snapshot configuration;</p>
<pre><code class="language-php">return [
    &apos;prooph&apos; =&gt; [
        &apos;pdo_snapshot_store&apos; =&gt; [
            &apos;default&apos; =&gt; [
                &apos;connection_service&apos; =&gt; &apos;pdo.connection&apos;,
                &apos;serializer&apos; =&gt; Serializer::class
            ],
        ],
</code></pre>
<p>Do not forget to clear existing snapshots and reset running snapshot projectors.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Solving websockets subscriptions and disconnects]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I finally came around to solve an annoying issue in an EmberJS application that uses websockets to connect to RabbitMQ messaging. The issue is with disconnects after a network error and existing subscription to the channels. I had solved the principal reconnect issue before in a similar fashion as described</p>]]></description><link>https://bushbaby.nl/solving-websockets-subscriptions-and-disconnects/</link><guid isPermaLink="false">63396c2bbccf960001187718</guid><category><![CDATA[websockets]]></category><category><![CDATA[emberjs]]></category><category><![CDATA[rabbitmq]]></category><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Wed, 08 Feb 2017 12:33:35 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2022/10/pexels-lil-artsy-5130296.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://bushbaby.nl/content/images/2022/10/pexels-lil-artsy-5130296.jpg" alt="Solving websockets subscriptions and disconnects"><p>I finally came around to solve an annoying issue in an EmberJS application that uses websockets to connect to RabbitMQ messaging. The issue is with disconnects after a network error and existing subscription to the channels. I had solved the principal reconnect issue before in a similar fashion as described <a href="https://github.com/jmesnil/stomp-websocket/issues/81?ref=bushbaby.nl">here</a>.</p>
<p>But as I also use channel subscriptions these active subscriptions should also be restored when the connection is restored...</p>
<p>The idea is that I track those channels subscription, so when an connection error happens I can simply add those a list of pendingSubscriptions.</p>
<pre><code class="language-js">import Ember from &quot;ember&quot;;
import ENV from &quot;../config/environment&quot;;
import webstomp from &quot;npm:webstomp-client&quot;;

/* global SockJS */

export default Ember.Service.extend({
  account: Ember.inject.service(),
  store: Ember.inject.service(),
  client: null,
  identity: Ember.computed.alias(&apos;account.identity&apos;),
  /**
   * Established subscriptions
   * {name: {connection: value, destination: destination} (on which a unsubscribe method is available)
   */
  establishedSubscriptions: {},
  /**
   * Subscriptions that have been requested but are not yet possible because a connection is available yet.
   * {name: destination}
   */
  pendingSubscriptions: {},

  /**
   * Start a connection
   */
  initialize: function () {
    this.connectAndReconnect(`https://${ENV.APP.mq.baseHost}/stomp`, () =&gt; {
      Ember.debug(&apos;Connected established...&apos;);
      this.subscribeToPendingSubscriptions();
    });

    this.requestSubscription(&apos;lab-plhw-identity/all&apos;, &quot;/exchange/lab-plhw-identity/all&quot;);
  },
  subscribeToPendingSubscriptions: function () {
    if (!this.client.connected) {
      setTimeout(() =&gt; {
        this.subscribeToPendingSubscriptions();
      }, 1500);

      return;
    }

    let subscriptionName;

    for (subscriptionName in this.pendingSubscriptions) {
      if (this.pendingSubscriptions.hasOwnProperty(subscriptionName)) {
        /**
         * First unsubscribe from destination
         */
        if (!Ember.isNone(this.establishedSubscriptions[subscriptionName])) {
          this.establishedSubscriptions[subscriptionName][&apos;connection&apos;].unsubscribe();
          // remove established
          delete this.establishedSubscriptions[subscriptionName];
        }
        /**
         * Subscribe to destination
         */
        let destination = this.pendingSubscriptions[subscriptionName];
        Ember.debug(&apos;Subscribing to: &apos; + destination);
        this.establishedSubscriptions[subscriptionName] = {
          connection: this.client.subscribe(destination, this.onMessage.bind(this)),
          destination: destination
        };

        // remove pending
        delete this.pendingSubscriptions[subscriptionName];
      }
    }
  },
  requestSubscription(subscriptionName, destination) {
    this.pendingSubscriptions[subscriptionName] = destination;

    Ember.run.scheduleOnce(&apos;afterRender&apos;, this, this.subscribeToPendingSubscriptions);
  },
  onUserIdChanged: Ember.observer(&apos;identity.id&apos;, function () {
    this.requestSubscription(&apos;lab-plhw-identity/identity&apos;, &quot;/exchange/lab-plhw-identity/&quot; + this.get(&apos;identity.id&apos;));
  }),

  /**
   * To enable debugging for SockJS execute this in the console and reload &quot;localStorage.debug = &apos;*&apos;;&quot;
   *
   * @param socketUrl
   * @param successCallback
   */
  connectAndReconnect: function (socketUrl, successCallback) {
    Ember.debug(&apos;Connecting to: &apos; + socketUrl);

    let sock = new SockJS(socketUrl/*, {transports: ???}*/);

    this.client = webstomp.over(sock, {
      binary: false,
      heartbeat: {outgoing: 0, incoming: 0},
      debug: ENV.environment !== &apos;production&apos;
    });

    this.client.connect(
      ENV.APP.mq.username,
      ENV.APP.mq.password,
      (frame) =&gt; {
        successCallback(frame);
      },
      (error) =&gt; {
        /**
         * Connection lost... Make sure that for existing subscriptions new subscriptions are requested
         */
        let subscriptionName;
        for (subscriptionName in this.establishedSubscriptions) {
          if (this.establishedSubscriptions.hasOwnProperty(subscriptionName)) {
            this.requestSubscription(subscriptionName, this.establishedSubscriptions[subscriptionName][&apos;destination&apos;]);

            delete this.establishedSubscriptions[subscriptionName];
          }
        }

        Ember.debug(&apos;Connection error: &apos; + error.reason);
        setTimeout(() =&gt; {
          this.connectAndReconnect(socketUrl, successCallback);
        }, 1500);
      },
      ENV.APP.mq.vhost
    );
  },
  onMessage: function (message) {
    let payload, store = this.get(&apos;store&apos;);

    switch (message.headers[&apos;content-type&apos;]) {
      case &apos;application/json&apos;:
        payload = JSON.parse(message.body);
        break;

      default: // assume &apos;text/plain&apos;
        payload = message.body;
    }

    Ember.debug(&apos;Recieved payload for: &apos; + message.headers[&apos;destination&apos;]);

    switch (message.headers[&apos;destination&apos;]) {
      case &apos;/exchange/lab-plhw-identity/&apos; + this.getWithDefault(&apos;identity.id&apos;, &apos;xxx&apos;):
      case &apos;/exchange/lab-plhw-identity/all&apos;:
        if (!Ember.isNone(payload.changeset)) {
          if (!Ember.isNone(payload.changeset[&apos;entity-insertions&apos;])) {
            Ember.A(payload.changeset[&apos;entity-insertions&apos;]).forEach(function (mutationPayload) {
              const mutationData = JSON.parse(mutationPayload);
              store.pushPayload(mutationData);
            });
          }
          if (!Ember.isNone(payload.changeset[&apos;entity-updates&apos;])) {
            Ember.A(payload.changeset[&apos;entity-updates&apos;]).forEach(function (mutationPayload) {
              const mutationData = JSON.parse(mutationPayload);
              store.pushPayload(mutationData);
            });
          }
          if (!Ember.isNone(payload.changeset[&apos;entity-deletions&apos;])) {
            Ember.A(payload.changeset[&apos;entity-deletions&apos;]).forEach(function (mutationPayload) {
              const mutationData = JSON.parse(mutationPayload);
              let record = store.peekRecord(mutationData.data.type, mutationData.data.id);
              if (!Ember.isNone(record)) {
                record.unloadRecord();
              }
            });
          }
        }
        break;
      default:
        Ember.debug(&apos;payload not for me&apos;);
    }
  }
});
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[EmberJS & SASS & Bootstrap]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Because I have figured this out more than once... I present my notes on how to setup an <strong>ember-cli</strong> project with <strong>sass</strong> css.</p>
<p>Additionally I will add the bootstrap framework and a pattern to be able to override it&apos;s .scss sources. I focus on the Bootstrap framework but</p>]]></description><link>https://bushbaby.nl/emberjs-sass-bootstrap/</link><guid isPermaLink="false">63396c2bbccf960001187716</guid><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Sun, 22 Jan 2017 21:45:45 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2022/10/pexels-achira-751373.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://bushbaby.nl/content/images/2022/10/pexels-achira-751373.jpg" alt="EmberJS &amp; SASS &amp; Bootstrap"><p>Because I have figured this out more than once... I present my notes on how to setup an <strong>ember-cli</strong> project with <strong>sass</strong> css.</p>
<p>Additionally I will add the bootstrap framework and a pattern to be able to override it&apos;s .scss sources. I focus on the Bootstrap framework but the above should work with any library that have sources in sass.</p>
<p>Finally I describe a tiny pattern I have found useful regarding directories and css.</p>
<p>First we&apos;ll init a ember project. You can do this with any existing ember project, but please note that ember-cli only allows to use one styling language.</p>
<pre><code class="language-bash">mkdir new-app
cd new-app
ember init
npm uninstall ember-welcome-page --save-dev
</code></pre>
<h3 id="sass">SASS</h3>
<p>It is not difficult to switch to SASS support to in ember project. First some dependencies.</p>
<pre><code class="language-bash">npm install broccoli-funnel --save-dev
npm install broccoli-merge-trees --save-dev
bower install bootstrap-sass --save
ember install ember-cli-sass
</code></pre>
<p>Then it is necessary must modify the <code>ember-cli-build.js</code> file to be able to generate css from .sass (or .scss) files.</p>
<p>I define the <code>includePaths</code> option of <code>ember-cli-sass</code> to <code>bower_components/bootstrap-sass/assets/stylesheets</code>. This simply makes it possible to do <code>@import &apos;bootstrap/*&apos;;</code> within our application styles file.</p>
<p>A &apos;Funnel&apos; is created to be able to use the bootstrap icon fonts.</p>
<pre><code class="language-js">/*jshint node:true*/
/* global require, module */
var EmberApp = require(&apos;ember-cli/lib/broccoli/ember-app&apos;);
var Funnel = require(&apos;broccoli-funnel&apos;);
var MergeTrees = require(&apos;broccoli-merge-trees&apos;);

module.exports = function (defaults) {
  var app = new EmberApp(defaults, {
    sassOptions: {
      includePaths: [
        &apos;bower_components/bootstrap-sass/assets/stylesheets&apos;
      ]
    }
  });

  // Use `app.import` to add additional libraries to the generated
  // output files.
  //
  // If you need to use different assets in different
  // environments, specify an object as the first parameter. That
  // object&apos;s keys should be the environment name and the values
  // should be the asset to use in that environment.
  //
  // If the library that you are including contains AMD or ES6
  // modules that you would like to import into your application
  // please specify an object with the list of modules as keys
  // along with the exports of each module as its value.

  // @see https://ember-cli.com/user-guide/#using-broccoli-funnel
  var bootstrapAssets = new Funnel(app.bowerDirectory + &apos;/bootstrap-sass/assets/fonts/bootstrap&apos;, {
    srcDir: &apos;/&apos;,
    include: [&apos;**/*.*&apos;],
    destDir: &apos;/fonts/bootstrap&apos;
  });

  return app.toTree(new MergeTrees([bootstrapAssets]));
};
</code></pre>
<p>Finally renaming <code>app/styles/app.css</code> to <code>app/styles/app.scss</code> enables us to do something like this <code>@import &quot;bootstrap/normalize&quot;;</code> and have the sass preprocessor find the correct file.</p>
<pre><code class="language-bash">mv app/styles/app.css app/styles/app.scss
</code></pre>
<h3 id="adirectorypatternforstyles">A directory pattern for styles</h3>
<p>Organizing style segments is kind of hard. I usually start out cleanly but things get muddier as I build my app. Here is a directory structure as a suggestion.</p>
<pre><code class="language-bash">mkdir app/styles/bootstrap
mkdir app/styles/bootstrap/overrides
mkdir app/styles/mixins 
mkdir app/styles/body-classes
</code></pre>
<p>And some files</p>
<pre><code class="language-bash">echo &apos;// application variables&apos; &gt; app/styles/variables.scss
echo &apos;@import &quot;variables&quot;;&apos; &gt; app/styles/app.scss
echo &apos;@import &quot;bootstrap/bootstrap&quot;;&apos; &gt;&gt; app/styles/app.scss
cp ./bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss app/styles/bootstrap/overrides/variables.scss
</code></pre>
<p>Add the following (as needed) to app/styles/app.scss</p>
<pre><code class="language-js">@import &quot;variables&quot;;
@import &quot;bootstrap/variables&quot;;
@import &quot;bootstrap/overrides/variables&quot;;

@import &quot;bootstrap/mixins&quot;;
// Reset and dependencies
@import &quot;bootstrap/normalize&quot;;
@import &quot;bootstrap/print&quot;;
@import &quot;bootstrap/glyphicons&quot;;

// Core CSS
@import &quot;bootstrap/scaffolding&quot;;
@import &quot;bootstrap/type&quot;;
@import &quot;bootstrap/code&quot;;
@import &quot;bootstrap/grid&quot;;
@import &quot;bootstrap/tables&quot;;
@import &quot;bootstrap/forms&quot;;
@import &quot;bootstrap/buttons&quot;;

// Components
@import &quot;bootstrap/component-animations&quot;;
@import &quot;bootstrap/dropdowns&quot;;
@import &quot;bootstrap/button-groups&quot;;
@import &quot;bootstrap/input-groups&quot;;
@import &quot;bootstrap/navs&quot;;
@import &quot;bootstrap/navbar&quot;;
@import &quot;bootstrap/breadcrumbs&quot;;
@import &quot;bootstrap/pagination&quot;;
@import &quot;bootstrap/pager&quot;;
@import &quot;bootstrap/labels&quot;;
@import &quot;bootstrap/badges&quot;;
@import &quot;bootstrap/jumbotron&quot;;
@import &quot;bootstrap/thumbnails&quot;;
@import &quot;bootstrap/alerts&quot;;
@import &quot;bootstrap/progress-bars&quot;;
@import &quot;bootstrap/media&quot;;
@import &quot;bootstrap/list-group&quot;;
@import &quot;bootstrap/panels&quot;;
@import &quot;bootstrap/responsive-embed&quot;;
@import &quot;bootstrap/wells&quot;;
@import &quot;bootstrap/close&quot;;

// Components w/ JavaScript
@import &quot;bootstrap/modals&quot;;
@import &quot;bootstrap/tooltip&quot;;
@import &quot;bootstrap/popovers&quot;;
@import &quot;bootstrap/carousel&quot;;

// Utility classes
@import &quot;bootstrap/utilities&quot;;
@import &quot;bootstrap/responsive-utilities&quot;;
@import &quot;bootstrap/theme&quot;;
</code></pre>
<p>Copy any bootstrap scss file so you can override properties and css definitions in each of them.</p>
<p>You&apos;ll need to import them from <code>app/styles/app.scss</code>. Beware of the order you import them.</p>
<h4 id="bodyclasses">Body classes</h4>
<p>A pattern that I found useful makes use of routes that upon activation add classes to the body tag.</p>
<pre><code class="language-bash">ember install ember-body-class
</code></pre>
<p>All routes have a classNames attribute of type Array. If you wanted to add a body class <code>page-special</code> to your route, it would look like this:</p>
<pre><code class="language-js">export default Ember.Route.extend({
  classNames: [&quot;page-common&quot;, &quot;page-special&quot;]
})
</code></pre>
<p>Then I create a file per use body class and import them in <code>app/styles/app.scss</code>.</p>
<pre><code class="language-css">// app/styles/pages/page-common.scss
body.page-common {
// a things are black
}
</code></pre>
<pre><code class="language-css">// app/styles/pages/page-special.scss
body.page-special {
// special things are red
}
</code></pre>
<h3 id="theemberwaytobootstrap">The Ember way to Bootstrap</h3>
<p>Since we&apos;ve now added bootstrap css compilation to our workflow it might be an idea to also add these ember-bootstrap integrations.</p>
<pre><code class="language-bash">ember install ember-bootstrap
ember install ember-bootstrap-cp-validations
</code></pre>
<p>To avoid adding ember-bootstrap compiled css modify <code>ember-cli-build.js</code> to instruct it not to include it.</p>
<pre><code class="language-js">&apos;ember-bootstrap&apos;: {
  &apos;importBootstrapCSS&apos;: false,
  &apos;importBootstrapTheme&apos;: false
},
</code></pre>
<h3 id="vendorcssprefixing">Vendor CSS Prefixing</h3>
<p><em>Completely free bonus step</em></p>
<p>Install the <a href="https://github.com/kimroen/ember-cli-autoprefixer?ref=bushbaby.nl">ember-cli-autoprefixer</a> addon so you can simply write the css you know and forget about vendor prefixing.</p>
<pre><code class="language-bash">ember install ember-cli-autoprefixer
</code></pre>
<p>Then add a list of browsers (per environment target) to your packages.json so the prefixer (and other npm modules) can do its magic.</p>
<p>See <a href="https://github.com/ai/browserslist?ref=bushbaby.nl">browserlist</a> for more information on this subject.</p>
<pre><code class="language-js">&quot;browserslist&quot;: {
  &quot;production&quot;: [
    &quot;last 2 version&quot;,
    &quot;ie 10&quot;
  ],
  &quot;testing&quot;: [
    &quot;last 2 versions&quot;
  ],
  &quot;development&quot;: [
    &quot;last 2 versions&quot;
  ]
}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How to specify custom message names on prooph messages?]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I&apos;ve been using FQCN as the names of command, queries and events in my prooph application. I felt it was time to change those long and ugly names to something more readable. In various places these names are used in configuration so I wanted to keep the ability</p>]]></description><link>https://bushbaby.nl/how-to-specify-message-name-on-a-command/</link><guid isPermaLink="false">63396c2bbccf960001187715</guid><category><![CDATA[prooph]]></category><category><![CDATA[zendframework]]></category><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Mon, 05 Sep 2016 14:39:11 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2016/09/dl-size-card-blanks-without-envelopes-pack-of-50-10457-p.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://bushbaby.nl/content/images/2016/09/dl-size-card-blanks-without-envelopes-pack-of-50-10457-p.jpg" alt="How to specify custom message names on prooph messages?"><p>I&apos;ve been using FQCN as the names of command, queries and events in my prooph application. I felt it was time to change those long and ugly names to something more readable. In various places these names are used in configuration so I wanted to keep the ability to easily refactor and jump into classes.</p>
<p>To change the name of messages we should do two things for different circumstances. For whenever we instantiate messages <em>ourselves</em> we must override the $messageName property in out Message.</p>
<h4 id="directinstantiationofmessages">direct instantiation of messages</h4>
<pre><code class="language-php">// done in some script, direct instantiation of messages
$this-&gt;commandBus-&gt;dispatch(new RegisterUser([
    &apos;user_id&apos;           =&gt; (string) UserId::generate(),
    &apos;display_name&apos;      =&gt; $dataToImport[&apos;display_name&apos;],
    &apos;email_address&apos;     =&gt; $dataToImport[&apos;email_address&apos;],
    &apos;password_hash&apos;     =&gt; $dataToImport[&apos;password_hash&apos;],
    &apos;registration_date&apos; =&gt; $dataToImport[&apos;registration_date&apos;],
    &apos;account_status&apos;    =&gt; $dataToImport[&apos;account_status&apos;],
    &apos;email_status&apos;      =&gt; $dataToImport[&apos;email_status&apos;],
]));
</code></pre>
<p>In my messages adding this <code>protected $messageName = &apos;identity.registerUser&apos;;</code> would be sufficient but I created a special CommandName class with only constants representing the messages names. My Command therefore looks like this;</p>
<pre><code class="language-php">final class RegisterUser extends Command implements PayloadConstructable
{
    use PayloadTrait;

    protected $messageName = CommandName::registerUser;

    /**
     * @return UserId
     */
    public function userId(): UserId
    ...
</code></pre>
<h4 id="instantiationofmessagesviafactory">instantiation of messages via factory</h4>
<p>For these situations we should create a new message factory that is capable of mapping these names to classes.<br>
I came up with this one where the following should be noted; It is almost a verbatim copy of the FQCNMessageFactory that ships with prooph and that it is still capable of creating messages by FQCN. This is important because you might not have control over every message. (eg the Prooph\Snapshotter\TakeSnapshot Command)</p>
<pre><code class="language-php">use My\Domain\Identity;

class CustomMessageFactory implements MessageFactory
{
    protected $messageMap = [
        Identity\CommandName::registerUser =&gt; Identity\Command\RegisterUser::class,
        ...
    ];

    /**
     * @param string $messageName
     * @param array $messageData
     * @throws \UnexpectedValueException
     * @return CustomMessageFactory
     */
    public function createMessageFromArray($messageName, array $messageData)
    {
        if (isset($this-&gt;messageMap[$messageName])) {
            $messageClass = $this-&gt;messageMap[$messageName];
        } else {
            $messageClass = $messageName;
        }

        if (! class_exists($messageClass)) {
            throw new \UnexpectedValueException(&apos;Given message name is not a valid class: &apos; . (string) $messageClass);
        }

        if (!is_subclass_of($messageClass, DomainMessage::class)) {
            throw new \UnexpectedValueException(sprintf(
                &apos;Message class %s is not a sub class of %s&apos;,
                $messageClass,
                DomainMessage::class
            ));
        }

        if (!isset($messageData[&apos;message_name&apos;])) {
            $messageData[&apos;message_name&apos;] = $messageName;
        }

        if (!isset($messageData[&apos;uuid&apos;])) {
            $messageData[&apos;uuid&apos;] = Uuid::uuid4();
        }

        if (!isset($messageData[&apos;version&apos;])) {
            $messageData[&apos;version&apos;] = 0;
        }

        if (!isset($messageData[&apos;created_at&apos;])) {
            $time = (string) microtime(true);
            if (false === strpos($time, &apos;.&apos;)) {
                $time .= &apos;.0000&apos;;
            }
            $messageData[&apos;created_at&apos;] = \DateTimeImmutable::createFromFormat(&apos;U.u&apos;, $time);
        }

        if (!isset($messageData[&apos;metadata&apos;])) {
            $messageData[&apos;metadata&apos;] = [];
        }

        return $messageClass::fromArray($messageData);
    }
</code></pre>
<h4 id="enablefactory">enable factory</h4>
<p>To enable the factory in the prooph middleware we change the appropriate (don&apos;t need to do them all at once) <code>message_factory</code> to our newly created factory.</p>
<pre><code class="language-php">return [
    &apos;prooph&apos; =&gt; [
        &apos;middleware&apos;     =&gt; [
            &apos;query&apos;   =&gt; [
                &apos;message_factory&apos;   =&gt; \My\Messaging\CustomMessageFactory::class,
                ...
            ],
            &apos;command&apos; =&gt; [
                &apos;message_factory&apos;   =&gt; \My\Messaging\CustomMessageFactory::class,
                ...
            ],
            &apos;event&apos;   =&gt; [
                &apos;message_factory&apos;   =&gt; \My\Messaging\CustomMessageFactory::class,
                ...
            ],
            &apos;message&apos; =&gt; [
                &apos;message_factory&apos;   =&gt; \My\Messaging\CustomMessageFactory::class,
                ...
            ],
        ],
</code></pre>
<p>And finally we can change every occurrence in the application configuration from the <code>My\Domain\Command\RegisterUser::class</code> name to <code>My\Domain\Identity\CommandName::registerUser</code>.</p>
<p>I find this useful because it keeps the configuration files readable. Especially since there are many locations where message names are used in configuration, in routing, busses, rbac in-memory configuration, etc...</p>
<p>Additionally, recorded events names are now independent of the actual used class.</p>
<p>Update: I moved the $messageMap property from the <code>CustomMessageFactory</code> to the various <code>CommandName</code> classes (per bounded context) because now I can change commands in one location.</p>
<pre><code class="language-php">class CustomMessageFactory implements MessageFactory
{
    /**
     * @var array maps message names to FQCN per bounded context domain
     */
    protected $messageMap = [
        &apos;identity&apos;    =&gt; Identity\CommandName::MESSAGE_MAP,
        ...
    ];

    public function createMessageFromArray($messageName, array $messageData)
    {
        $domain = explode(&apos;.&apos;, &apos;$messageName&apos;, 2)[0];

        if (isset($this-&gt;messageMap[$domain][$messageName])) {
            $messageClass = $this-&gt;messageMap[$domain][$messageName];
        } else {
            $messageClass = $messageName;
        }
        ...

</code></pre>
<pre><code class="language-php">final class CommandName
{
    const MESSAGE_MAP = [
        CommandName::registerUser =&gt; Command\RegisterUser::class,
    ];

    const registerUser = &apos;identity.registerUser&apos;;
    ....
}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Logging with Zend Framework and PSR-3]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently I encountered a library that requires its logger dependencies to implement the <a href="https://packagist.org/packages/psr/log?ref=bushbaby.nl">psr-3</a> standard. A common interface for logging libraries. I wanted to use the zend loggers because they are easy to configure and already used in my application.</p>
<p><strong>General configuration</strong></p>
<p>Zend has a LoggerAbstractServiceFactory which is capable of</p>]]></description><link>https://bushbaby.nl/logging-with-zendframework-and-psr3/</link><guid isPermaLink="false">63396c2bbccf960001187713</guid><category><![CDATA[zendframework]]></category><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Thu, 07 Jul 2016 14:13:00 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2016/07/IMG_7974.JPG" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://bushbaby.nl/content/images/2016/07/IMG_7974.JPG" alt="Logging with Zend Framework and PSR-3"><p>Recently I encountered a library that requires its logger dependencies to implement the <a href="https://packagist.org/packages/psr/log?ref=bushbaby.nl">psr-3</a> standard. A common interface for logging libraries. I wanted to use the zend loggers because they are easy to configure and already used in my application.</p>
<p><strong>General configuration</strong></p>
<p>Zend has a LoggerAbstractServiceFactory which is capable of instanciating multiple loggers configured via configuration files. Simply registering the LoggerAbstractServiceFactory  as abstract factory dependency enables it.</p>
<pre><code class="language-php">// config/autoload/logging.global.php
return [
    &apos;dependencies&apos; =&gt; [
        &apos;abstract_factories&apos; =&gt; [
            \Zend\Log\LoggerAbstractServiceFactory::class,
        ],
    ],
];
</code></pre>
<p>Next we can add a top level <code>log</code> key to the configuration which the LoggerAbstractServiceFactory will reference to create and configure zend loggers.</p>
<pre><code class="language-php">// config/autoload/logging.global.php
return [
  &apos;log&apos; =&gt; [
    &apos;StreamLogger&apos; =&gt; [
      &apos;writers&apos; =&gt; [
        [
          &apos;name&apos;     =&gt; &apos;stream&apos;,
          &apos;priority&apos; =&gt; Zend\Log\Logger::DEBUG,
          &apos;options&apos;  =&gt; [
            &apos;stream&apos; =&gt; &apos;php://output&apos;,
          ],
        ],
      ],
    ],
   &apos;dependencies&apos; =&gt; ...
];
</code></pre>
<p>At this point you would think I could configure said library to pull this logger from the ServiceManager. Simply give it the service name &apos;StreamLogger&apos;. However the zend loggers do not implement psr-3 and I would get an error due to incompatible dependencies.</p>
<p>zend-log comes with <code>Zend\Log\PsrLoggerAdapter</code> that is capable of wrapping a zend logger instance and is itself psr-3 compatible.</p>
<p>So I figured I could add a second service called <code>PsrStreamLogger</code> that would do just that and configure said library to use that service name instead.</p>
<p><strong>PsrLoggerAdapter configuration</strong></p>
<pre><code class="language-php">// config/autoload/logging.global.php
return [
    &apos;dependencies&apos; =&gt; [
        &apos;abstract_factories&apos; =&gt; [
            \Zend\Log\LoggerAbstractServiceFactory::class,
        ],
        &apos;factories&apos;          =&gt; [
            &apos;PsrStreamLogger&apos; =&gt; function($container) {
                return new \Zend\Log\PsrLoggerAdapter($container-&gt;get(&apos;StreamLogger&apos;));
            }
        ],
    ],

    &apos;log&apos; =&gt; ...
];
</code></pre>
<p>Although this will work another approach is to use a delegator factory. This is a feature of the zend-servicemanager that enables it to decorate services with a different service via a factory. Here&apos;s a good <a href="http://ocramius.github.io/blog/zend-framework-2-delegator-factories-explained?ref=bushbaby.nl">article</a> about it.</p>
<p>This is how you need to configure delegators;</p>
<pre><code class="language-php">// config/autoload/logging.global.php
return [
    &apos;dependencies&apos; =&gt; [
        &apos;abstract_factories&apos; =&gt; [...
        &apos;delegators&apos;         =&gt; [
            &apos;StreamLogger&apos; =&gt; [
                \My\Service\Log\PsrLoggerDelegator::class,
            ],
        ]
    ],

    &apos;log&apos; =&gt; [...
];
</code></pre>
<p>And here is my implementation of <code>\My\Service\Log\PsrLoggerDelegator</code> written in such a way it is backwards compatible with zend-servicemanager 2.</p>
<pre><code class="language-php">namespace My\Service\Service\Log;

use Interop\Container\ContainerInterface;
use Zend\Log\PsrLoggerAdapter;
use Zend\ServiceManager\DelegatorFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class PsrLoggerDelegator implements DelegatorFactoryInterface
{
    /**
     * @param ContainerInterface $container
     * @param                    $requestedName
     * @param callable           $callback
     * @param array|null         $options
     * @return PsrLoggerAdapter
     */
    public function __invoke(ContainerInterface $container, $requestedName, callable $callback, array $options = null)
    {
        return new PsrLoggerAdapter($callback());
    }

    /**
     * SM2 Compatibility
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param string                  $name
     * @param string                  $requestedName
     * @param callable                $callback
     * @return mixed
     */
    public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
    {
        return $this($serviceLocator, $requestedName, $callback);
    }
}
</code></pre>
<p>This approach allows you to continue using the log name you&apos;ve configured, and just ensures that it is decorated as a PSR-3-compliant logger.</p>
<p>Final note: As you now you will get a zend-log Logger decorated with the PsrLoggerDelegator whenever you pull <code>StreamLogger</code> existing services that currently require <code>Zend\Log\LoggerInterface</code> might now be problematic. Since psr-3 is supposed to improve interoperability you are encouraged to change its dependencies to the psr-3 interfaces.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Profile]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>How do you write your own profile. Not sure, so I&apos;ll just start...</p>
<p>I am a loone wolf working as developer slash designer. Although I have a creative education as Interaction Designer and am still doing some design occasionally (user experience these days) I prefer to call myself</p>]]></description><link>https://bushbaby.nl/profile-2/</link><guid isPermaLink="false">63396c2bbccf960001187710</guid><category><![CDATA[restart]]></category><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Tue, 28 Jun 2016 10:05:25 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2016/06/6691076519_d1bc02cf05_o.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://bushbaby.nl/content/images/2016/06/6691076519_d1bc02cf05_o.jpg" alt="Profile"><p>How do you write your own profile. Not sure, so I&apos;ll just start...</p>
<p>I am a loone wolf working as developer slash designer. Although I have a creative education as Interaction Designer and am still doing some design occasionally (user experience these days) I prefer to call myself a developer these days. It&apos;s where I feel most comfortable.</p>
<div style="max-width: 300px;"><img src="https://bushbaby.nl/content/images/2016/06/BasProfile-50%25.jpg" style="float: left;width: 100%; height: auto; padding-right:.5em;padding-bottom:0.5em;" alt="Profile"></div>My first freelancing project was &apos;de Jazzserver&apos;. A self publishing platform containing a comprehensive overview of anything Jazz in the Netherlands.
<p>When Flash was still a thing, I liked to work with that. Thought that Flex was awesome, don&apos;t know how I could think that. Briefly had a startup called zillizi which created components for Flash. I did that with this <a href="http://frankofficier.nl/?ref=bushbaby.nl">guy</a>.</p>
<p>Before that I made CD-Roms with Director but I won&apos;t try to explain what those are... :-)</p>
<p>These days I have a loose collaboration with these <a href="http://www.tranquilo.nl/wie-we-zijn/?ref=bushbaby.nl">people</a>.</p>
<h5 id="opensource">Open Source</h5>
<p>I like open source software. It has been a very beneficial to me in an educational sense. The first contributions I did were to a PHP framework called Binarycloud. It embraced OO when it became a thing in PHP and had great architecture. There are still some leftover references <a href="http://binarycloud.com/?ref=bushbaby.nl">here</a> and <a href="https://www.phing.info/docs/guide/trunk/ch02s02.html?ref=bushbaby.nl">there</a>.</p>
<p>Later I focussed on using Zend Framework and contributed a bit to Zend Framework 2.</p>
<p>Here are a few libraries I maintain and/or contribute to;</p>
<ul>
<li><a href="https://github.com/bushbaby/BsbFlysystem?ref=bushbaby.nl">BsbFlysystem</a></li>
<li><a href="https://github.com/juriansluiman/SlmQueue?ref=bushbaby.nl">SlmQueue, SlmQueueDoctrine</a></li>
<li><a href="https://github.com/bushbaby/BsbPostponableJobStrategy?ref=bushbaby.nl">BsbPostponableJobStrategy</a></li>
<li><a href="https://github.com/bushbaby/BsbDoctrineReconnect?ref=bushbaby.nl">BsbDoctrineReconnect</a></li>
<li><a href="https://github.com/bushbaby/BsbDoctrineReconnect?ref=bushbaby.nl">BsbDoctrineReconnect</a></li>
</ul>
<h5 id="currenttopicofinterests">Current Topic of interests</h5>
<p>Domain Driven Design, CQRS and Event Sourcing. PHP and EmberJS</p>
<h5 id="projectandclients">Project and Clients</h5>
<p>I&apos;ve worked on many projects and clients in the past, here are a few old and new...</p>
<ul>
<li><a href="http://sandalinos.nl/?ref=bushbaby.nl">Sandalinos (dealer portal)</a></li>
<li><a href="http://360player.nl/gbf?ref=bushbaby.nl">Lumasol (360 player)</a> / <a href="http://store.lumasol.nl/?ref=bushbaby.nl">Lumasol (store)</a></li>
<li><a href="http://juffrouwjansen.com/?ref=bushbaby.nl">JuffrouwJansen (website)</a></li>
<li><a href="http://roetz-bikes.com/?ref=bushbaby.nl">Roetz Bikes (commandline tool complementing their ODOO website)</a></li>
<li><a href="http://gids.nbf.nl/?ref=bushbaby.nl">Nederlandse Beroepsvereniging van Film en Televisiemakers (iOS/Android App + backend)</a></li>
<li><a href="http://www.kmc-idocu.nl/?ref=bushbaby.nl">Podium voor Architectuur (project website)</a></li>
<li><a href="http://www.manu-manu.nl/?ref=bushbaby.nl">ManuManu (website)</a></li>
<li><a href="http://www.wkams.com/?ref=bushbaby.nl">Wieden+Kennedy (website &amp; internal media repository)</a></li>
<li><a href="http://torenkraanregistratiesysteem.nl/?ref=bushbaby.nl">Lammers BV (torenkraan registratie systeem)</a></li>
<li><a href="http://www.carhartt.de/?ref=bushbaby.nl">Carhartt (website)</a></li>
<li><a href="http://www.jazzserver.nl/?ref=bushbaby.nl">de Jazzserver (website)</a></li>
<li><a href="http://www.dekamervraag.nl/?ref=bushbaby.nl">de Kamervraag (website)</a></li>
</ul>
<h5 id="social">Social</h5>
<p><a href="https://twitter.com/baskamer?ref=bushbaby.nl">Twitter</a> <a href="https://github.com/basz?ref=bushbaby.nl">Github (basz)</a> <a href="https://github.com/bushbaby?ref=bushbaby.nl">Github (bushbaby)</a> <a href="https://linkedin.com/in/baskamer?ref=bushbaby.nl">LinkedIn</a><br>
<a href="http://stackoverflow.com/users/364537/bas?ref=bushbaby.nl">Stack Overflow</a><br>
<a href="https://coderwall.com/basz?ref=bushbaby.nl">CoderWall</a></p>
<p><small style="float:right;"><a href="https://flic.kr/p/bcgvWg?ref=bushbaby.nl">image attribution</a></small></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Intro]]></title><description><![CDATA[<p>As a freelance developer I work solitair a lot. I like it that way and working like that fits my personality. I probably need it.</p><p>However, it can be a bit <a href="https://en.wikipedia.org/wiki/Sans_Famille?ref=bushbaby.nl">Remi</a> like at times. Therefore I decided I need to get <em>out</em> more and look for like minded people.</p>]]></description><link>https://bushbaby.nl/intro/</link><guid isPermaLink="false">633595f024fa2a0001f82727</guid><category><![CDATA[News]]></category><category><![CDATA[New Beginnings]]></category><dc:creator><![CDATA[Bas Kamer]]></dc:creator><pubDate>Sat, 18 Jun 2016 12:56:00 GMT</pubDate><media:content url="https://bushbaby.nl/content/images/2022/09/3534869625_8315dfeab3_o.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://bushbaby.nl/content/images/2022/09/3534869625_8315dfeab3_o.jpg" alt="Intro"><p>As a freelance developer I work solitair a lot. I like it that way and working like that fits my personality. I probably need it.</p><p>However, it can be a bit <a href="https://en.wikipedia.org/wiki/Sans_Famille?ref=bushbaby.nl">Remi</a> like at times. Therefore I decided I need to get <em>out</em> more and look for like minded people. I finally started to go to meetups, call those former colleagues and now I decided to restart blogging a bit.</p><p>Additionally I need to keep learning. I leech a lot and this might be my way of giving back. Hope it is helpful to someone...</p><p>Don&apos;t expect deep philosophical insights, those I won&apos;t share ;-) , but my intend is to write some random stuff about encountered development related issues. Sort of &apos;How did I do that again?&apos; type of articles. As my current interests are lying with anything PHP and EmberJS chances are that&apos;s were I will go subject wise.</p><p>So, I hope you like it, thanks for listening. Or not, that&apos;s fine too!</p><p><a href="https://flic.kr/p/6on8A2?ref=bushbaby.nl">image attribution</a></p>]]></content:encoded></item></channel></rss>