<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anna R Dunster</title>
    <description>The latest articles on DEV Community by Anna R Dunster (@ardunster).</description>
    <link>https://dev.to/ardunster</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F340145%2F9acf4119-b5f3-423f-897b-cdef1f2c72c0.jpg</url>
      <title>DEV Community: Anna R Dunster</title>
      <link>https://dev.to/ardunster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ardunster"/>
    <language>en</language>
    <item>
      <title>Deploying a Next.js Website to a Virtual Private Server</title>
      <dc:creator>Anna R Dunster</dc:creator>
      <pubDate>Mon, 25 Sep 2023 01:40:37 +0000</pubDate>
      <link>https://dev.to/ardunster/deploying-a-nextjs-website-to-a-virtual-private-server-gpm</link>
      <guid>https://dev.to/ardunster/deploying-a-nextjs-website-to-a-virtual-private-server-gpm</guid>
      <description>&lt;p&gt;Originally published &lt;a href="https://www.annardunster.com/posts/2023/deploying-nextjs-application" rel="noopener noreferrer"&gt;September 14, 2023 on my personal site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I initially began writing this article, after spending a week or so working on the functionality and writing for my renovated Next.js website, I assumed I would be able to deploy it to my existing hosting provider, Bluehost. Bluehost is a web hosting service that is primarily focused on supporting WordPress sites, and as such the tools and processes for deploying other types of applications are obscure and often poorly documented. After several hours of digging, a conversation with their tech support, and a trial run of using the &lt;code&gt;output: 'export'&lt;/code&gt; option in Next.js, I concluded that either I'd have to live with substandard features, quadruple my monthly cost with Bluehost, or select a new service provider. I opted to select a new service provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Word About Digital Ocean
&lt;/h2&gt;

&lt;p&gt;My initial research lead me to Digital Ocean, which seemed like a good choice throughout the initial deployment to the VPS. Everything was reasonably smooth and straightforward, and easy to provision. However, when I then moved to the process of preparing to change the DNS over for my domain name, and investigating what I would need to do to swap my email over, I came across some very concerning information that lead me to rethink this decision. Unfortunately, &lt;a href="https://www.reddit.com/r/webdev/comments/n3wf0c/heads_up_if_youre_on_digitalocean_almost_their/" rel="noopener noreferrer"&gt;entire blocks&lt;/a&gt; of Digital Ocean's available IP ranges are &lt;a href="https://www.digitalocean.com/community/questions/spamhaus-blacklisting" rel="noopener noreferrer"&gt;blacklisted by multiple spam prevention lists&lt;/a&gt; due to the convenience of &lt;a href="https://discourse.mailinabox.email/t/digital-ocean-ips-being-blacklisted-by-more-and-more-esps/8502" rel="noopener noreferrer"&gt;setting up a mail server&lt;/a&gt; on their VPSs. (If you're curious about your VPS's specific IP, you can check sites such as &lt;a href="https://www.uceprotect.net/" rel="noopener noreferrer"&gt;UCEPROTECT&lt;/a&gt;, who runs one of the biggest blacklists that gets used by many organizations, or &lt;a href="https://multirbl.valli.org/" rel="noopener noreferrer"&gt;multirbl&lt;/a&gt; which checks the IP against a broad array of blacklists to see if it is listed.) From &lt;a href="https://news.ycombinator.com/item?id=26064722" rel="noopener noreferrer"&gt;my reading&lt;/a&gt;, it seems that even if you do not serve email through their IP addresses, simply associating your domain with Digital Ocean can cause your domain to get blacklisted from some providers. This doesn't seem worth the risk to me, and for this reason I &lt;strong&gt;&lt;em&gt;do not&lt;/em&gt;&lt;/strong&gt; recommend using Digital Ocean as your VPS provider. However, I am leaving the tutorial available as it should apply equally well to any VPS hosting provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up The Virtual Private Server
&lt;/h2&gt;

&lt;p&gt;When you first set up an account with a VPS provider, they should walk you through setting up your first virtual private server. For this tutorial, we'll be using a minimal VPS with largely default features.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPS Creation Steps
&lt;/h3&gt;

&lt;p&gt;These steps and screenshots demonstrate the process via Digital Ocean. Other providers will have similar options depending on the specifics of their service.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Select your region and datacenter.&lt;/strong&gt; Here I have selected San Fransisco and left the datacenter dropdown on default.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8chv05zs6t3e1p1aeatz.png" title="Screenshot of DigitalOcean dashboard showing region and datacenter selection." alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select an image for the VPS.&lt;/strong&gt; For easy deployment, we will want one that already comes with Node.js installed on it. This is as easy as searching for Node.js in the "Marketplace" tab of the image selection section.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmeumqzq1toximrympxov.png" title="Screenshot of DigitalOcean dashboard showing image marketplace search." alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select the size you would like for the VPS.&lt;/strong&gt; Here we are just using the default smallest size, which comes with the Basic Plan's Shared CPU, 10GB of SSD storage, 512MB of RAM, and a total transfer bandwidth of 500GB. If you know you have higher requirements, feel free to adjust the details here.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk4k3tiu2agultt96br7.png" title="Screenshot of DigitalOcean dashboard showing VPS size selection." alt="Image description"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose an authentication method.&lt;/strong&gt; Using an SSH key is strongly suggested. The rest of this tutorial assumes you have set up SSH rather than opting to use a password based login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select any additional services such as metrics and backup that you want on your server.&lt;/strong&gt; These are entirely up to your needs on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finalize details.&lt;/strong&gt; Here you can adjust how many instances of the VPS you want to deploy, and give your VPS a name and tags. You can also select which project to add it to if you have more than one project set up.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Deploying The Site On The VPS
&lt;/h3&gt;

