Jekyll2023-07-01T02:04:10+00:00http://monicagranbois.com/feed.xmlMonica GranboisExplaining code to myself.Monica GranboisResolving “cannot upload bitcode because bitcode is imbalanced” error when uploading to App Store Connect2022-09-27T00:00:00+00:002022-09-27T00:00:00+00:00http://monicagranbois.com/blog/xcode/bitcode-is-imbalanced<p>I received the following error from <a href="https://fastlane.tools/">fastlane</a> when uploading to App Store Connect:</p>
<blockquote>
<p>exportArchive: exportOptionsPlist error for key ‘uploadBitcode’: cannot upload bitcode because bitcode is imbalanced</p>
</blockquote>
<p>This error occurred because, as stated in the <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes#Deprecations">XCode 14 deprecation notes</a>:</p>
<blockquote>
<p>Starting with Xcode 14, bitcode is no longer required for watchOS and tvOS applications, and the App Store no longer accepts bitcode submissions from Xcode 14.</p>
<p>Xcode no longer builds bitcode by default and generates a warning message if a project explicitly enables bitcode: “Building with bitcode is deprecated. Please update your project and/or target settings to disable bitcode.” The capability to build with bitcode will be removed in a future Xcode release. IPAs that contain bitcode will have the bitcode stripped before being submitted to the App Store. Debug symbols for past bitcode submissions remain available for download.
<!--more--></p>
</blockquote>
<p>My <a href="https://barbellhelper.com/">app</a> is a watchOS app. So, I had previously enabled bitcode upload in the Fastfile:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lane</span> <span class="ss">:build</span> <span class="k">do</span>
<span class="n">build_app</span><span class="p">(</span><span class="ss">scheme: </span><span class="s2">"MyApp"</span><span class="p">,</span>
<span class="ss">include_bitcode: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>I resolved the upload error by removing the <code>include_bitcode</code> parameter:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lane</span> <span class="ss">:build</span> <span class="k">do</span>
<span class="n">build_app</span><span class="p">(</span><span class="ss">scheme: </span><span class="s2">"MyApp"</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Sharing this in case it helps someone else.</p>Monica GranboisI received the following error from fastlane when uploading to App Store Connect: exportArchive: exportOptionsPlist error for key ‘uploadBitcode’: cannot upload bitcode because bitcode is imbalanced This error occurred because, as stated in the XCode 14 deprecation notes: Starting with Xcode 14, bitcode is no longer required for watchOS and tvOS applications, and the App Store no longer accepts bitcode submissions from Xcode 14. Xcode no longer builds bitcode by default and generates a warning message if a project explicitly enables bitcode: “Building with bitcode is deprecated. Please update your project and/or target settings to disable bitcode.” The capability to build with bitcode will be removed in a future Xcode release. IPAs that contain bitcode will have the bitcode stripped before being submitted to the App Store. Debug symbols for past bitcode submissions remain available for download.Using React to visualize the knapsack algorithm2022-06-14T00:00:00+00:002022-06-14T00:00:00+00:00http://monicagranbois.com/blog/webdev/knapsack-algorithm<p>React and Tailwind CSS are popular frontend technologies. I had not used either and wanted to try them. After completing some React tutorials, I wanted to create my own app. I decided to create an <a href="https://monicagranbois.com/knapsack-algorithm-visualization/">app</a> that would step through the knapsack algorithm. This would help me solidify my understanding of the algorithm while learning React and Tailwind CSS. This post lists the design and technology choices I made to build the app. For details about the algorithm itself, please visit <a href="https://monicagranbois.com/knapsack-algorithm-visualization/">https://monicagranbois.com/knapsack-algorithm-visualization/</a>.</p>
<!--more-->
<h2 id="approach">Approach</h2>
<p>My approach was to build a skeleton of the app first, without any CSS. This allowed me to focus on React and the logic of the app.</p>
<p>Once I had the basic logic working I added Tailwind CSS to the project. This allowed me to then focus on learning Tailwind CSS.</p>
<p>I then iterated on the project using React and Tailwind CSS at the same time.</p>
<p>I think this was a good approach since I was learning two technologies. It allowed me to focus on one technology at a time. Now that I am familiar with both I would approach a new project differently. In the future, I would iterate on a project with React and Tailwind CSS at the same time.</p>
<h2 id="technologies-used">Technologies Used</h2>
<ul>
<li><a href="https://create-react-app.dev/">Create React App</a>
<ul>
<li>I chose Create React App (CRA) because I was creating a new, standalone React App.</li>
<li>This was my first time using CRA, as the tutorials only used React in an index.html file.</li>
</ul>
</li>
<li><a href="https://tailwindcss.com/">Tailwind CSS</a>
<ul>
<li>To get started I followed the tutorials on the <a href="https://www.youtube.com/tailwindlabs">Tailwinds YouTube channel</a>. The documentation and online playground were also helpful.</li>
<li>I used the <a href="https://heroicons.com/">Hero Icons</a> project to find and add icons to my project.</li>
<li>Overall, I liked Tailwind CSS, but it felt verbose to apply styles in the HTML rather than in a separate CSS file.
<ul>
<li>The developer addressed the issue of separation of concerns in his blog post <a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/">CSS Utility Classes and “Separation of Concerns”</a>.</li>
<li>I did define custom classes for styles used in multiple places.</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://react-hook-form.com/">React Hook Form</a>
<ul>
<li>I initially wrote my own form validation. That was the wrong approach.</li>
<li>I found React Hook Form, which made form validation much easier.</li>
<li>It also allowed me to use dynamically-generated fields. This allowed me to add, edit and delete items on the initial setup screen.</li>
<li>I would recommend investigating this tool if your app uses forms.</li>
</ul>
</li>
<li><a href="https://github.com/react-syntax-highlighter/react-syntax-highlighter">react-syntax-highlighter</a>
<ul>
<li>I used this to highlight code segments when the app steps through the code.</li>
</ul>
</li>
<li><a href="https://github.com/msaracevic/react-embed-gist">react-embed-gist</a>
<ul>
<li>I used this to display the complete algorithm at the end. I used this rather than react-syntax-highlighter because gist has better copy support. If someone wants to copy the algorithm, it is easier for them to do so with the gist.</li>
<li>The downside is it performs a network call to get the gist.</li>
<li>I am not sure if this was the right approach.</li>
</ul>
</li>
<li><a href="https://pages.github.com/">GitHub Pages</a>
<ul>
<li>I use this to host the app. My blog is already hosted on GitHub Pages, so it seemed a natural place to host it.</li>
<li>I use a GitHub workflow to deploy the site.</li>
</ul>
</li>
</ul>
<p>Please try the app at <a href="https://monicagranbois.com/knapsack-algorithm-visualization/">https://monicagranbois.com/knapsack-algorithm-visualization/</a>. I would appreciate any feedback on it or my tech choices. Happy coding!</p>Monica GranboisReact and Tailwind CSS are popular frontend technologies. I had not used either and wanted to try them. After completing some React tutorials, I wanted to create my own app. I decided to create an app that would step through the knapsack algorithm. This would help me solidify my understanding of the algorithm while learning React and Tailwind CSS. This post lists the design and technology choices I made to build the app. For details about the algorithm itself, please visit https://monicagranbois.com/knapsack-algorithm-visualization/.Install PostgreSQL onto Ubuntu multipass vm2022-02-15T00:00:00+00:002022-02-15T00:00:00+00:00http://monicagranbois.com/blog/linux/install-postgresql-onto-ubuntu-multipass-vm<p>I recently installed PostgreSQL on a virtual machine on my dev computer. This post describes what I did to:</p>
<ol>
<li>install a vm</li>
<li>install PostgreSQL</li>
<li>access PostgreSQL from the host machine via <a href="https://www.pgadmin.org/">pgAdmin</a></li>
<li>install a sample database into PostgreSQL</li>
</ol>
<p><!--more--></p>
<p>I decided to use <a href="https://multipass.run/">Ubuntu multipass</a> to create the vm. I had not used it before, and this was a chance to experiment with it. Multipass lets you spin up vm instances from the command line. I found the <a href="https://multipass.run/docs">installation docs</a> easy to follow and had an Ubuntu vm running in a few minutes.</p>
<h2 id="install-multipass">Install Multipass</h2>
<p>I have a Mac, so I had a couple of options to install Multipass: <a href="https://brew.sh/">brew</a> or a direct install. I chose to install via brew:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>brew <span class="nb">install</span> <span class="nt">--cask</span> multipass
</code></pre></div></div>
<p>Some notes on my install:</p>
<ul>
<li>I used the default hyperkit driver for the hypervisor.
<ul>
<li>Multipass defaults to hyperkit; but, you can set <a href="https://www.virtualbox.org/">VirtualBox</a> as the hypervisor.</li>
</ul>
</li>
<li>I set the terminal application to iTerm following <a href="https://multipass.run/docs/changing-macos-terminal">these instructions</a></li>
</ul>
<p>After the installation process, I checked that multipass was installed:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>multipass version
multipass 1.8.1+mac
multipassd 1.8.1+mac
</code></pre></div></div>
<h2 id="create-a-vm">Create a VM</h2>
<p>The <code class="language-plaintext highlighter-rouge">launch</code> command is used to create a new vm. You can provide it a name or multipass can generate one for you. I chose to provide a name, <em>db-server</em>, using the <code class="language-plaintext highlighter-rouge">--name</code> parameter.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>multipass launch <span class="nt">--name</span> db-server
</code></pre></div></div>
<p>There are two ways two access the vm:</p>
<ul>
<li>by opening a shell in the instance</li>
<li>by using the <code class="language-plaintext highlighter-rouge">exec</code> command to execute commands directly.</li>
</ul>
<p>I chose to open a shell:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>multipass shell db-server
</code></pre></div></div>
<p>The shell will display some stats, including the ipaddress of the vm. <strong>Make note of the ipaddress, as it will be needed</strong> when connecting to the database at a later step.</p>
<h2 id="install-postgresql">Install PostgreSQL</h2>
<p>I installed the latest version using <a href="https://www.postgresql.org/download/linux/ubuntu/">the instructions on the PostgreSQL Ubuntu page</a>. The following commands are copied from that page. Please note, the following are all performed within the <code class="language-plaintext highlighter-rouge">db-server</code> shell.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create the file repository configuration:</span>
<span class="nb">sudo </span>sh <span class="nt">-c</span> <span class="s1">'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'</span>
<span class="c"># Import the repository signing key:</span>
wget <span class="nt">--quiet</span> <span class="nt">-O</span> - https://www.postgresql.org/media/keys/ACCC4CF8.asc | <span class="nb">sudo </span>apt-key add -
<span class="c"># Update the package lists:</span>
<span class="nb">sudo </span>apt-get update
<span class="c"># Install the latest version of PostgreSQL.</span>
<span class="c"># If you want a specific version, use 'postgresql-12' or similar instead of 'postgresql':</span>
<span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>postgresql
</code></pre></div></div>
<p>Installing PostgreSQL creates a <code class="language-plaintext highlighter-rouge">postgres</code> account on the Ubuntu vm. I logged into it to access psql:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ubuntu@db-server:<span class="nv">$ </span><span class="nb">sudo </span>su - postgres
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postgres@db-server:~<span class="nv">$ </span>psql
</code></pre></div></div>
<p>The following was displayed:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql <span class="o">(</span>14.2 <span class="o">(</span>Ubuntu 14.2-1.pgdg20.04+1<span class="o">))</span>
Type <span class="s2">"help"</span> <span class="k">for </span>help.
<span class="nv">postgres</span><span class="o">=</span><span class="c">#</span>
</code></pre></div></div>
<p>The postgres user does not have a password. It can be set using the <code class="language-plaintext highlighter-rouge">\password</code> command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql <span class="o">(</span>14.2 <span class="o">(</span>Ubuntu 14.2-1.pgdg20.04+1<span class="o">))</span>
Type <span class="s2">"help"</span> <span class="k">for </span>help.
<span class="nv">postgres</span><span class="o">=</span><span class="c"># \password</span>
Enter new password <span class="k">for </span>user <span class="s2">"postgres"</span>:
</code></pre></div></div>
<p>To exit postgres use the <code class="language-plaintext highlighter-rouge">\q</code> command</p>
<h2 id="setup-db-access">Setup DB Access</h2>
<p>PostgreSQL is installed on the vm, but it is not yet accessible from the Host machine (my macOS). The PostgreSQL config files will need to be modified. The files are located in <code class="language-plaintext highlighter-rouge">/etc/postgresql/{version}/main/</code> directory. In my case the files are in <code class="language-plaintext highlighter-rouge">/etc/postgresql/14/main/</code>.</p>
<p><strong>Warning:</strong> The following is not a secure way to setup PostgreSQL. See the <a href="https://www.postgresql.org/docs/14/admin.html">PostgreSQL documentation</a> for information on setting up PostgreSQL securely.</p>
<p>Note: I was logged in as the <code class="language-plaintext highlighter-rouge">postgres</code> user while making modifications.</p>
<h3 id="postgresqlconf">postgresql.conf</h3>
<p>This file contains settings such as default storage location and memory allocation. It also configures the IPAddresses that PostgreSQL will listen on.</p>
<p>Find this line:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#listen_addresses = 'localhost' # what IP address(es) to listen on;</span>
<span class="c"># comma-separated list of addresses;</span>
<span class="c"># defaults to 'localhost'; use '*' for all</span>
</code></pre></div></div>
<p>Remove the <code class="language-plaintext highlighter-rouge">#</code> to uncomment the line and change localhost to “*”</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>listen_addresses <span class="o">=</span> <span class="s1">'*'</span> <span class="c"># what IP address(es) to listen on;</span>
<span class="c"># comma-separated list of addresses;</span>
<span class="c"># defaults to 'localhost'; use '*' for all</span>
</code></pre></div></div>
<h3 id="pg_hbaconf">pg_hba.conf</h3>
<p>This file manages security. It controls user authentication, database access and which ipaddresses are allowed to connect. Entries are in the form:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONNECTION_TYPE DATABASE USER ADDRESS METHOD
</code></pre></div></div>
<p>Add the following line to the file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>host all all samenet md5
</code></pre></div></div>
<p>The above line will allow TCP/IP connections for all databases and users where the host is on the same subnet as the server. The connection method is “md5”.</p>
<ul>
<li>host - This is the connection type. “host” means a TCP/IP socket (either encrypted or not).</li>
<li>all - The first <code class="language-plaintext highlighter-rouge">all</code> is the database that is allowed to be connected to. In this case all databases can be connected to.</li>
<li>all - The second <code class="language-plaintext highlighter-rouge">all</code> is the user that can connect. In this case all users can connect.</li>
<li>samenet - This is the host address that is allowed to connect. Using <code class="language-plaintext highlighter-rouge">samenet</code> means that any address in the subnet the server is on is allowed to connect.</li>
<li>md5 - use <a href="https://www.postgresql.org/docs/14/auth-password.html">md5 authentication</a>.</li>
</ul>
<p>PostgreSQL will need to be restarted in order for the changes to take effect. Exit the <code class="language-plaintext highlighter-rouge">postgres</code> user back to the <code class="language-plaintext highlighter-rouge">ubuntu</code> user and restart PostgreSQL:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl restart postgresql.service
<span class="c"># check that PostgreSQL is ready after the restart</span>
<span class="nb">sudo </span>pg_isready
</code></pre></div></div>
<h3 id="pgadmin">pgAdmin</h3>
<p><a href="https://www.pgadmin.org/">pgAdmin</a> is installed on my host machine (MacOS). I want to use it to connect to the PostgreSQL installed on the Ubuntu vm. I used the ‘Add Server’ wizard to connect to the database with the following information</p>
<ul>
<li>General -> Name: db-server (although this could be anything you want)</li>
<li>Connection -> Host name/Address: 192.168.64.3 (The IPAddress of my Ubuntu vm)</li>
<li>Connection -> Port: 5432 (the default PostgreSQL port)</li>
<li>Connection -> Maintenance database: postgres</li>
<li>Connection -> Username: postgres</li>
</ul>
<h2 id="install-a-sample-database">Install a sample database</h2>
<p>Next, I installed a sample database because I wanted some tables and data to play with. Googling turned up many options. I chose the <a href="https://github.com/pthom/northwind_psql">northwind sample database for psql</a>.</p>
<h3 id="create-role">Create Role</h3>
<p>First, I created a role to use with the northwind db. I created a new role by right clicking on the “db-server” menu option and selecting “Create -> Login/Group Roles…”. The <a href="https://www.pgadmin.org/docs/pgadmin4/latest/role_dialog.html">pgAdmin documentation</a> has details on all the various fields. In short, I did the following:</p>
<ul>
<li>General -> Name: north</li>
<li>Definition -> Password: <em>super secret password</em></li>
<li>Privileges -> Can login: Yes</li>
<li>Privileges -> Superuser: No</li>
<li>Privileges -> Create roles: No</li>
<li>Privileges -> Create databases: Yes</li>
<li>Privileges -> Inherit rights from the parent roles: Yes</li>
<li>Privileges -> Can initiate streaming replication and backups: No</li>
</ul>
<p>The sql displayed in the SQL tab was the following:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">ROLE</span> <span class="n">north</span> <span class="k">WITH</span>
<span class="n">LOGIN</span>
<span class="n">NOSUPERUSER</span>
<span class="k">CREATEDB</span>
<span class="n">NOCREATEROLE</span>
<span class="n">INHERIT</span>
<span class="n">NOREPLICATION</span>
<span class="k">CONNECTION</span> <span class="k">LIMIT</span> <span class="o">-</span><span class="mi">1</span>
<span class="n">PASSWORD</span> <span class="s1">'xxxxxx'</span><span class="p">;</span>
<span class="k">COMMENT</span> <span class="k">ON</span> <span class="k">ROLE</span> <span class="n">north</span> <span class="k">IS</span> <span class="s1">'The user for the northwind database'</span><span class="p">;</span>
</code></pre></div></div>
<p>After clicking the save button the ‘north’ role was created.</p>
<h3 id="create-the-database">Create the database</h3>
<p>I created a database by right clicking on the “db-server” menu option and selecting “Create -> Database…”. The <a href="https://www.pgadmin.org/docs/pgadmin4/latest/database_dialog.html">pgAdmin documentation</a> details how to create a database. I did the following:</p>
<ul>
<li>General -> Database: northwind</li>
<li>General -> Owner: north</li>
<li>Definition -> Encoding: UTF8 (This is the encoding used by the sample database)</li>
<li>Definition -> Template: template1</li>
<li>Definition -> Tablespace: pg_default</li>
<li>Definition -> Collation: C.UTF-8</li>
<li>Definition -> Character Type: C.UTF-8</li>
<li>Definition -> Connection limit: -1</li>
</ul>
<p>The sql displayed in the SQL tab was:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">DATABASE</span> <span class="n">northwind</span>
<span class="k">WITH</span>
<span class="k">OWNER</span> <span class="o">=</span> <span class="n">north</span>
<span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">template1</span>
<span class="k">ENCODING</span> <span class="o">=</span> <span class="s1">'UTF8'</span>
<span class="n">LC_COLLATE</span> <span class="o">=</span> <span class="s1">'C.UTF-8'</span>
<span class="n">LC_CTYPE</span> <span class="o">=</span> <span class="s1">'C.UTF-8'</span>
<span class="n">TABLESPACE</span> <span class="o">=</span> <span class="n">pg_default</span>
<span class="k">CONNECTION</span> <span class="k">LIMIT</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="k">COMMENT</span> <span class="k">ON</span> <span class="k">DATABASE</span> <span class="n">northwind</span>
<span class="k">IS</span> <span class="s1">'Sample database'</span><span class="p">;</span>
</code></pre></div></div>
<h3 id="load-the-database">Load the database</h3>
<p>I opened the <a href="https://www.pgadmin.org/docs/pgadmin4/latest/query_tool.html">Query Tool</a> by drilling down to db-server -> Databases -> northwind, right clicking and choosing Query Tool. I am logged in as the postgres user, so any SQL I run will be under that account. I want to use the north account instead. The user can be changed by clicking on the connection drop down in the Query Tool editor.</p>
<p><img src="/images/pgAdminQueryTool.png" class="img-fluid mx-auto d-block" alt="The connection menu dropdown" /></p>
<p>After connecting as the <code class="language-plaintext highlighter-rouge">north</code> user the connection information should be: <code class="language-plaintext highlighter-rouge">northwind/north@db-server</code>.</p>
<p>Next, I download the northwind sample database from GitHub to my host machine (MacOS):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl https://raw.githubusercontent.com/pthom/northwind_psql/master/northwind.sql <span class="nt">-o</span> northwind.sql
</code></pre></div></div>
<p>I then loaded the tables using the Query Tool in pgAdmin. The steps I used were:</p>
<ul>
<li>Open File
<ul>
<li>Choose northwind.sql file that was just downloaded</li>
<li>This will display the contents of the file into the editor</li>
</ul>
</li>
<li>Click ‘Execute/Run’</li>
<li>Right click on <code class="language-plaintext highlighter-rouge">db-server</code> and choose refresh.</li>
</ul>
<p>Now, drilling down through the menus: Databases -> northwind -> Schemas -> public -> Tables displays 14 tables.</p>
<p>The tables can also be viewed on the guest machine (Ubuntu). As the postgres user, log into the northwind database and use the <code class="language-plaintext highlighter-rouge">\dt</code> command to list the tables.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postgres@db-server:~<span class="nv">$ </span>psql northwind
psql <span class="o">(</span>14.2 <span class="o">(</span>Ubuntu 14.2-1.pgdg20.04+1<span class="o">))</span>
Type <span class="s2">"help"</span> <span class="k">for </span>help.
<span class="nv">northwind</span><span class="o">=</span><span class="c"># \dt </span>
</code></pre></div></div>
<h2 id="stopstart-the-vm">Stop/Start the VM</h2>
<p>To shutdown the Ubuntu vm use the command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>multipass stop db-server
</code></pre></div></div>
<p>To restart the server use the command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>multipass start db-server
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>I now have a vm running PostgreSQL!</p>Monica GranboisI recently installed PostgreSQL on a virtual machine on my dev computer. This post describes what I did to: install a vm install PostgreSQL access PostgreSQL from the host machine via pgAdmin install a sample database into PostgreSQLHow to mock nanoid2021-11-16T00:00:00+00:002021-11-16T00:00:00+00:00http://monicagranbois.com/blog/webdev/how-to-mock-nanoid<p>This short blog post describes how to use <a href="https://jestjs.io/">Jest</a> to mock <a href="https://github.com/ai/nanoid">nanoid</a>. The nanoid function generates a unique string id. I used it to generate an id for an object. However, I wanted a stable id when unit testing the code. To accomplish this, I mocked the nanoid module and function by doing the following:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">jest</span><span class="p">.</span><span class="nx">mock</span><span class="p">(</span><span class="dl">"</span><span class="s2">nanoid</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">nanoid</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">"</span><span class="s2">1234</span><span class="dl">"</span> <span class="p">};</span>
<span class="p">});</span>
</code></pre></div></div>
<!--more-->
<p>The above code does the following:</p>
<ul>
<li><code>jest.mock("nanoid"...)</code> - This part mocks the nanoid module.</li>
<li><code>return { nanoid: () => "1234" };</code> - This part mocks the nanoid function. “1234” is returned when the nanoid function is called.</li>
</ul>
<p>Below, is a simple example of a React app using the nanoid function and a unit test mocking it.</p>
<h4 id="appjs"><strong><code class="language-plaintext highlighter-rouge">App.js</code></strong></h4>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">nanoid</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">nanoid</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">class</span> <span class="nx">Item</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">price</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">nanoid</span><span class="p">();</span> <span class="c1">//use nanoid to generate a unique id</span>
<span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">price</span> <span class="o">=</span> <span class="nx">price</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">Display</span><span class="p">({</span> <span class="nx">item</span> <span class="p">})</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span>
<span class="o"><</span><span class="nx">h2</span><span class="o">></span><span class="nx">Item</span><span class="o"><</span><span class="sr">/h2</span><span class="err">>
</span> <span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">id</span><span class="p">:</span> <span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="o"><</span><span class="sr">/p</span><span class="err">>
</span> <span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">name</span><span class="p">:</span> <span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="o"><</span><span class="sr">/p</span><span class="err">>
</span> <span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">price</span><span class="p">:</span> <span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span><span class="p">}</span><span class="o"><</span><span class="sr">/p</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">item</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Item</span><span class="p">(</span><span class="dl">"</span><span class="s2">apple</span><span class="dl">"</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">App</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">h1</span><span class="o">></span><span class="nx">Nanoid</span> <span class="nx">Unit</span> <span class="nx">Test</span> <span class="nx">Example</span><span class="o"><</span><span class="sr">/h1</span><span class="err">>
</span> <span class="o"><</span><span class="nx">Display</span> <span class="nx">item</span><span class="o">=</span><span class="p">{</span><span class="nx">item</span><span class="p">}</span> <span class="sr">/</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
<span class="k">export</span> <span class="p">{</span> <span class="nx">Display</span><span class="p">,</span> <span class="nx">Item</span> <span class="p">};</span>
</code></pre></div></div>
<h4 id="apptestjs"><strong><code class="language-plaintext highlighter-rouge">App.test.js</code></strong></h4>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">render</span><span class="p">,</span> <span class="nx">screen</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@testing-library/react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Display</span><span class="p">,</span> <span class="nx">Item</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./App</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">mock</span><span class="p">(</span><span class="dl">"</span><span class="s2">nanoid</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">nanoid</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">"</span><span class="s2">1234</span><span class="dl">"</span> <span class="p">};</span>
<span class="p">});</span>
<span class="nx">describe</span><span class="p">(</span><span class="dl">"</span><span class="s2">test the Display</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="dl">"</span><span class="s2">should display the item info</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">item</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Item</span><span class="p">(</span><span class="dl">"</span><span class="s2">Orange</span><span class="dl">"</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
<span class="nx">render</span><span class="p">(</span><span class="o"><</span><span class="nx">Display</span> <span class="nx">item</span><span class="o">=</span><span class="p">{</span><span class="nx">item</span><span class="p">}</span> <span class="sr">/></span><span class="se">)</span><span class="err">;
</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">screen</span><span class="p">.</span><span class="nx">getByText</span><span class="p">(</span><span class="sr">/id: 1234/i</span><span class="p">)).</span><span class="nx">toBeInTheDocument</span><span class="p">();</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">screen</span><span class="p">.</span><span class="nx">getByText</span><span class="p">(</span><span class="sr">/name: Orange/i</span><span class="p">)).</span><span class="nx">toBeInTheDocument</span><span class="p">();</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">screen</span><span class="p">.</span><span class="nx">getByText</span><span class="p">(</span><span class="sr">/price: 5/i</span><span class="p">)).</span><span class="nx">toBeInTheDocument</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Note: As of this writing CodeSandbox <a href="https://github.com/codesandbox/codesandbox-client/issues/513">does not support Jest mocks</a>.</p>
<h2 id="references">References:</h2>
<ul>
<li><a href="https://jestjs.io/docs/mock-functions#mocking-partials">Jest Mock Functions</a></li>
</ul>Monica GranboisThis short blog post describes how to use Jest to mock nanoid. The nanoid function generates a unique string id. I used it to generate an id for an object. However, I wanted a stable id when unit testing the code. To accomplish this, I mocked the nanoid module and function by doing the following: jest.mock("nanoid", () => { return { nanoid: () => "1234" }; });How to free up disk space for XCode Beta install2021-09-09T00:00:00+00:002021-09-09T00:00:00+00:00http://monicagranbois.com/blog/xcode/how-to-free-up-disk-space-for-xcode-beta-install<p>TL;DR - delete Time Machine local snapshots to free up space.</p>
<p>Does anyone else find installing XCode frustrating? I always need more free disk space, especially when installing a beta. This was the case when I downloaded XCode 13 Beta 5 from the Apple Developer Downloads page. When I tried to extract the xip file I got an error stating:</p>
<blockquote>
<p>“The archive XCode_13_beta_5.xip can’t be expanded because the current volume doesn’t have enough free space.”</p>
</blockquote>
<!--more-->
<p>I deleted XCode simulators and cache files to free up space with the following steps:</p>
<ul>
<li>Go to Apple icon -> About This Mac</li>
<li>Click on ‘Storage’ tab</li>
<li>Click ‘Manage’ Button</li>
<li>In the Developer menu delete:
<ul>
<li>the XCode Cache file</li>
<li>old iOS and watchOS Device support files</li>
</ul>
</li>
</ul>
<p>I also delete unused applications, log files, and music to clear up space. After much pruning, I had over 100GB free. Still, the Archive Utility app complained there was not enough free space!</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">😭😭😭 Why does XCode need so much space?! <a href="https://t.co/cowtceXF3h">pic.twitter.com/cowtceXF3h</a></p>— Monica Granbois (@mgranbois) <a href="https://twitter.com/mgranbois/status/1435299567583600645?ref_src=twsrc%5Etfw">September 7, 2021</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>This seemed odd. I tried the solution proposed in this blog post: <a href="https://medium.com/geekculture/installing-xcode-with-not-enough-disk-space-available-b96c8f17115b">Installing Xcode with “not enough disk space available”</a>. It didn’t work for me. But I suggest trying it out.</p>
<p>However, the blog post did lead me to check my disk space using the Disk Utility app (Applications -> Utilities -> Disk Utility). It reported I only had ~25GB free when the “About This Mac” storage screen reported I had ~100GB.</p>
<p>After some googling I found this question on StackExchange: <a href="https://apple.stackexchange.com/q/324440">macOS not showing the actual free space</a>. The answers said to delete Time Machine local snapshots. I did so, and <em>boom</em>, the Disk Utility app reported I had 100GB free. I tried the Archive Utility app again. This time it expanded the xip file and installed XCode Beta! Yay!</p>
<p>The commands I used to list and delete the time machine files were (from <a href="https://apple.stackexchange.com/a/352511/432763">this answer</a>):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>tmutil listlocalsnapshots /
com.apple.TimeMachine.2021-08-20-195949.local
com.apple.TimeMachine.2021-09-08-114353.local
<span class="nv">$ </span>tmutil deletelocalsnapshots 2021-08-20-195949
<span class="nv">$ </span>tmutil listlocalsnapshots /
com.apple.TimeMachine.2021-09-08-114353.local
</code></pre></div></div>
<p>Why is there a discrepancy between the free space reported by the About This Mac and the Disk Utility apps? Because <a href="https://support.apple.com/en-us/HT204015">macOS will automatically delete snapshots when space is needed</a>. So, About This Mac considers the space used by local snapshots as available. Therefore, it reported ~100GB free space. However, the Disk Utility does <strong>not</strong> consider snapshots as free space. The snapshots are using space so Disk Utility reports that space as used. So, it reported there was only ~25GB of free space.</p>
<p>The Archive Utility also does <strong>not</strong> consider the storage used by snapshot as free space. It checks for disk space before extracting the file and, in my case, detected that only ~25GB was free. By deleting the local snapshot, it was able to expand the xip file and install XCode Beta.</p>
<p><strong>References:</strong></p>
<ul>
<li><a href="https://apple.stackexchange.com/q/324440">macOS not showing the actual free space</a></li>
<li><a href="https://medium.com/geekculture/installing-xcode-with-not-enough-disk-space-available-b96c8f17115b">Installing Xcode with “not enough disk space available”</a></li>
<li><a href="https://support.apple.com/en-us/HT204015">About Time Machine local snapshots</a></li>
<li><a href="https://www.cnet.com/tech/computing/disk-utility-showing-less-available-space-than-the-finder-in-os-x/">Disk Utility showing less available space than the Finder in OS X</a></li>
</ul>Monica GranboisTL;DR - delete Time Machine local snapshots to free up space. Does anyone else find installing XCode frustrating? I always need more free disk space, especially when installing a beta. This was the case when I downloaded XCode 13 Beta 5 from the Apple Developer Downloads page. When I tried to extract the xip file I got an error stating: “The archive XCode_13_beta_5.xip can’t be expanded because the current volume doesn’t have enough free space.”Generating A New Apple Distribution Certificate2021-03-29T00:00:00+00:002021-03-29T00:00:00+00:00http://monicagranbois.com/blog/xcode/generating-a-new-distribution-certificate-<p>This post is a note to my future self about generating Apple Distribution Certificates. I received an email stating:</p>
<blockquote>
<p>Your Distribution Certificate will no longer be valid in 30 days. To generate a new certificate, sign in and visit <a href="https://developer.apple.com/account">Certificates, Identifiers & Profiles</a>.</p>
</blockquote>
<!--more-->
<p>After signing in, the Certificates, Identifiers & Profiles page will list two certificates:</p>
<ol>
<li>Apple Distribution certificate
<ul>
<li>Used to distribute apps to TestFlight and the App Store.</li>
<li>An expired certificate does not affect my apps on the App Store. However, I cannot update my apps or upload new apps with the expired certificate.</li>
</ul>
</li>
<li>Apple Development certificate
<ul>
<li>Used to run apps on devices and use app services.</li>
<li>It will have my computer’s name appended to the certificate name.</li>
</ul>
</li>
</ol>
<p>To generate a new certificate I followed the <a href="https://stackoverflow.com/a/59850970/4704303">steps from this StackOverflow answer</a>:</p>
<blockquote>
<ul>
<li>Open Xcode</li>
<li>Open Xcode Preferences (Xcode->Preferences or Cmd-,)</li>
<li>Click on Accounts</li>
<li>At the left, click on your developer ID</li>
<li>At the bottom right, click on Manage Certificates…</li>
<li>In the lower left corner, click the arrow to the right of the + (plus)</li>
<li>Select Apple Distribution from the menu</li>
</ul>
</blockquote>
<p>I also revoked the old certificate on the <a href="https://developer.apple.com/account/resources/certificates/list">Certificates, Identifiers & Profiles</a> page. I did this by clicking on the distribution certificate and then clicking the revoke button.</p>
<p>XCode manages my provisioning profile, so I did not need to manually update it.</p>
<p>References:</p>
<ul>
<li><a href="https://stackoverflow.com/a/59850970/4704303">https://stackoverflow.com/a/59850970/4704303</a></li>
<li><a href="https://developer.apple.com/support/certificates/">https://developer.apple.com/support/certificates/</a></li>
<li><a href="https://help.apple.com/xcode/mac/current/#/dev3a05256b8">https://help.apple.com/xcode/mac/current/#/dev3a05256b8</a></li>
</ul>Monica GranboisThis post is a note to my future self about generating Apple Distribution Certificates. I received an email stating: Your Distribution Certificate will no longer be valid in 30 days. To generate a new certificate, sign in and visit Certificates, Identifiers & Profiles.How To Use GitHub Actions To Deploy an 11ty Website To S32021-02-11T00:00:00+00:002021-02-11T00:00:00+00:00http://monicagranbois.com/blog/webdev/use-github-actions-to-deploy-11ty-site-to-s3<p>I use <a href="https://www.11ty.dev/">11ty</a> to generate a static website and <a href="https://aws.amazon.com/s3/">S3</a> to host it. This post describes how I set up a workflow in <a href="https://docs.github.com/en/actions">GitHub Actions</a> to build and deploy the website. The workflow uses the AWS - CLI <a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/sync.html">S3 - sync</a> command to transfer the files.</p>
<p>This post describes how to:</p>
<ul>
<li>create an AWS policy with only those permission needed by the <code class="language-plaintext highlighter-rouge">sync</code> command</li>
<li>create an <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html">AWS individual IAM user</a> with the above policy</li>
<li>create a GitHub Action workflow using the <code class="language-plaintext highlighter-rouge">sync</code> command to upload the files</li>
</ul>
<p>I will provide the policy and workflow files first for those wanting only the code. A detailed <a href="#walk-through">walkthrough</a> is provided below these files. This post assumes you already have set up an S3 bucket. If not, please see the <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html">Hosting a static website using Amazon S3</a> guide.<br />
<!--more--></p>
<h4 id="json"><strong><code class="language-plaintext highlighter-rouge">s3_sync_policy.json</code></strong></h4>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Sid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"VisualEditor0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"s3:PutObject"</span><span class="p">,</span><span class="w">
</span><span class="s2">"s3:GetObject"</span><span class="p">,</span><span class="w">
</span><span class="s2">"s3:ListBucket"</span><span class="p">,</span><span class="w">
</span><span class="s2">"s3:DeleteObject"</span><span class="p">,</span><span class="w">
</span><span class="s2">"s3:GetBucketLocation"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"arn:aws:s3:::your-bucket-name"</span><span class="p">,</span><span class="w">
</span><span class="s2">"arn:aws:s3:::your-bucket-name/*"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h4 id="yml"><strong><code class="language-plaintext highlighter-rouge">github_build_and_deploy_workflow.yml</code></strong></h4>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Build and Deploy to S3</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">build_and_deploy</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout repository</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2.3.4</span>
<span class="c1"># Uncomment if you want to specify a certain </span>
<span class="c1"># Node version. Otherwise the Node version installed</span>
<span class="c1"># on the GitHub VM will be used. For more details</span>
<span class="c1"># see: https://github.com/actions/virtual-environments </span>
<span class="c1"># - name: Setup Node.js environment</span>
<span class="c1"># uses: actions/setup-node@v2.1.4</span>
<span class="c1"># with:</span>
<span class="c1"># node-version: '15.7.0'</span>
<span class="c1"># Uncomment if your project uses dependencies</span>
<span class="c1"># - name: Install dependencies</span>
<span class="c1"># run: npm ci</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build the website</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">npx @11ty/eleventy</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Configure AWS Credentials</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">aws-actions/configure-aws-credentials@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">aws-access-key-id</span><span class="pi">:</span> <span class="s">${{ secrets.AWS_ACCESS_KEY_ID }}</span>
<span class="na">aws-secret-access-key</span><span class="pi">:</span> <span class="s">${{ secrets.AWS_SECRET_ACCESS_KEY }}</span>
<span class="na">aws-region</span><span class="pi">:</span> <span class="s">us-west-2</span> <span class="c1"># replace this with your aws-region</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload files to S3 with AWS CLI</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">aws s3 sync _site/ s3://${{ secrets.S3_BUCKET }} --delete </span>
</code></pre></div></div>
<h2 id="walk-through">Walk-through</h2>
<p>There are many menus to navigate to set this up. I’ve created a video walk through to help explain where everything is. If you want just the text write up then please skip to the <a href="#aws-setup">AWS Setup section below</a>.</p>
<div class="embed-responsive embed-responsive-16by9 my-3">
<iframe src="https://www.youtube.com/embed/h-iowIY4DCU" frameborder="0" allowfullscreen="" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
</div>
<h3 id="aws-setup">AWS Setup</h3>
<p>Using an individual IAM account with limited permissions is a best practice. Creating a new user, rather than using an existing one, means the credentials are not shared across services. If the credentials change then GitHub alone will be affected. Plus, we can restrict the user to only those permissions needed by the workflow. In this case, the account will only have permissions for the actions needed by the <code class="language-plaintext highlighter-rouge">sync</code> command. For more details on credential best practices, see the <a href="https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#credentials">configure-aws-credentials</a> documentation.</p>
<h4 id="create-a-policy">Create a policy</h4>
<p>The first step is to create a policy that will only contain the permissions needed by the <code class="language-plaintext highlighter-rouge">sync</code> command. This policy will be used during the account creation step.</p>
<p>To create the policy:</p>
<ul>
<li>Sign in to the <a href="https://console.aws.amazon.com/iam/">IAM Console</a>.</li>
<li>Goto Access Management -> Policies -> Create Policy.</li>
<li>Click the JSON tab and paste in the <a href="#json">JSON policy from above</a>.</li>
<li>Click the ‘Review’ Policy button.</li>
<li>Next, name the policy. I used S3-Sync-Policy.</li>
<li>Click the ‘Create Policy’ button.</li>
</ul>
<p>This policy does the following:</p>
<ul>
<li>Allows the following actions:
<ul>
<li><code class="language-plaintext highlighter-rouge">s3:PutObject</code>
<ul>
<li>“Grants permission to add an object to a bucket”</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">s3:GetObject</code>
<ul>
<li>“Grants permission to retrieve objects from Amazon S3”</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">s3:ListBucket</code>
<ul>
<li>“Grants permission to list some or all of the objects in an Amazon S3 bucket”</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">s3:DeleteObject</code>
<ul>
<li>“Grants permission to remove the null version of an object and insert a delete marker, which becomes the current version of the object”</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">s3:GetBucketLocation</code>
<ul>
<li>Grants permission to return the Region that an Amazon S3 bucket resides in”</li>
</ul>
</li>
</ul>
</li>
<li>Specifies the resources the actions can be applied to. Ensure you change the <code class="language-plaintext highlighter-rouge">your-bucket-name</code> value.
<ul>
<li><code class="language-plaintext highlighter-rouge">arn:aws:s3:::your-bucket-name</code>
<ul>
<li>Applied to the bucket: <code class="language-plaintext highlighter-rouge">your-bucket-name</code></li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">arn:aws:s3:::your-bucket-name/*</code>
<ul>
<li>Applied to all objects in <code class="language-plaintext highlighter-rouge">your-bucket-name</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>You can also create the policy using the Visual Editor rather than pasting in the JSON code. Note: You do not need to do both. The Visual Editor is a second way to create the policy. The video demonstrates using the Visual Editor.</p>
<h4 id="create-a-user">Create a user</h4>
<p>The next step is to create a user and apply the policy to it.</p>
<p>To create a user:</p>
<ul>
<li>Goto Access Management -> Users -> Add user</li>
<li>On the first screen:
<ul>
<li>Enter a name in the “User Name” field. I chose github-actions.</li>
<li>Select “Programmatic access” in the “Access Type” section. This user does not need “AWS Management Console access”.</li>
<li>Click the “Next: Permissions” button.</li>
</ul>
</li>
<li>On the second screen:
<ul>
<li>Click on the “Attach existing policies directly” box in the “Set permissions” section.</li>
<li>Use the search feature to find the policy created in the previous section. Select it.
<img src="/images/github-actions/iam-create-user-filter-policy.png" class="img-fluid mx-auto d-block" alt="The IAM Add User Permission screen. The search feature has been used to find the policy created previously." /></li>
<li>Click the “Next: Tags” button.</li>
</ul>
</li>
<li>On the third screen:
<ul>
<li>Optionally add a tag (or more) for the user.</li>
<li>Click the “Next: Review” button.</li>
</ul>
</li>
<li>On the fourth screen:
<ul>
<li>Review the user settings.</li>
<li>Click the “Create User” button.</li>
</ul>
</li>
<li>On the fifth screen:
<ul>
<li>This screen displays the user’s AWS credentials. These will be used with the GitHub repo. WARNING: This is the only time the credentials will be available to download. Either keep this screen open while setting up GitHub, OR click the Download button.</li>
</ul>
</li>
</ul>
<h3 id="github-setup">GitHub Setup</h3>
<h4 id="setup-aws-credentials">Setup AWS Credentials</h4>
<p>The GitHub action will need the AWS credentials in order to work. Add the credentials to your repo by doing the following:</p>
<ul>
<li>Go to your repo</li>
<li>Click Settings -> Secrets -> New Repository Secret</li>
<li>Using the credentials created in the previous section, add keys for:
<ul>
<li><code class="language-plaintext highlighter-rouge">AWS_ACCESS_KEY_ID</code></li>
<li><code class="language-plaintext highlighter-rouge">AWS_SECRET_ACCESS_KEY</code></li>
</ul>
</li>
<li>Also, add an <code class="language-plaintext highlighter-rouge">S3_BUCKET</code> key with the name of your S3 bucket.</li>
</ul>
<h4 id="create-a-github-workflow">Create a GitHub Workflow</h4>
<p>Now to create a workflow that will build the website and upload it to S3. The workflow will do the following:</p>
<ul>
<li>Use the <a href="https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions">configure-aws-credentials</a> to access the AWS access and secret keys</li>
<li>Build the website using 11ty</li>
<li>Use the <a href="https://aws.amazon.com/cli/">AWS-CLI</a> to upload the generated site to the S3 bucket</li>
</ul>
<p>To create the workflow:</p>
<ul>
<li>In your repo click the Actions link.</li>
<li>Click on the “Skip this and set up a workflow yourself” link.</li>
<li>Remove the template code and replace it with the <a href="#yml">yml file from above</a>.</li>
<li>Change the <code class="language-plaintext highlighter-rouge">aws-region</code> location to your bucket’s region.</li>
<li>Optionally, rename the file.</li>
<li>Click the “Start Commit” button.</li>
</ul>
<p>Below is a description of what each line does:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Build and Deploy to S3</span>
</code></pre></div></div>
<ul>
<li>The name of this workflow. It will show under the Actions tab in your repository. Name the workflow whatever you like.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>
</code></pre></div></div>
<ul>
<li>This is the event that will trigger this workflow. In this example, it will run on any push to the repository. See <a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows">Events that trigger workflows</a> for other options.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span>
</code></pre></div></div>
<ul>
<li>Groups together the jobs that run in this workflow (Build and Deploy to S3).</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">build_and_deploy</span><span class="pi">:</span>
</code></pre></div></div>
<ul>
<li>The name of the job. This name can be whatever you would like it to be.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
</code></pre></div></div>
<ul>
<li>In this example, the workflow will run in an ubuntu virtual environment using the latest version supported by GitHub. See <a href="https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners">Specifications for GitHub-hosted runners</a> for other options.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span>
</code></pre></div></div>
<ul>
<li>Groups together all the steps that run during the build_and_deploy job.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout repository`</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2.3.4</span>
</code></pre></div></div>
<ul>
<li>This block of code is one step. The <code class="language-plaintext highlighter-rouge">name</code> is optional. If provided it will be displayed in the output when the workflow runs. The <code class="language-plaintext highlighter-rouge">uses</code> keyword retrieves the action to run in this step. An action is a reusable piece of code. In this case, the step uses the public action actions/checkout@v2.3.4 to checkout your repository. See the <a href="https://github.com/marketplace/actions/checkout">Checkout</a> documentation for more details.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build the website</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">npx @11ty/eleventy</span>
</code></pre></div></div>
<ul>
<li>This step uses the <code class="language-plaintext highlighter-rouge">run</code> keyword to execute a command-line program using the OS’s default shell. In this case, <code class="language-plaintext highlighter-rouge">npx</code> is used to generate the 11ty website.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Configure AWS Credentials</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">aws-actions/configure-aws-credentials@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">aws-access-key-id</span><span class="pi">:</span> <span class="s">${{ secrets.AWS_ACCESS_KEY_ID }}</span>
<span class="na">aws-secret-access-key</span><span class="pi">:</span> <span class="s">${{ secrets.AWS_SECRET_ACCESS_KEY }}</span>
<span class="na">aws-region</span><span class="pi">:</span> <span class="s">us-west-2</span> <span class="c1"># replace this with your aws-region</span>
</code></pre></div></div>
<ul>
<li>This step uses the public <code class="language-plaintext highlighter-rouge">configure-aws-credentials</code> action to set up the credentials for the workflow. The <code class="language-plaintext highlighter-rouge">with</code> keyword is a map of input parameters to the action. Here, the repo’s AWS secret keys are passed to the action. See <a href="https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#credentials">“Configure AWS Credentials” Action For GitHub Actions</a> for more details on this Action.</li>
</ul>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload files to S3 with AWS CLI</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">aws s3 sync _site/ s3://${{ secrets.S3_BUCKET }} --delete </span>
</code></pre></div></div>
<ul>
<li>This step uses the AWS CLI to transfer the files generated by the “Build the website” step to the S3 bucket. The AWS CLI is installed by default on the virtual machine. See the <a href="https://github.com/actions/virtual-environments">GitHub Actions Virtual Environments</a> for more details on pre-installed tools.</li>
<li>The <a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/sync.html">S3 sync</a> command recursively copies files from the <code class="language-plaintext highlighter-rouge">_site</code> directory to the S3 bucket. The <code class="language-plaintext highlighter-rouge">--delete</code> option deletes any files in the S3 bucket that are not in the <code class="language-plaintext highlighter-rouge">_site</code> directory.</li>
</ul>
<h4 id="run-the-workflow">Run the workflow</h4>
<p>The workflow will run when a commit is pushed to the repo. You can see the output of a run by going to the ‘Actions’ menu for the repo. Here is an example of the output of the workflow.</p>
<p><img src="/images/github-actions/output.png" class="img-fluid mx-auto d-block" alt="The output after a workflow has been executed. It shows the steps run." /></p>
<p>Thank you for reading! I hope you found this useful!</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html">AWS Identity and Access Management User Guide</a></li>
<li><a href="https://stackoverflow.com/q/48894886/4704303">AWS permissions required for sync</a></li>
<li><a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/sync.html">S3 - sync</a></li>
<li><a href="https://docs.github.com/en/actions">GitHub Actions </a></li>
</ul>Monica GranboisI use 11ty to generate a static website and S3 to host it. This post describes how I set up a workflow in GitHub Actions to build and deploy the website. The workflow uses the AWS - CLI S3 - sync command to transfer the files. This post describes how to: create an AWS policy with only those permission needed by the sync command create an AWS individual IAM user with the above policy create a GitHub Action workflow using the sync command to upload the files I will provide the policy and workflow files first for those wanting only the code. A detailed walkthrough is provided below these files. This post assumes you already have set up an S3 bucket. If not, please see the Hosting a static website using Amazon S3 guide.Switching to Utterances For Comments2021-01-06T00:00:00+00:002021-01-06T00:00:00+00:00http://monicagranbois.com/blog/webdev/switching-to-utterances-for-comments-<p>I removed <a href="https://disqus.com">disqus</a> as the comments provider on my website because I do not like its privacy policy. I’m trying <a href="https://utteranc.es/">utterances</a> as the comment provider now. From the website, it’s: <em>A lightweight comments widget built on GitHub issues. Use GitHub issues for blog comments, wiki pages and more!</em></p>
<p>It was easy to install and only required two steps:</p>
<ol>
<li>Install the App into my GitHub repo via <a href="https://github.com/apps/utterances">https://github.com/apps/utterances</a>.</li>
<li>Added the following script to my blog</li>
</ol>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nx">script</span> <span class="nx">src</span><span class="o">=</span><span class="dl">"</span><span class="s2">https://utteranc.es/client.js</span><span class="dl">"</span>
<span class="nx">repo</span><span class="o">=</span><span class="dl">"</span><span class="s2">MonicaG/MonicaG.github.io</span><span class="dl">"</span>
<span class="nx">issue</span><span class="o">-</span><span class="nx">term</span><span class="o">=</span><span class="dl">"</span><span class="s2">pathname</span><span class="dl">"</span>
<span class="nx">theme</span><span class="o">=</span><span class="dl">"</span><span class="s2">github-light</span><span class="dl">"</span>
<span class="nx">crossorigin</span><span class="o">=</span><span class="dl">"</span><span class="s2">anonymous</span><span class="dl">"</span>
<span class="k">async</span><span class="o">></span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre></div></div>
<p>See <a href="https://utteranc.es/">https://utteranc.es/</a> for more information on the settings available.</p>Monica GranboisI removed disqus as the comments provider on my website because I do not like its privacy policy. I’m trying utterances as the comment provider now. From the website, it’s: A lightweight comments widget built on GitHub issues. Use GitHub issues for blog comments, wiki pages and more!TIL: Python has an assertLogs function for unit testing log statements2021-01-06T00:00:00+00:002021-01-06T00:00:00+00:00http://monicagranbois.com/blog/python/til-how-to-unit-test-a-log-statement-in-python<p>Today I learned how to unit test log statements with Python. I updated <a href="https://twitter.com/bcdevexbot">@bcdevexbot</a> to check the status of an opportunity. The bot logs an error message if it encounters an unknown status. To test this scenario I used the <a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs">assertLogs</a> function.</p>
<p>The <code class="language-plaintext highlighter-rouge">assertsLogs(logger=None, level=None)</code> tests that at least one statement is logged at the given level. The test fails if no statements are logged at that level. If statements were logged then the function returns an object containing them.</p>
<p>Below is a very simple example of using the <code class="language-plaintext highlighter-rouge">assertsLogs</code> function. The <code class="language-plaintext highlighter-rouge">logging_example.py</code> file contains the code to test. The <code class="language-plaintext highlighter-rouge">check_status(status)</code> function is a contrived example that performs some logging.</p>
<h4 id="logging_examplepy"><strong><code class="language-plaintext highlighter-rouge">logging_example.py</code></strong></h4>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">logging</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s">'demo_logger'</span><span class="p">)</span>
<span class="n">logging</span><span class="p">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="p">.</span><span class="n">DEBUG</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">check_status</span><span class="p">(</span><span class="n">status</span><span class="p">):</span>
<span class="k">if</span> <span class="n">status</span> <span class="o">==</span> <span class="s">"Open"</span><span class="p">:</span>
<span class="n">logger</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"Good status"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">logger</span><span class="p">.</span><span class="n">error</span><span class="p">(</span><span class="s">"Unknown status: {0}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">status</span><span class="p">))</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
<span class="n">check_status</span><span class="p">(</span><span class="s">"Open"</span><span class="p">)</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">test_logging_example.py</code> file contains the unit test. The <code class="language-plaintext highlighter-rouge">assertsLogs(logger=None, level=None)</code> takes two optional parameters.</p>
<p>The first parameter is the name of the logger. It defaults to the root logger if one is not provided. In the example below, the logger used in the <code class="language-plaintext highlighter-rouge">logging_example.py</code> file is used.</p>
<p>The second parameter is the log level. It defaults to INFO if a level is not provided. A different level can be specified as in the example below.</p>
<h4 id="test_logging_examplepy"><strong><code class="language-plaintext highlighter-rouge">test_logging_example.py</code></strong></h4>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">unittest</span>
<span class="kn">import</span> <span class="nn">logging_example</span>
<span class="k">class</span> <span class="nc">MyTestCase</span><span class="p">(</span><span class="n">unittest</span><span class="p">.</span><span class="n">TestCase</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">test_logging</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">assertLogs</span><span class="p">(</span><span class="s">'demo_logger'</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="s">'DEBUG'</span><span class="p">)</span> <span class="k">as</span> <span class="n">lc</span><span class="p">:</span>
<span class="n">logging_example</span><span class="p">.</span><span class="n">check_status</span><span class="p">(</span><span class="s">'Open'</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">assertEqual</span><span class="p">([</span><span class="s">'DEBUG:demo_logger:Good status'</span><span class="p">],</span> <span class="n">lc</span><span class="p">.</span><span class="n">output</span><span class="p">)</span>
<span class="n">logging_example</span><span class="p">.</span><span class="n">check_status</span><span class="p">(</span><span class="s">'Bad'</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">assertEqual</span><span class="p">([</span><span class="s">'DEBUG:demo_logger:Good status'</span><span class="p">,</span><span class="s">'ERROR:demo_logger:Unknown status: Bad'</span><span class="p">],</span> <span class="n">lc</span><span class="p">.</span><span class="n">output</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
<span class="n">unittest</span><span class="p">.</span><span class="n">main</span><span class="p">()</span>
</code></pre></div></div>
<p>To learn more see the <a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs">Python Documentation</a>.</p>Monica GranboisToday I learned how to unit test log statements with Python. I updated @bcdevexbot to check the status of an opportunity. The bot logs an error message if it encounters an unknown status. To test this scenario I used the assertLogs function.Autoscaling PDF Images On Apple watchOS2020-11-25T00:00:00+00:002020-11-25T00:00:00+00:00http://monicagranbois.com/blog/xcode/autoscaling-pdf-images-on-apple-watchos<p>In this post, I will discuss autoscaling PDF images and how to add one to a watchOS project.</p>
<p>The PDF format allows an image to scale without looking fuzzy at different sizes. This format is best used with vector artwork; think icons. Programs like <a href="https://inkscape.org/">Inkscape</a>, <a href="https://www.adobe.com/products/illustrator.html">Adobe Illustrator</a> and, <a href="https://www.vectornator.io/">Vectornator</a> can export PDF images.</p>
<p>For more details on designing a PDF image see the <a href="https://developer.apple.com/design/human-interface-guidelines/watchos/visual/image-optimization/autoscaling-pdf-images">Image Optimization</a> page in the <a href="https://developer.apple.com/design/human-interface-guidelines/watchos/overview/getting-started/">Human Interface Guidelines for watchOS</a>. It includes the scales used for the different screen sizes.</p>
<p>Setting certain flags in XCode will tell WatchKit to scale the image based on screen size. And so, you have an autoscaling PDF image. This means the project only needs one image for all screen sizes. Otherwise, the project would need multiple scaled image files.</p>
<!--more-->
<p>Once you have a PDF image, add it to the Asset Catalog in an XCode project.</p>
<p>In an XCode project:</p>
<ul>
<li>Go to Assets.xcassets</li>
<li>Right-click anywhere in the Asset Catalog Editor (the white area). This will bring up a pop-up menu.</li>
<li>Choose Image Set. This will create an empty Image Set.</li>
</ul>
<p><img src="/images/autoscalingPDFImage/createImageSet.png" class="img-fluid mx-auto d-block" alt="Image showing Assets.xcassets pop-up menu" /></p>
<p>The next step is to change some settings in the Attribute Inspector. If the Inspectors editor is not displayed, open it using the button in the top right corner of XCode.
<img src="/images/autoscalingPDFImage/inspectorToggle.png" class="img-fluid mx-auto d-block" alt="Image showing the inspector toggle." /></p>
<p>Next, mark the image for use on an Apple Watch. In the ‘Devices’ section of the Attribute Inspector:</p>
<ul>
<li>uncheck the ‘Universal’ option</li>
<li>check the ‘Apple Watch’ option</li>
<li>This will result in one 2x square for the Apple Watch.</li>
</ul>
<p><img src="/images/autoscalingPDFImage/appleWatchImageSet.png" class="img-fluid mx-auto d-block" alt="Image showing the settings for an Apple Watch Image Set" /></p>
<p>Now add the pdf image to XCode. I’m using this <a href="/images/autoscalingPDFImage/happy.pdf">happy face image</a>.</p>
<ul>
<li>Drag and drop the image onto the 2x Apple Watch Square.</li>
<li>Give the Image Set a name in the Attribute Inspector. I chose ‘Happy’.</li>
</ul>
<p><img src="/images/autoscalingPDFImage/happyImageAdded2.png" class="img-fluid mx-auto d-block" alt="Image showing the happy face pdf image added to Image Set" /></p>
<p>Next set the scaling options in the Attribute Inspector:</p>
<ul>
<li>Set the ‘Scales’ option to ‘Single Scale’</li>
</ul>
<p><img src="/images/autoscalingPDFImage/singleScale.png" class="img-fluid mx-auto d-block" alt="Image showing the scaling options" /></p>
<ul>
<li>Further down the menu, in the ‘Apple Watch’ section, change ‘Auto Scaling’ to ‘Automatic’</li>
</ul>
<p><img src="/images/autoscalingPDFImage/autoScale.png" class="img-fluid mx-auto d-block" width="524" height="160" alt="Image showing the Apple Watch options" /></p>
<p>Now, the image will scale based on the Apple Watch’s screen size!</p>
<p>An aside about the ‘Preserve Vector Data’ box in the Attribute Inspector. This appears to be for iOS apps and not watchOS apps. Checking the box means the vector data is included with an iOS app. This will allow the image to scale automatically. The option is discussed in the <a href="https://developer.apple.com/videos/play/wwdc2017/201/?time=2034">What’s New in Cocoa Touch</a> video around the 33:50 mark.</p>
<p>I experimented with both checking and unchecking the box. It did not seem to make a difference for a watchOS project. I created a <a href="https://stackoverflow.com/q/64668198/4704303">StackOverflow Question</a> about it, but as of this writing, it has not received a response. I have left the ‘Preserve Vector Data’ box unchecked.</p>
<p>Below is code demonstrating the use of the autoscaling PDF image. The result shown is from the preview canvas for 44mm and 38mm screens.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">SwiftUI</span>
<span class="kd">struct</span> <span class="kt">ContentView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kt">VStack</span> <span class="p">{</span>
<span class="kt">Image</span><span class="p">(</span><span class="s">"Happy"</span><span class="p">)</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"Hello, World!"</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">ContentView_Previews</span><span class="p">:</span> <span class="kt">PreviewProvider</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">previews</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">Group</span> <span class="p">{</span>
<span class="kt">ContentView</span><span class="p">()</span>
<span class="o">.</span><span class="nf">previewDevice</span><span class="p">(</span><span class="s">"Apple Watch Series 6 - 44mm"</span><span class="p">)</span>
<span class="kt">ContentView</span><span class="p">()</span>
<span class="o">.</span><span class="nf">previewDevice</span><span class="p">(</span><span class="s">"Apple Watch Series 3 - 38mm"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><img src="/images/autoscalingPDFImage/scaledImages2.png" class="img-fluid mx-auto d-block" alt="Image showing the scaled Happy Face Image in the Preview Canvas on a 44mm and a 38mm screens." /></p>Monica GranboisIn this post, I will discuss autoscaling PDF images and how to add one to a watchOS project. The PDF format allows an image to scale without looking fuzzy at different sizes. This format is best used with vector artwork; think icons. Programs like Inkscape, Adobe Illustrator and, Vectornator can export PDF images. For more details on designing a PDF image see the Image Optimization page in the Human Interface Guidelines for watchOS. It includes the scales used for the different screen sizes. Setting certain flags in XCode will tell WatchKit to scale the image based on screen size. And so, you have an autoscaling PDF image. This means the project only needs one image for all screen sizes. Otherwise, the project would need multiple scaled image files.