&lt;p&gt;There are a few things we will want to take care of here. First, we need to get the code onto the VPS, all the &lt;code&gt;npm&lt;/code&gt; dependencies installed, and the website built. Then, we will need to swap the configuration of &lt;code&gt;pm2&lt;/code&gt; (which comes pre-installed) to run the Next.js server instead of its default Node server. We will run these commands in the terminal via &lt;code&gt;ssh&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;ssh&lt;/code&gt; into your virtual machine using the root account.
&lt;/h4&gt;

&lt;p&gt;From the terminal, run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

ssh root@your.ip.here


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or, to select a specific SSH private key file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/custom_rsa root@your.ip.here


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On successful login, your terminal will update to show &lt;code&gt;root@name-of-vps&lt;/code&gt; as the user, and display some welcome messaging from the operating system (including active IP addresses) and possibly your hosting provider. The first thing you will want to do is apply any updates, especially security updates, by running &lt;code&gt;apt update&lt;/code&gt; followed by &lt;code&gt;apt upgrade&lt;/code&gt; (You may see an alert about how many packages are available to update). Your provider may mirror the packages, which makes this process faster, but it still may take a few minutes depending on how many packages need to be installed or upgraded.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pick a location to clone your Next.js app into.
&lt;/h4&gt;

&lt;p&gt;This is entirely up to you, but for a bit of guidance, the &lt;a href="https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard" rel="noopener noreferrer"&gt;Filesystem Hierarchy Standard&lt;/a&gt; specifies &lt;code&gt;/srv&lt;/code&gt; as the location for "Site-specific data served by this system, such as data and scripts for web servers, data offered by FTP servers, and repositories for version control systems (appeared in FHS-2.3 in 2004)." Another common location for content that is served to the web is &lt;code&gt;/var/www/&lt;/code&gt;. However, neither are strictly required by the NGINX setup this image comes with.&lt;/p&gt;

&lt;p&gt;One word of caution: &lt;a href="https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#not-using-standard-document-root-locations" rel="noopener noreferrer"&gt;never use &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;/root&lt;/code&gt; for this&lt;/a&gt;. You don't want your web server files to live in the same place as your user logins and other sensitive data.&lt;/p&gt;

&lt;h4&gt;
  
  
  Clone your repository.
&lt;/h4&gt;

&lt;p&gt;If the repository is private, you need to set up access to clone the repository into this VPS. Assuming the repository is hosted on GitHub, creating a &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token" rel="noopener noreferrer"&gt;personal access token&lt;/a&gt; and cloning via HTTPS is one way to get the required access without setting up personal SSH keys on the VPS. Using a fine-grained access token even allows you to restrict the token specifically to the repository you are working with. Remember to select the "Contents" permission from the list of possible permissions for this repository if you go this route.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install dependencies and build the project.
&lt;/h4&gt;

&lt;p&gt;Change directory into the newly cloned repository, then run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm ci


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Followed by:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm run build


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Install may take a few minutes, and due to the low CPU and memory available on the low cost VPS, &lt;code&gt;build&lt;/code&gt; also takes a few minutes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Turn off the &lt;code&gt;hello&lt;/code&gt; app.
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE:&lt;br&gt;
  Make sure you have the right user when working with &lt;code&gt;pm2&lt;/code&gt;. It's easy to miss an important piece of information here: by default, the &lt;code&gt;pm2&lt;/code&gt; process belongs to the &lt;code&gt;nodejs&lt;/code&gt; user. If you run &lt;code&gt;pm2&lt;/code&gt; again as root, you'll end up with two instances running. Be sure to either log in as the &lt;code&gt;nodejs&lt;/code&gt; user (instructions to find the login data are in the welcome message), or use &lt;code&gt;sudo -u nodejs&lt;/code&gt; before any &lt;code&gt;pm2&lt;/code&gt; commands to avoid this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you don't do this before you start your Next.js server, Next.js will attempt to start on the same port and quit with an error rather than starting successfully. Alternatively, if you want to keep &lt;code&gt;hello&lt;/code&gt; or another app running on port 3000, you can start your Next.js server in the next step by adding a specific port, instead. If you're logged in as the root user, use:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; nodejs pm2 delete hello


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Run the server.
&lt;/h4&gt;

&lt;p&gt;In order to run the server and set up automatic restart when the VPS reboots, this VPS uses the &lt;code&gt;pm2&lt;/code&gt; (Process Manager) command. From the directory where you cloned and built your project (and assuming you are using the default commands in &lt;code&gt;package.json&lt;/code&gt;), we'll run Next.js &lt;code&gt;start&lt;/code&gt; with this command to give it a name of &lt;code&gt;next-js&lt;/code&gt; in the &lt;code&gt;pm2&lt;/code&gt; interface:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; nodejs pm2 start npm &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"next-js"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; start


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For variations on &lt;code&gt;pm2&lt;/code&gt; commands you might want to use here, &lt;a href="https://stackoverflow.com/a/70361967/12869266" rel="noopener noreferrer"&gt;this Stackoverflow answer&lt;/a&gt; goes into more detail.&lt;/p&gt;

&lt;h4&gt;
  
  
  Save your &lt;code&gt;pm2&lt;/code&gt; configuration.
&lt;/h4&gt;

&lt;p&gt;Verify that &lt;code&gt;pm2&lt;/code&gt; is running the correct processes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; nodejs pm2 list


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then save the active list:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; nodejs pm2 save


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If Next.js shows an error in the information returned from &lt;code&gt;pm2&lt;/code&gt;, for example if you tried to start it before removing &lt;code&gt;hello&lt;/code&gt; and it quit with the port error, you can restart it (assuming you named it &lt;code&gt;"next-js"&lt;/code&gt; as shown in the previous step):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; nodejs pm2 restart &lt;span class="s2"&gt;"next-js"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Edit NGINX configuration.
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE:&lt;br&gt;
For more detail on what you might want to change in NGINX config, a good place to start is NGINX's &lt;a href="https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/" rel="noopener noreferrer"&gt;Pitfalls and Common Mistakes&lt;/a&gt; article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you reload your site at your IP address now, you will already see your Next.js app, as it runs internally on the same port that the &lt;code&gt;hello&lt;/code&gt; script was using. However, there are additional things to edit in the config file. To edit the config (for the default server, which comes as the &lt;code&gt;hello&lt;/code&gt; app in this image) using &lt;code&gt;nano&lt;/code&gt;, run (replacing &lt;code&gt;nano&lt;/code&gt; with your favorite command line text editor if desired):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

nano /etc/nginx/sites-available/default


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There are several things to particularly pay attention to in this file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;root&lt;/code&gt; and asset &lt;code&gt;location&lt;/code&gt; directives should be updated to match the location of your &lt;a href="https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/" rel="noopener noreferrer"&gt;static files and assets&lt;/a&gt; to let NGINX serve your static files. You &lt;em&gt;can&lt;/em&gt; proxy everything to Next.js, however letting NGINX serve them directly when possible will be faster. &lt;a href="https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#proxy-everything" rel="noopener noreferrer"&gt;See more info from NGINX&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Configure the &lt;code&gt;root&lt;/code&gt; directive to match the Next.js &lt;code&gt;public&lt;/code&gt; directory (in this example we cloned the &lt;code&gt;your_site&lt;/code&gt; repo to the &lt;code&gt;/srv&lt;/code&gt; folder):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;

&lt;span class="k"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/srv/your_site/public&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Configure the &lt;code&gt;location&lt;/code&gt; directive for the images directory to point to the images directory in the Next.js repository, using the &lt;code&gt;try_files&lt;/code&gt; directive:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/images&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="s"&gt;@proxy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then ensure you have your root directory configured to use your proxy setup also:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="s"&gt;@proxy&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then set up the named location for proxy configuration:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="s"&gt;@proxy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# proxy config here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The proxy configuration itself likely may be reused from the &lt;code&gt;hello&lt;/code&gt; app with few or no changes.&lt;/p&gt;

&lt;p&gt;To test that it is working correctly, you can load your site and verify a) everything that &lt;em&gt;is&lt;/em&gt; present loads as expected, and b) that you get the correct 404 page from Next.js, and not a 500 error, if you attempt to view a image that does &lt;em&gt;not&lt;/em&gt; exist.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;server_name&lt;/code&gt; field, which should be updated to your &lt;a href="https://nginx.org/en/docs/http/server_names.html" rel="noopener noreferrer"&gt;domain address or an appropriate wildcard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have finished any configuration updates, verify that the configuration file's syntax is correct with &lt;code&gt;sudo nginx -t&lt;/code&gt;, then use &lt;code&gt;sudo systemctl restart nginx&lt;/code&gt; to restart the NGINX server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations! Your content is now available on your VPS.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Possible Additional Steps
&lt;/h3&gt;

&lt;p&gt;There are some more things you may want to do on your VPS once the site is up and running, such as configuring the firewall and other security, setting up non-root users for everyday use, or configuring NGINX logging. We won't be covering the details here, but you may find these pages useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.nginx.com/nginx/admin-guide/monitoring/logging/" rel="noopener noreferrer"&gt;Configure NGINX Access and Error Logs&lt;/a&gt; (Note this page is documentation for NGINX +, so double check if any particular feature you want to use is supported by the base version before use.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bitlaunch.io/blog/how-to-secure-your-anonymous-vps/" rel="noopener noreferrer"&gt;How To Secure Your Anonymous VPS on bitlaunch.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/knowbee/how-to-setup-continuous-deployment-of-a-website-on-a-vps-using-github-actions-54im"&gt;How to set up continuous deployment of a website on a VPS using GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>virtualmachine</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building a Dungeon Generator, Part 1. Introduction</title>
      <dc:creator>Anna R Dunster</dc:creator>
      <pubDate>Wed, 29 Dec 2021 02:25:59 +0000</pubDate>
      <link>https://dev.to/ardunster/building-a-dungeon-generator-part-1-introduction-26cn</link>
      <guid>https://dev.to/ardunster/building-a-dungeon-generator-part-1-introduction-26cn</guid>
      <description>&lt;p&gt;(Originally published on my blog on December 19, 2021)&lt;/p&gt;

&lt;p&gt;Over the summer, myself and a few friends hatched an idea to work on a project together to practice programming. A bit of brainstorming later and we settled on a game idea that is sort of a hybrid roguelike / Legend of Zelda style game, and started hashing out some details; finally we had enough of a working name to put together a Unity project and a github repo, and started banging out some code. (I am sure Project Dungeon will not be the final name of this project.... I hope....)&lt;/p&gt;

&lt;h2&gt;
  
  
  Layout Generation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AjeshJhalli" rel="noopener noreferrer"&gt;Ajesh Jhalli&lt;/a&gt; and &lt;a href="https://github.com/frodosynthesis" rel="noopener noreferrer"&gt;Denver S&lt;/a&gt; collaborated to design and implement the layout generator part of the code, which produces layouts like these based on input variables.&lt;/p&gt;

&lt;p&gt;With twenty rooms:&lt;/p&gt;

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

&lt;p&gt;Seven rooms:&lt;/p&gt;

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

&lt;p&gt;Thirty-seven rooms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fok3hq6qw173eqhieaf96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fok3hq6qw173eqhieaf96.png" alt="Layout with thirty-seven rooms"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blue room is considered the "starting room" and the red room is considered the "boss room". This data at this stage isn't interactable in any way, but shows a proof of concept in the code to build the layout, correctly assign doors, and pathfind from a randomized starting room to the most distant dead end room in the dungeon, which we then assign as a boss room.&lt;/p&gt;

&lt;p&gt;Originally this was implemented using &lt;code&gt;Random&lt;/code&gt;, but after watching a few Game Developer Conference talks on using "random" functions vs using a seed and hashing, I convinced my co-developers that we should use hashing to create noise, instead of random number generators, to determine our "random" generation. This way, every dungeon generated from the same seed will have the same layout, monster placement, and so on. This will allow different players to start in the same world if they choose, and also increase the reproducibility of behavior for testing and bug hunting.&lt;/p&gt;

&lt;p&gt;Ajesh adapted the C++ function that Squirrel Eiserloh shows at 46:33 in his GDC talk, &lt;a href="https://www.gdcvault.com/play/1024365/Math-for-Game-Programmers-Noise" rel="noopener noreferrer"&gt;Math for Game Developers: Noise-Based RNG&lt;/a&gt; into C# for us, and swapped our implementation over to use the new function. It works great! There will be some interesting challenges to solve in the future using this (for one, if we have one world seed used to generate all hashes, how do we handle changing the dungeon layouts when a character dies and we want to generate new ones?), but I think overall it is the correct choice. Besides, it's fun to generate a layout, mess with the seed, generate a new layout, then switch back to the first one and see the first layout again!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tilemap generation
&lt;/h2&gt;

&lt;p&gt;I volunteered to handle the tilemap generation end of things, the actual generating of the rooms described by the layout generator as in-game objects. I figured I would be best positioned to do this as I'm also planning to handle the majority of the art assets as well.&lt;/p&gt;

&lt;p&gt;Following parts of &lt;a href="https://www.youtube.com/playlist?list=PLcRSafycjWFenI87z7uZHFv6cUG2Tzu9v" rel="noopener noreferrer"&gt;this video tutorial series on generating procedural dungeons in Unity&lt;/a&gt; by &lt;a href="https://www.youtube.com/c/SunnyValleyStudio" rel="noopener noreferrer"&gt;Sunny Valley Studio&lt;/a&gt;, and using &lt;a href="https://stealthix.itch.io/dungeon-tileset-32x32-px" rel="noopener noreferrer"&gt;this 32x32 dungeon tileset&lt;/a&gt; by &lt;a href="https://stealthix.itch.io/" rel="noopener noreferrer"&gt;Stealthix&lt;/a&gt; for prototyping, I got the code into a situation where I could paint tiles into a tilemap programmatically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0u2vcvj1qu7fjk1xmpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0u2vcvj1qu7fjk1xmpo.png" alt="Room tilemap painted by code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But then I basically ran into a wall. I was thinking I would have to generate each room as an object and create a prefab or something to pass back to an array of rooms for the game engine to pick from as needed, and I just didn't know enough about Unity. I had no idea how to do that. Honestly, I still don't. Most of my time on this project in the past couple months has been spent on other things, like setting up &lt;code&gt;SpriteLibraryAsset&lt;/code&gt;s and animations, and trying to get a better handle on how the different parts of Unity work together and interact in general.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But&lt;/em&gt;... Saturday I had a different idea.&lt;/p&gt;

&lt;p&gt;What if we use each floor of the dungeon as its own tilemap? This idea will probably come with optimization challenges, especially if we go for larger dungeons. But I can see a path to making this work! And any working prototype is better than none. Let's do this!&lt;/p&gt;

&lt;p&gt;Next up in part 2: adapting the Room Generator code to take floor and wall tilemaps, room dimensions, and starting coordinates as arguments. Also work on assigning wall tiles manually, since trying to do it with a rule tile ... didn't turn out good, and probably even less so once I switch to using the assets I created that are 3 units deep for the complete height of a wall on-screen. Oh, yeah, and at some point I'll need to finish figuring out the doors and room transitions to be able to generate them with code. I'm sure it's doable.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>gamedev</category>
      <category>unity2d</category>
      <category>procedural</category>
    </item>
    <item>
      <title>Adding Cypress to an existing Angular project</title>
      <dc:creator>Anna R Dunster</dc:creator>
      <pubDate>Wed, 29 Dec 2021 02:14:54 +0000</pubDate>
      <link>https://dev.to/ardunster/adding-cypress-to-an-existing-angular-project-27eh</link>
      <guid>https://dev.to/ardunster/adding-cypress-to-an-existing-angular-project-27eh</guid>
      <description>&lt;p&gt;(Originally published on my blog December 23, 2021)&lt;/p&gt;

&lt;p&gt;When I set up my Angular13 single-page application (SPA) at work, I initially considered setting up Selenium for end to end testing, but due to the steep learning curve I was experiencing with the stack in general (and some other projected hurdles such as figuring out how to mock data that would be accessed through the &lt;code&gt;window.external&lt;/code&gt; property in the live environment), I kept putting it off.&lt;/p&gt;

&lt;p&gt;But, in the past month I encountered a situation in which the tests that I have written for this project did not actually catch a dependency error that caused the page not to load when the template was accessed at runtime.  I suspect this is because I am &lt;em&gt;not&lt;/em&gt; using the &lt;code&gt;TestBed&lt;/code&gt; for the majority of my unit tests, since TestBed has a high overhead and slows down my couple thousand test cases dramatically.&lt;/p&gt;

&lt;p&gt;I also recently read some articles (particularly &lt;a href="https://corgibytes.com/blog/2019/12/16/From-Zero-to-Tests/"&gt;From Zero to Tests&lt;/a&gt; on Corgibytes) that got me rethinking my test structure. Most of my tests currently are simply unit tests, possibly a few integration tests - mostly, does this function do what we expect when given the inputs we expect it to get (or handle it gracefully if we give it bad inputs)?  I definitely don't have any tests that address DOM rendering. So, I had a stretch of downtime where I don't have anyone waiting on any specific projects and figured it would be a great time to set something up.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why Cypress?&lt;/strong&gt;&lt;br&gt;
You may have noticed that I mentioned Selenium to begin with, but this article is about Cypress. Going back to Corgibytes, this time to &lt;a href="https://corgibytes.com/blog/2017/02/21/integration-tests-fun/"&gt;Integration Tests Can Be Fun!&lt;/a&gt;, I didn't really care for the idea of setting up a black box that was hard to understand to handle my higher level tests. Plus, I've heard good things about Cypress from other sources, and it looks like it is relatively easy to set up, compared to Selenium.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The first thing I did after deciding to set up Cypress was hop over to &lt;a href="https://docs.cypress.io/guides/overview/why-cypress"&gt;their website&lt;/a&gt; and start watching the &lt;a href="https://www.youtube.com/watch?v=LcGHiFnBh3Y"&gt;introduction video&lt;/a&gt;. This video suggests to install Cypress in any project with &lt;code&gt;npm install -D cypress&lt;/code&gt;. I'm sure this would work, but I am also aware that Angular has its own package addition process, and sure enough, double checking for an Angular-specific install path lead me to &lt;a href="https://testing-angular.com/end-to-end-testing/"&gt;End-to-End testing with Cypress - Testing Angular&lt;/a&gt;, which gives the Angular CLI command &lt;code&gt;ng add @cypress/schematic&lt;/code&gt; instead.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt;&lt;br&gt;
If, like me, the first thing you do after installing Cypress is run it with &lt;code&gt;npm run cypress:open&lt;/code&gt;, you may be confused by the immediate error message of "Warning: Cypress could not verify that this server is running." Don't worry about this for now if you're following the tutorials. Later in, they go over running your development server alongside Cypress for testing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;From there I hopped into the &lt;a href="https://docs.cypress.io/guides/getting-started/writing-your-first-test"&gt;introductory tutorials that Cypress provides&lt;/a&gt; to get started. This is my first experience with a browser automation suite, and I have to say, it's pretty fun to enter commands in text, hop back over to the browser window and see it executing them! Definitely excited to push forward and get to a state where I could test my actual application.&lt;/p&gt;

&lt;p&gt;So, the next step was to ensure that Cypress actually could test against it. The Cypress tutorials direct the user to enter a localhost address into &lt;code&gt;cy.visit()&lt;/code&gt;, but the Angular CLI &lt;code&gt;ng add&lt;/code&gt; command we used earlier already set up a default localhost address that's &lt;em&gt;different&lt;/em&gt; from the one mentioned in Cypress's tutorials... &lt;em&gt;and&lt;/em&gt; matches the one available from &lt;code&gt;ng serve&lt;/code&gt;. If you have customized &lt;code&gt;ng serve&lt;/code&gt;, I don't know if this will hold true, but in my case the address Cypress was looking for is a match.  (If you want to customize the localhost address, the Cypress tutorial goes over configuring this in &lt;a href="https://docs.cypress.io/guides/getting-started/testing-your-app#Step-3-Configure-Cypress"&gt;Step 3 of Testing Your App&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Angular also provides a basic test file with a couple of &lt;code&gt;contains()&lt;/code&gt; assertions when you use &lt;code&gt;ng add&lt;/code&gt; to include Cypress in your project. In my case, since this is an existing project with actual content, neither passed. I'm not sure whether they're valid for the empty new app that Angular's CLI generates when creating a new application; I suspect they might be.&lt;/p&gt;

&lt;p&gt;My case is a bit unusual in that a user should never actually see this page; users access individual pages by URLs that are stored in the software that they show up in. The only time a user would end up at the root of the application is if somehow the stored link in the software didn't match any valid route. Nonetheless, it's useful to ensure that the page loads and renders, since if it doesn't, none of the rest of the application will either! I ended up just writing a few basic test cases against the minimal content and links that I have at the root of my application for proof of concept.&lt;/p&gt;

&lt;p&gt;I also took one of the user-facing pages that was fairly simple and worked up an initial-state test for that, too, as proof of concept. I added &lt;code&gt;data-cy="whatever"&lt;/code&gt; tags to the important elements, and had Cypress check that the ones that should exist on page load, do; and the ones that should be hidden on page load, are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Mock Environment
&lt;/h2&gt;

&lt;p&gt;Most developers at this stage are going to be starting to test their login flow and look at building stubs for server requests. For my application, though, the software that the pages get loaded in handles all of that, my code doesn't have anything to do with authenticating a user. So what I needed to do next was figure out how to stub the entire interface that lives on &lt;code&gt;window.external&lt;/code&gt; when the pages are live in the production environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Didn't Work
&lt;/h3&gt;

&lt;p&gt;First I decided to try using the custom commands feature to add a command to apply the mock I've been using for my unit tests to &lt;code&gt;window.external&lt;/code&gt;. There was an immediate hurdle involving Typescript and parsing the commands file: as soon as I copied the example namespace declaration from the comment at the top of &lt;code&gt;commands.ts&lt;/code&gt; and adapted it to name my custom command, I got &lt;code&gt;Argument of type 'mockWindow' is not assignable to parameter of type 'keyof Chainable&amp;lt;any&amp;gt;'.ts(2345)&lt;/code&gt; from my linter. I tried a few things and got other errors, then ended up following along with &lt;a href="https://medium.com/@gghalistan/adding-custom-commands-to-cypress-typescript-28d23f90c2fd"&gt;Adding Custom Commands to Cypress Typescript&lt;/a&gt;. I created an &lt;code&gt;index.d.ts&lt;/code&gt; file in &lt;code&gt;./support&lt;/code&gt; to hold my modified namespace declaration, and added &lt;code&gt;./support&lt;/code&gt; to the &lt;code&gt;tsconfig.json&lt;/code&gt; in the &lt;code&gt;cypress&lt;/code&gt; folder, which resolved the type errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.d.ts&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;Cypress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Chainable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mockExternal&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Chainable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, then the part of the custom command where I import the mock caused a huge error, the first part of which looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Webpack&lt;/span&gt; &lt;span class="nx"&gt;Compilation&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;node_modules&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;angular&lt;/span&gt;&lt;span class="sr"&gt;/router/&lt;/span&gt;&lt;span class="nx"&gt;fesm2015&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mjs&lt;/span&gt;
&lt;span class="nx"&gt;Module&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Can&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;t resolve &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;angular&lt;/span&gt;&lt;span class="sr"&gt;/common' in 'C:&lt;/span&gt;&lt;span class="se"&gt;\U&lt;/span&gt;&lt;span class="sr"&gt;sers&lt;/span&gt;&lt;span class="se"&gt;\a&lt;/span&gt;&lt;span class="sr"&gt;dunster&lt;/span&gt;&lt;span class="se"&gt;\D&lt;/span&gt;&lt;span class="sr"&gt;ocuments&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sr"&gt;epos&lt;/span&gt;&lt;span class="se"&gt;\H&lt;/span&gt;&lt;span class="sr"&gt;tmlApp&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;ode_modules&lt;/span&gt;&lt;span class="se"&gt;\@&lt;/span&gt;&lt;span class="sr"&gt;angular&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sr"&gt;outer&lt;/span&gt;&lt;span class="se"&gt;\f&lt;/span&gt;&lt;span class="sr"&gt;esm2015&lt;/span&gt;&lt;span class="err"&gt;'
&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C:&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;Users&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;adunster&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;Documents&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s1"&gt;epos&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;HtmlApp&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;ode_modules&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;@angular&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s1"&gt;outer&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;fesm2015&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;Parsed&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;
  &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;adunster&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Documents&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;HtmlApp&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;node_modules&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;angular&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;fesm2015&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;Field&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;doesn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;t contain a valid alias configuration
    resolve as module
// ... 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tried getting around this by duplicating my mock to a file in cypress's directory and editing out any references that imported anything in my Angular project such as types or mocks. Only then I did I find out that nothing I assigned to a property of &lt;code&gt;window.external&lt;/code&gt; was actually showing up in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  How did I fix it?
&lt;/h3&gt;

&lt;p&gt;My ultimate solution was to skip using Cypress commands entirely, and change program behavior based on environment variables. I'll probably refine this to a more specific npm script, maybe with an additional &lt;code&gt;environment.ts&lt;/code&gt; file (as opposed the development environment in general) in the future. For now, the code that assigns the &lt;code&gt;.external&lt;/code&gt; property in my Angular service changed from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;_external&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;_external&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MockCEMRWindow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, as I develop my specific Cypress tests, I can mock data into the functions in the &lt;code&gt;MockCEMRWindow.external&lt;/code&gt; property to produce the results I need to test. It's not ideal, but for the moment it will do what I need it to do, and actually is something I'd meant to do for a while for manual testing in a browser window running the development environment. I'm sure I will want to dig further into &lt;code&gt;cy.stub()&lt;/code&gt; and &lt;code&gt;cy.spy()&lt;/code&gt; when I get the chance, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Tests Automatically
&lt;/h2&gt;

&lt;p&gt;I'm sure there's a lot of room for growth in automating our deployment processes, but for now, the way building and deploying to production is currently set up is through the npm scripts in the root &lt;code&gt;package.json&lt;/code&gt;. After completing code and testing on a feature or bug fix, the code gets merged into the &lt;code&gt;fresh&lt;/code&gt; branch, and then we run an npm script that will cause the production server to pull the &lt;code&gt;fresh&lt;/code&gt; branch, run the Karma/Jasmine unit tests, and then run build it if the tests don't fail. (It's similar for the non-production server we use for semi-live testing on real data, only usually on the current development branch instead of &lt;code&gt;fresh&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;Unfortunately, it looks like this will be neither simple nor straightforward to set up for this situation (especially given that all of these have to live inside a &lt;code&gt;pushd&lt;/code&gt; command since we are using UNC paths), so this will be an adventure for another week. But Cypress has some direction here in their &lt;a href="https://docs.cypress.io/guides/continuous-integration/introduction#Boot-your-server"&gt;Continuous Integration&lt;/a&gt; documentation if you're looking for where to head next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After getting my feet wet with Cypress and seeing a few of the things it can do, I'm actually really genuinely excited about it. It looks like a great tool and I can't wait to use it to help guarantee the resiliency of my code!&lt;/p&gt;

&lt;p&gt;It isn't too hard to get the basics set up on an existing project, although there are a few tricks you may have to deal with depending on your environment. Their tutorials are great, though, and their documentation is clear and easy to follow.&lt;/p&gt;

&lt;p&gt;I think if I were building a page that works through more typical API calls and HTTP requests it would be a lot easier to set up appropriate mocks and stubs, or at least have a lot more clear direction. Still, even with the unique challenges of my environment, the basic setup has not been difficult at all. Don't be afraid to just install it and get started!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>cypress</category>
      <category>testing</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Consequences (The Game)</title>
      <dc:creator>Anna R Dunster</dc:creator>
      <pubDate>Wed, 29 Dec 2021 02:10:08 +0000</pubDate>
      <link>https://dev.to/ardunster/consequences-the-game-4fmi</link>
      <guid>https://dev.to/ardunster/consequences-the-game-4fmi</guid>
      <description>&lt;p&gt;(Originally published on my blog on January 21, 2021)&lt;/p&gt;

&lt;p&gt;I had an interesting idea today when I received something I'd ordered, which happened to be a birthday present for my niece. The present is a Mad Libs book, and the idea was: that would be easy to write code for - and I bet my niece and her brothers would enjoy messing with it, too. (Plus it saves the books!) &lt;/p&gt;

&lt;p&gt;A little research later (to see how careful I needed to be with naming the script...) and I discovered that the game Mad Libs is actually significantly based on an older game called &lt;a href="https://en.wikipedia.org/wiki/Consequences_(game)"&gt;Consequences&lt;/a&gt;, and in fact the same code could work for either style of game, based on content templates. So here I'm going to organize my ideas for the code, and then I'll get to programming! &lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Decisions
&lt;/h2&gt;

&lt;p&gt;For all the enjoyment I've been getting out of C++ lately, and all that writing in Python now makes me feel strangely insecure ;), this project is relatively uncomplicated, and at a high level of abstraction, and simply doesn't need the level of control that C++ gives. Plus, I already know where to find most of the tools I need to make it work in Python. &lt;/p&gt;

&lt;p&gt;Initially I thought maybe I'd just write a command line script, and I might still make it able to run as a command line script if needed, but it's a better exercise if I stretch myself a bit and craft it with a GUI, and easier for a user to get into, too. For the GUI, mostly for licensing practicalities, I'll use PySide2. (For more information about the licensing and other differences, &lt;a href="https://machinekoder.com/pyqt-vs-qt-for-python-pyside2-pyside/"&gt;Machine Koder wrote an article covering the main points&lt;/a&gt;.) &lt;/p&gt;

&lt;p&gt;Content files will be written with JSON, since it's a nice, easy, human readable (and human editable) format that should be simple to work with, unless I get into the thick of writing code and have trouble getting it to do what I want. &lt;/p&gt;

&lt;p&gt;Finally, I'll set the program up to run as a package in a self-contained &lt;code&gt;venv&lt;/code&gt; to avoid dependency issues and keep users' system installs clean. Plus it's good practice (and &lt;em&gt;a&lt;/em&gt; good practice).&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Files
&lt;/h2&gt;

&lt;p&gt;At this point in ideation I can see basically 3 fields of information that a content file would need to have to work. First, a title, although that's not critical. Second, the base text that user entered words will be applied into, with properly named "blanks" to be filled in. Third, a dictionary of the values to fill, each corresponding to a named blank:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1:
&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Breakfast"&lt;/span&gt;
&lt;span class="c1"&gt;# 2:
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{name1} {verb1} to a/an {adjective1} {noun1} of {noun_plural1}."&lt;/span&gt;
&lt;span class="c1"&gt;# 3:
&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verb1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjective1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noun1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noun_plural1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possibly not in that order (it might make more sense to a content producer to have the dictionary above the text, for example), although order shouldn't actually matter if I call each field by name. &lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was thinking about using &lt;code&gt;.gitignore&lt;/code&gt; to hide &lt;em&gt;all&lt;/em&gt; "content" files, since initially I'll be populating it with content from the book I purchased for her, but I think it makes more sense to have a separation between public and private content - public content can be shared with anybody, and makes it easier to get into and start messing with the game, whereas private content can be things that &lt;em&gt;shouldn't&lt;/em&gt; be shared publicly, because of copyright or whatever other concerns. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;As a side note, I want to also save user generated output files, probably just as a text file based on the content filename + a timestamp. Alternatively, as a JSON including the original title and a "By: " field input by the user. If I go this route, I could also save the dictionary of values used, to allow editing or fine tuning a particular output for maximum entertainment. ;)&lt;/p&gt;

&lt;p&gt;Ideally, there'd be a way for a user to create a content file through the GUI, but that's a more complicated proposition that I don't intend to include immediately. Writing a JSON file with the necessary fields isn't too difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game Loop
&lt;/h2&gt;

&lt;p&gt;When the program starts, the user should be presented with a "Welcome" string, and the list of available content by title (possibly organized by subdirectory). Clicking on a title, then a select button will bring up a dialog in which the user enters each requested value, then a button at the bottom to "Generate" the text.  Selecting Generate will produce a text file saved to an output directory, and display the contents on the screen, at which point the game will offer the user to "Select new" or "Exit" - or possibly to "View saved" as well. Select new should return the user to the initial screen, while view saved would be an additional dialog to view the list of generated files and select one to view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Possible problems
&lt;/h3&gt;

&lt;p&gt;What happens if the user doesn't fill in all the blanks? One could go two ways here: one, by not &lt;em&gt;allowing&lt;/em&gt; the user to proceed without filling blanks (which could be annoying), or two, the option I prefer: if a value isn't filled in, just supply the name of the value (in the example above, if someone declined to fill in adjective1, then the text would just output with adjective1.) This would work especially well with the idea of saving the values, and actually could allow content producers to be more specific in their directions: noun1 could be pre-supplied with "noun related to food", or something like that, and if the user chooses not to fill it in, that would be the value used.&lt;/p&gt;

&lt;p&gt;Another issue that's less simple to fix is badly formed content. Too many possible values, such that not all of them get used, is not a big deal programmatically, although disappointing to the user when their &lt;em&gt;favorite verb ever&lt;/em&gt; doesn't make it into the final text. But what if they format the content incorrectly, such that either the JSON can't be read at all, or the base text doesn't have the input values correctly indicated, or asks for an input value that isn't listed in the dictionary?  Some kind of error handling and display is vital to this situation, preferably with helpful hints about what went wrong. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Writing all this out has given me a pretty solid place to start in developing the software of this game. I anticipate the hardest part will be getting PySide2 working as intended, since I haven't done very much GUI work at this point, possibly followed in difficulty by correctly packaging the program to work as intended, although I've made that work a few times. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After writing this post, I immediately dove into implementing the project. You can see my results in this &lt;a href="https://github.com/ardunster/consequences-game"&gt;Consequences github repository&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>projects</category>
      <category>learning</category>
      <category>consequences</category>
    </item>
    <item>
      <title>Thinking About Switching to Sphinx / ReST</title>
      <dc:creator>Anna R Dunster</dc:creator>
      <pubDate>Thu, 05 Nov 2020 23:54:09 +0000</pubDate>
      <link>https://dev.to/ardunster/thinking-about-switching-to-sphinx-rest-2p5i</link>
      <guid>https://dev.to/ardunster/thinking-about-switching-to-sphinx-rest-2p5i</guid>
      <description>&lt;p&gt;That’s it, I’m tired of WordPress. &lt;/p&gt;

&lt;p&gt;There’s just too much bloat, too much going on, too many layers of stuff to have the level of control that I want. The interface is clunky and slow and constantly wants me to update a variety of things which are of questionable import to my site anyway. The website itself is slow, and to get it the way I want it I’d have to dig way further into the available themes, or try to build one from scratch.&lt;/p&gt;

&lt;p&gt;If I’m going to build it from scratch, why not use a technology that actually interests me?&lt;/p&gt;

&lt;p&gt;At my internship at MousePaw Media I recently started using &lt;a href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; and reStructuredText to write documentation files for the project I’m working on. Pretty simple, and pretty cool (although in this case I’m fortunate enough that the web site is all set up - all I have to do is write docs in &lt;code&gt;.rst&lt;/code&gt;. So I thought it’d be a good option to try for my personal page, too.&lt;/p&gt;

&lt;p&gt;So now I’m researching the best way to go about this. Sphinx, by itself, is designed around building software documentation.  That’s a little bit different than a blog (although I suppose it doesn’t entirely have to be)  There are a few decent options for extending Sphinx to a more blog-like setup, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reinout van Rees
&lt;/h2&gt;

&lt;p&gt;Reinout has built some tools to assist Sphinx in converting &lt;code&gt;.rst&lt;/code&gt; source files into a blog format, including tagging, and &lt;a href="https://reinout.vanrees.org/weblog/2012/01/22/website-changes.html#sphinx-as-weblog-code-is-now-available-somewhat"&gt;made them public&lt;/a&gt;. On the plus side, it’s software designed to do basically what I’m thinking of doing. On the down side, there is no quick &lt;code&gt;pip install&lt;/code&gt; feature, and it’s very specifically designed for Reinout’s own website. So, if I want to use that code, I’d have to dig through it and borrow and adapt the pieces I want, while building the rest to my own personal specifications. This might be worth the effort if the other options don’t measure up, but isn’t a very attractive option to begin with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tinkerer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://tinkerer.me"&gt;Tinkerer&lt;/a&gt; is a blogging tool built on Sphinx. It has support for a handful of useful blogging tools, such as Discus, localization, RSS feeds, and more. It does have documentation that should make it fairly straightforward to use. On the down side, it hasn’t been updated since 2017. That may not be a problem, as long as it all works fine; I don’t need a lot of fancy, new, language features, I just need to generate static HTML from templates plus source files. Definitely an option. &lt;/p&gt;

&lt;h2&gt;
  
  
  ABlog
&lt;/h2&gt;

&lt;p&gt;Similarly to Tinkerer, &lt;a href="https://ablog.readthedocs.io"&gt;ABlog&lt;/a&gt; is built in Python, as an extension for Sphinx, for the purpose of easily converting a project into a fully functional blog. It also supports Discus and RSS feeds if I ever decide to use either of them (which I’m not sure if I will, but it’s nice to have options). The tagging and category functions look pretty handy and usable.  And it has current development - the last release was just a few days ago. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;These all seem like decent tools that do the job they were meant to do. The sites using them are functional. But overall, ABlog seems to have the most (and most recent) documentation and tutorials available. I’m going to start there, get a trial page working, and then migrate my WordPress site over.  Next post will probably be about what I have to learn to make &lt;em&gt;that&lt;/em&gt; happen....&lt;/p&gt;

</description>
      <category>python</category>
      <category>sphinx</category>
      <category>blogging</category>
      <category>ablog</category>
    </item>
  </channel>
</rss>
