<?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: Mirko Vukušić</title>
    <description>The latest articles on DEV Community by Mirko Vukušić (@psiho).</description>
    <link>https://dev.to/psiho</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%2F415377%2Fc72890f0-8a8a-46fe-bd96-658b7e044b12.jpeg</url>
      <title>DEV Community: Mirko Vukušić</title>
      <link>https://dev.to/psiho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/psiho"/>
    <language>en</language>
    <item>
      <title>VimWiki: how to automate wikis per project folder (Neovim)</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Fri, 11 Feb 2022 00:06:21 +0000</pubDate>
      <link>https://dev.to/psiho/vimwiki-how-to-automate-wikis-per-project-folder-neovim-3k72</link>
      <guid>https://dev.to/psiho/vimwiki-how-to-automate-wikis-per-project-folder-neovim-3k72</guid>
      <description>&lt;p&gt;Neovim is grabbing more and more pieces of my workflow from other systems, and last two pieces are notes and tasks.&lt;br&gt;
Org-mode plugins are great, but focused more on tasks.&lt;/p&gt;

&lt;p&gt;However, one small thing almost made me adopt org-mode instead of VimWiki. That is ease of creating .org files anywhere, and that usually means in many of my project folders. I like to have all my project related stuff inside individual project folders, leave it or hide it from source control, backup, sync, ... I don't like scattered files.&lt;/p&gt;

&lt;p&gt;Of course, VimWiki can have multiple wikis... just add them to your config. Ah, not my preferred way of managing any list. Who wants to edit wiki config manually every time there is a new project.&lt;/p&gt;

&lt;p&gt;So this is what I did to automate it. Please note it requires Neovim (script is written in Lua), Linux (or better to say &lt;code&gt;find&lt;/code&gt; which I use for searching folders, and optionally Fzf (but this can be easily changed).&lt;/p&gt;
&lt;h2&gt;
  
  
  General idea
&lt;/h2&gt;

&lt;p&gt;... is to use &lt;code&gt;find&lt;/code&gt; to search for a wiki folder recursively in &lt;code&gt;projects_folder&lt;/code&gt;. Then, result list is used to dynamically populate VimWiki global variable containing list of wikis (&lt;code&gt;g:vimwiki_list&lt;/code&gt;) and tell VimWiki to refresh the list. Now, VimWiki's &lt;code&gt;&amp;lt;Leader&amp;gt;ws&lt;/code&gt; is populated with project wikis! Wikis defined in config are there too, so you can still manually add wikis outside of projects folder if you want.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using VimWiki's original wiki search feature
&lt;/h2&gt;

&lt;p&gt;Minimal code for the above is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- configuration
local config = {
    projectsFolder = '/home/your_user/work/', --full path without ~
    maxDepth = 3,
    ignoreFolders = { 'node_modules', '.git' },
    rootWikiFolder = '_wiki',
    wikiConfig = { syntax='markdown', ext='.md' }
}

-- store original vimwiki_list config, we will need it later
-- !!!make sure vimwiki plugin is loaded before running this!!!
if vim.g.vimwiki_list == nil then
    error('VimWiki not loaded yet! make sure VimWiki is initialized before my-projects-wiki')
else
    _G.vimwiki_list_orig = vim.fn.copy(vim.g.vimwiki_list) or {}
end

-- function to update g:vimwiki_list config item from list of subfolder names (append project wikis)
--   this way, orginal &amp;lt;Leader&amp;gt;ws will get new project wikis in the list and also keep ones from config
local function updateVimwikiList(folders)
    local new_list = vim.fn.copy(vimwiki_list_orig)
    for _, f in ipairs(folders) do
        local item = {
            path = config.projectsFolder..f,
            syntax = config.wikiConfig.syntax,
            ext = config.wikiConfig.ext
        }
        table.insert(new_list, item)
    end
    vim.g.vimwiki_list = new_list
    vim.api.nvim_call_function('vimwiki#vars#init',{})
end

-- function to search project folders for root wiki folders (returns system list)
local function searchForWikis()
    local command = 'find ' .. config.projectsFolder ..
        ' -maxdepth ' .. config.maxDepth
    if #config.ignoreFolders &amp;gt; 0 then command = command .. " \\(" end
    for _, f in ipairs(config.ignoreFolders) do
        command = command .. " -path '*/"..f.."/*' -prune"
        if next(config.ignoreFolders,_) == nil then
            command = command .. " \\) -o"
        else
            command = command .. " -o"
        end
    end
    command = command .. ' -type d -name ' .. config.rootWikiFolder
    command = command .. ' -print | '
    command = command .. ' sed s#' .. config.projectsFolder .. '##'
    local list = vim.api.nvim_call_function('systemlist', {command})
    return list
end

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

&lt;/div&gt;



&lt;p&gt;Now, you just have to call &lt;code&gt;updateVimwikiList(searchForWikis())&lt;/code&gt; somewhere to populate VimWiki with project wikis. There are so many options to do this. You can define a new command to do it manually, you can remap VimWiki's &lt;code&gt;&amp;lt;Leader&amp;gt;ws&lt;/code&gt; to execute it before displaying a list of wikis, or you can simply call it at the bottom of the script to refresh the list on Neovim start (downside of this method is that new _wiki folders will not be detected till you restart).&lt;/p&gt;

&lt;h2&gt;
  
  
  Extend it with Fzf fuzzy-find menu
&lt;/h2&gt;

&lt;p&gt;However, I took a different approach. Script is really fast, so I don't mind running it every time I'm searching for wikis. Also, I use Fzf fuzzy-finder. So I decided to feed the list to Fzf for a nice fuzzy-find menu. This will replace default VimWiki's wiki select feature which is not very nice, especially for larger number of projects.&lt;/p&gt;

&lt;p&gt;Here is the additional code, just below the above code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- wrapper for Vimwiki's goto_index() to bypass :VimWikiUISelect and use FZF instead
-- if wiki is passed to the function, index page is opened directly, bypassing FZF
function _G.ProjectWikiOpen(name)
    -- show fzf wiki search if no wiki passed
    if not name then
        local wikis = searchForWikis()
        updateVimwikiList(searchForWikis())
        for _,f in ipairs(vimwiki_list_orig) do table.insert(wikis,f.path) end
        local options = {
            sink = function(selected) ProjectWikiOpen(selected) end,
            source = wikis,
            options = '--ansi --reverse --no-preview',
            window = {
                width = 0.3,
                height = 0.6,
                border = 'sharp'
            }
        }
        vim.fn.call('fzf#run', {options})
    else
        for i, v in ipairs(vim.g.vimwiki_list) do
            if v.path == name or v.path == config.projectsFolder..name then
                vim.fn.call('vimwiki#base#goto_index',{i})
                return
            end
        end
        print("Error. Selected project wiki not found")
    end
end


-- add commands
vim.api.nvim_command([[command! -nargs=? ProjectWikiOpen lua ProjectWikiOpen(&amp;lt;f-args&amp;gt;)]])

-- add keybindings
vim.api.nvim_set_keymap("n", "&amp;lt;Leader&amp;gt;wp", ":ProjectWikiOpen&amp;lt;CR&amp;gt;", { noremap=true })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, a new &lt;code&gt;:ProjectWikiOpen&lt;/code&gt; command is registered and mapped to wp. I like this key combo better than original &lt;code&gt;&amp;lt;Leader&amp;gt;ws&lt;/code&gt;. This leaves you with a very simple workflow... just add &lt;code&gt;_wiki&lt;/code&gt; folder to any project and hit &lt;code&gt;&amp;lt;Leader&amp;gt;wp&lt;/code&gt;. Fzf menu will open and it will already be there. Just select it and VimWiki takes over.&lt;/p&gt;

&lt;h2&gt;
  
  
  todo
&lt;/h2&gt;

&lt;p&gt;1) &lt;em&gt;Finish Project/Session switcher script&lt;/em&gt; - For me, everything is a project and everything project related is in a project folder. That's why I also have a few scripts to easily manage/switch my projects in Neovim. Those integrate project sessions (using &lt;code&gt;mksession&lt;/code&gt;), automatic fuzzy-finder for projects (using &lt;code&gt;find&lt;/code&gt; to search for &lt;code&gt;.git&lt;/code&gt; folders to find roots), and Kitty terminal project launcher integrated in Dmenu which starts project environment using Kitty terminal: opens few Kitty windows and tabs with Neovim (session preloaded), LazyGit, few terminals, etc. It's almost done so hopefully I find the time to publish that too.&lt;/p&gt;

&lt;p&gt;2) &lt;em&gt;Finish synchronization with mobile phone&lt;/em&gt; - I want my wikis on the go too. This is almost done with Syncthing client on my laptop, server (Nextcloud) and mobile phone. Wikis are pushed and synchronized on all devices. Currently trying Obsidian Android app to read/manage wikis on the mobile side.&lt;/p&gt;

&lt;p&gt;3) &lt;em&gt;Integrate VimWiki tasks and taskwarrior (or similar)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>vim</category>
    </item>
    <item>
      <title>How to toggle Polybar on Spectrwm tiling wm</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Wed, 09 Feb 2022 02:42:45 +0000</pubDate>
      <link>https://dev.to/psiho/how-to-toggle-polybar-on-spectrwm-tiling-wm-3o3d</link>
      <guid>https://dev.to/psiho/how-to-toggle-polybar-on-spectrwm-tiling-wm-3o3d</guid>
      <description>&lt;p&gt;Just a short one on this problem with combination of Spectrwm tiling window manager and Polybar. Managed to sort it out after hours without finding a solution on the web. So here is my hack.&lt;/p&gt;

&lt;p&gt;First, the problem... Once you replace the bar that comes with Spectrwm, with Polybar, you have to reserve a portion on top of the screen for Polybar. That's OK, but toggling Polybar leaves that reserved space empty so again you don't get a full screen. On the other hand, if you don't reserve that space, Polybar is displayed above your windows which is also not nice.&lt;/p&gt;

&lt;p&gt;My solution is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Leave original bar ON with minimal settings, and remove reserved space for Polybar if you had it configured before (&lt;code&gt;region&lt;/code&gt; in config). Only Spectrwm bar config items you need are:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bar_enabled     = 1
bar_border_width    = 0
bar_font        = &amp;lt;any font, just adjust size so bar is as large as Polybar&amp;gt;
bar_format      = ' # &amp;lt;--- something minimal so bar is almost not visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;make sure your original keybinding to toggle Spectrwm bar is still active (Super+b)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;install &lt;code&gt;xdotool&lt;/code&gt;. Miniature program that can send fake keys to Xorg&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;write shell script, I named it &lt;code&gt;fullscreen-toggle.sh&lt;/code&gt; and put it somewhere (&lt;code&gt;mine are in ~/scripts/&lt;/code&gt;). Also make it executable:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh
xdotool key super+b
polybar-msg cmd toggle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Script is really simple. First, xdotool toggles Spectrwm bar by sending fake keypresses. Then we use polybar-msg to send remote command to Polybar to toggle.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add new keybinding to Spectrwm conf (&lt;code&gt;~/.spectrwm.comf&lt;/code&gt;). I used MOD+Shift+f:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;program[toggle_bars] = ~/scripts/fullscreen-toggle.sh
bind[toggle_bars] = MOD+Shift+f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and we're done! Restart Spectrwm (&lt;code&gt;Super+q&lt;/code&gt;) and try it out. Make sure your keybinding was free before assigning it. Also, try running shell script from terminal for test.&lt;/p&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>Am I the only one frustrated with oh-my-zsh?</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Tue, 11 May 2021 11:19:45 +0000</pubDate>
      <link>https://dev.to/psiho/am-i-the-only-one-frustrated-with-oh-my-zsh-ne0</link>
      <guid>https://dev.to/psiho/am-i-the-only-one-frustrated-with-oh-my-zsh-ne0</guid>
      <description>&lt;p&gt;I feel lonely :) Help me stop this feeling I'm the only one. Just switched from bash to zsh, and while I completely get why somebody would want to use oh-my-zsh, and I don't have anything against the project, it just seems like everybody is using it!? It's difficult to Google for any zsh configuration help/tutorials without ending up with oh-my-zsh. Again, it's not that oh-my-zsh itself bothers me, it bothers me that all my search results are infested with it :)&lt;/p&gt;

</description>
      <category>question</category>
      <category>linux</category>
      <category>shell</category>
      <category>zsh</category>
    </item>
    <item>
      <title>Changed my Linux "stack" after 10 years</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Sun, 04 Oct 2020 14:58:59 +0000</pubDate>
      <link>https://dev.to/psiho/changed-my-linux-stackx-after-10-years-4l3n</link>
      <guid>https://dev.to/psiho/changed-my-linux-stackx-after-10-years-4l3n</guid>
      <description>&lt;p&gt;After 10 years and a simple post on dev.to I completely changed my Linux distro and tools.&lt;/p&gt;

&lt;p&gt;I wonder if you did something like this and what was your change? I want to hear long time Linux users, not a fresh ones. I'm interested about a change, not tools themselves.&lt;/p&gt;

&lt;p&gt;My change was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mint/Cinnamon to Arch/Xmonad (after 10 years)&lt;/li&gt;
&lt;li&gt;WebStorm to Vim (after 2 years, was Sublime before)&lt;/li&gt;
&lt;li&gt;MC to Vifm (after probably 30 years and Norton Commander in DOS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... Or in general: Mouse to keyboard :)&lt;/p&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>Do not update Linux Mint 19 !!! Bricked systems reported</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Tue, 29 Sep 2020 20:49:04 +0000</pubDate>
      <link>https://dev.to/psiho/do-not-update-linux-mint-19-bricked-systems-reported-p8b</link>
      <guid>https://dev.to/psiho/do-not-update-linux-mint-19-bricked-systems-reported-p8b</guid>
      <description>&lt;p&gt;Well, it seems Linux Mint 19 update from today bricks a lot of systems! My friend's one included. More info on their thread &lt;a href="https://forums.linuxmint.com/viewtopic.php?f=46&amp;amp;t=331520"&gt;https://forums.linuxmint.com/viewtopic.php?f=46&amp;amp;t=331520&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>Uncle Bob's clean code lecture</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Fri, 07 Aug 2020 22:22:37 +0000</pubDate>
      <link>https://dev.to/psiho/uncle-bob-s-clean-code-lecture-19d5</link>
      <guid>https://dev.to/psiho/uncle-bob-s-clean-code-lecture-19d5</guid>
      <description>&lt;p&gt;&lt;a href="https://youtu.be/7EmboKQH8lM"&gt;https://youtu.be/7EmboKQH8lM&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just had to reshare this. Amazing lecture by Uncle Bob, so mush wider than a title says.&lt;/p&gt;

</description>
      <category>codequality</category>
    </item>
    <item>
      <title>How to migrate AWS Cognito User Pool and DynamoDB Table (script)</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Sun, 12 Jul 2020 10:59:27 +0000</pubDate>
      <link>https://dev.to/psiho/how-to-migrate-aws-cognito-user-pool-and-dynamodb-table-script-3188</link>
      <guid>https://dev.to/psiho/how-to-migrate-aws-cognito-user-pool-and-dynamodb-table-script-3188</guid>
      <description>&lt;p&gt;Yesterday I had to migrate my AWS app to a new setup on CloudFormation. Code is easy but Cognito User Pool and DynamoDB table(s) took some time to investigate. Here is my experience and a solution (for small to medium apps).&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating Cognito User Pool
&lt;/h2&gt;

&lt;p&gt;Note that there are several ways to migrate Cognito User pool, two main ones are described here: &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-users.html"&gt;https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-users.html&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1) Using "seamless" Lambda migration
&lt;/h3&gt;

&lt;p&gt;Basically you keep both pools active; app is normally linked to the new pool but on every failed login Lambda will check user against the old pool and migrate it to a new pool seamlessly (if exists and credentials are ok).&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2) Import your users from CSV file
&lt;/h3&gt;

&lt;p&gt;Simple but not seamless. Using Cognito console you can import users from CSV file, but they'll be put into RESET_REQUIRED status. Then, your app needs to be changed a bit to forward users with this status to "forgot password" workflow. There they will re-confirm their email and it's done. Getting a CSV file to import is another story as Cognito console does not export users (yet).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I chose option 2 and import CSV?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;because it is much simpler for small to medium apps&lt;/li&gt;
&lt;li&gt;because I didn't want to keep both user pools active for that long (my users migrate very slowly)&lt;/li&gt;
&lt;li&gt;because my database links some data with username (which is a login alias for email) so in "seamless" method there was a chance that a completely new user would "steal" old username (although with new email) exposing that data to him.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exporting users from Cognito user pool is still not available in console, but a &lt;a href="https://github.com/rahulpsd18/cognito-backup-restore"&gt;cognito-backup-restore&lt;/a&gt; does the job very well. However, it does not (yet) create proper CSV for import through console. Some simple Node scripting and it's done (link to code at the end).&lt;br&gt;
Note that cognito-backup-restore does have a restore feature too, but it sends out emails to users which was not what I wanted. I wanted this migration silent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating DynamoDB table(s)
&lt;/h2&gt;

&lt;p&gt;Again, there are several options to migrate DynamoDB. Amazon suggests &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBPipeline.html"&gt;AWS Data Pipeline&lt;/a&gt; but for small to medium tables that's an overkill. Many other variations and methods to do this exist online, free or commercial. Also many different JSON formats are used in diffrente tools which require some parsing. At the end I decided to use AWS CLI commands wrapped in a Node script to automate the process.&lt;/p&gt;

&lt;p&gt;step 1: &lt;code&gt;aws dynamodb scan&lt;/code&gt; downloads DynamoDB JSON&lt;br&gt;
step 2: &lt;code&gt;aws dynamodb put-item&lt;/code&gt; in a loop sends all records to a new table, one by one&lt;/p&gt;

&lt;p&gt;&lt;code&gt;batch-write&lt;/code&gt; was something to think about (it can do 25 records in a batch) but my table was small enough to do it this way.&lt;/p&gt;

&lt;p&gt;Complete code is on GitHub: &lt;a href="https://github.com/psiho/cognito-dynamodb-migrate"&gt;cognito-dynamodb-migrate&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
      <category>cognito</category>
      <category>devops</category>
    </item>
    <item>
      <title>Javascript performance benchmarking (aka JSPerf) with JSBench.me</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Sun, 12 Jul 2020 00:24:15 +0000</pubDate>
      <link>https://dev.to/psiho/javascript-performance-benchmarking-aka-jsperf-with-jsbench-me-2ojn</link>
      <guid>https://dev.to/psiho/javascript-performance-benchmarking-aka-jsperf-with-jsbench-me-2ojn</guid>
      <description>&lt;p&gt;Disclaimer: this is my app and the post is mostly announcement&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jsbench.me"&gt;https://jsbench.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Long time ago, JSPerf was down for some time and since I was learning React and JSPerf was server-side app, I decided to build my own, SPA version in React. Fast forward to today... JSBench.me is out of beta. v1.0.0 is released.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it for?
&lt;/h2&gt;

&lt;p&gt;Well, if you use(d) JSPerf, then explanation is not needed. If not, shortest description would be that it is Javasscript playground (like JSBin, JSFiddle) but for testing performance of your snippets. In the background Benchmark.js runs all tests and JSBench.me is UI for it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Question? Do you benchmark your Javascript code?
&lt;/h4&gt;

&lt;p&gt;I got a habit of doing it, more as a learning tool than a development tool. Once you realize one way of doing something is faster, you adopt it for future.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's under the hood?
&lt;/h2&gt;

&lt;p&gt;As said, it is an SPA, built with React and Mobx. Back office is relatively complex mix of AWS services: Cloud Formation, API Gateway, Cognito, Lambda and DynamoDB. So, complete project is in the cloud depending on AWS services. V1 also uses AWS SAM with Cloud Formation to build complete infrastructure from yaml files. Whole project tought me a lot and will produce several posts here, one of which is already &lt;a href="https://dev.to/psiho/build-deploy-all-aws-resources-as-easy-as-npm-run-deploy-prod-aws-sam-worflow-4d83"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features and what's new in this version
&lt;/h2&gt;

&lt;p&gt;Other than standard test running, you always could save/publish and link your test suites. Voting is also possible. Last version (other than complete rewrite to latest libraries and Typescript) adds many UI helpers that users requested like: sorting tests (drag&amp;amp;drop or buttons), running individual tests in the suite, dark &amp;amp; light theme, manual&amp;amp;auto resizing of code editors, etc. More in the &lt;a href="https://github.com/psiho/jsbench-me/wiki/Change-log"&gt;change log&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>benchmark</category>
      <category>react</category>
    </item>
    <item>
      <title>Search and sort of articles are confusing to me</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Sat, 27 Jun 2020 12:26:32 +0000</pubDate>
      <link>https://dev.to/psiho/search-and-sort-of-articles-are-confusing-to-me-424m</link>
      <guid>https://dev.to/psiho/search-and-sort-of-articles-are-confusing-to-me-424m</guid>
      <description>&lt;p&gt;Reading dev.to for a few days now. After reading some fresh posts, often I want to check some topics of interest using a search. But my first impression is that it just doesn't feel right. Mostly its about getting old articles in results, so quickly I have nothing more to read on the topic, ot it just appears like it, or Im doing it wrong.&lt;/p&gt;

&lt;p&gt;Here is a story of my first impressions...&lt;/p&gt;

&lt;p&gt;Day 1)&lt;br&gt;
I'm interested in "aws" topics. I know there's #aws tag for it, let me find it. Menu - tags... Oh, list is huge, lets try to search for a tag... Nope. search above searches the site, not tags.&lt;br&gt;
Ok, lets go alphabetically... Nope, they're not alphabetically sorted. Well, I'm not browsing all those. Let's search for '#aws', yes! Got it. Wait ... Too many articles are 2 years old. It cannot be that. Open first article, click on #aws tag...  Aha, completely different thing.&lt;/p&gt;

&lt;p&gt;Day 2)&lt;br&gt;
I've read top #aws articles, let's dig deeper. I want to see articles on AWS Sam. Hmm, can I search by tag? Let's search for: #aws sam&lt;br&gt;
Good... But hey... Again top articles are 2 years old. There must be more on dev.to on Sam!? Scrolling down I realize there are newer articles deep down. Uh, don't want that. I've read all articles at the top, how am I going to continue reading tomorrow? I don't have a clue how those articles are sorted. It seems that list in search results and list created by clicking on the #tag are sorted differently, and there's no "sort by date" in any. I don't see the other way but to go through complete list every day, not to miss new ones but continuing to read beyond the point where I stopped yesterday.&lt;/p&gt;

&lt;p&gt;There must be something about this on dev.to. Let me search for: #devto sort&lt;br&gt;
Whaaat!? I get list of people in results? Confused. Maybe this is just displayed when there are no search result to display? Let me try searching for: 'gshdyejsjsieheh'...&lt;br&gt;
Oh, now i get "no results match that query". So what's with that people in search results. Still confused &lt;/p&gt;

&lt;p&gt;Day 3)&lt;br&gt;
Dev.to just appears like it has less (and older) content than it really has. At least in my use scenario. I mostly use homepage and 'latest' and a few tags I subscribed to. I feel like I'm missing something.&lt;/p&gt;

&lt;p&gt;What am I getting\doing wrong?&lt;/p&gt;

</description>
      <category>devto</category>
      <category>question</category>
    </item>
    <item>
      <title>Build &amp; deploy all AWS resources as easy as: npm run deploy-prod (AWS Sam worflow)</title>
      <dc:creator>Mirko Vukušić</dc:creator>
      <pubDate>Tue, 23 Jun 2020 21:07:25 +0000</pubDate>
      <link>https://dev.to/psiho/build-deploy-all-aws-resources-as-easy-as-npm-run-deploy-prod-aws-sam-worflow-4d83</link>
      <guid>https://dev.to/psiho/build-deploy-all-aws-resources-as-easy-as-npm-run-deploy-prod-aws-sam-worflow-4d83</guid>
      <description>&lt;p&gt;After manually managing my AWS resources I finally decided to use Sam templates and put everything in Git. Easy deploying to prod and test stages was also a requirement. Here is a short overview of some issues I ran into and how I resolved them. It is by no means a detailed step by step guide, but just a quick overview with some problems I ran into and solutions.&lt;/p&gt;

&lt;p&gt;Disclaimer: This is my first experience with AWS Sam. I probably did some things sub-optimal or wrong. Idea is to share and get comments from those with more experience. It may also help those starting with AWS Sam, to speed up the process and save a week I spend investigating.&lt;br&gt;
Also note that current Sam version I'm using is 0.52.0 and I'm on Linux.&lt;/p&gt;
&lt;h2&gt;
  
  
  sam-cli installation
&lt;/h2&gt;

&lt;p&gt;First requirement is sam-cli. I'm not getting into details of installing and configuring it, but you can find it &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will also have to add sam-cli user an permissions on AWS IAM. This can be a slow process of trial and error. Depending on your resources, and motivation to close down permissions, settings will vary. Basically you try to publish, read error, add permission, rinse and repeat. Many tutorials just tell you to use admin account but I don't like that. Here's my IAM role policy but you'll need to adapt it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:DeleteUserPool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:AddCustomAttributes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:UntagRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:TagRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:CreateRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:AttachRolePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:PutRolePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudformation:CreateChangeSet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:DELETE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:DescribeUserPool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:DetachRolePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudformation:DescribeStackEvents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:DeleteRolePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:PATCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudformation:DescribeChangeSet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudformation:ExecuteChangeSet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:GetRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:UpdateUserPoolClient"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:DeleteRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:ListTagsForResource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudformation:DescribeStacks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:CreateUserPoolClient"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"apigateway:POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:UpdateUserPool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:GetRolePolicy"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cognito-idp:&amp;lt;region&amp;gt;:&amp;lt;AccountID&amp;gt;:userpool/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;AccountID&amp;gt;:role/&amp;lt;MyApp&amp;gt;*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudformation:eu-west-1:&amp;lt;AccountID&amp;gt;:stack/jsbenchme*/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudformation:eu-west-1:&amp;lt;AccountID&amp;gt;:stack/aws-sam-cli-managed-default/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudformation:&amp;lt;region&amp;gt;:aws:transform/Serverless-2016-10-31"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:apigateway:&amp;lt;region&amp;gt;::/restapis/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:apigateway:&amp;lt;region&amp;gt;::/restapis"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cognito-idp:CreateUserPool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cloudformation:GetTemplateSummary"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"events:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:events:iam::&amp;lt;AccountID&amp;gt;:rule/MyApp-*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lambda:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:iam:&amp;lt;region&amp;gt;:&amp;lt;AccountID&amp;gt;:function:&amp;lt;MyLambdaFunction&amp;gt;-*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Organizing folders and files
&lt;/h2&gt;

&lt;p&gt;I decided to put my AWS Serverless app templates into &lt;code&gt;AWS&lt;/code&gt; subfolder of my project. Sam will create folder structure like this.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;sam init&lt;/code&gt; and choose AWS Quick Start Templates, runtime (mine was Node) and Hello World Example. Folder structure like this will be created by Sam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS
 |-- events -&amp;gt; where events for testing go
 |-- hello-world -&amp;gt; where your code goes (Lambda in my case)
 `- template.yaml -&amp;gt; Sam template to deploy resources through CloudFormation
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First, I renamed hello-world with &lt;code&gt;Lambda&lt;/code&gt; and put my Lambda code there, deleted some files too.&lt;br&gt;
Next step, which might not be as obvious at start, is splitting up &lt;code&gt;template.yaml&lt;/code&gt; in different files. If you have many services (especially API), file becomes unmanageable very fast. so I created folder &lt;code&gt;./templates&lt;/code&gt; for my templates expecting to merge them easily when the time comes to build.&lt;/p&gt;
&lt;h3&gt;
  
  
  Issue #1 - merging AWS Sam templates
&lt;/h3&gt;

&lt;p&gt;It turns out there were several issues here. First about ability of Sam to merge files, then with some references requiring resources to be in the same template and last (but not least for sure) a lot of issues I had in my API Gateway definition yaml with references to other properties. More on that later. Enough to say for now that I solved all those issues with a small tool &lt;code&gt;json-refs&lt;/code&gt;. So, I just did &lt;code&gt;npm i -D json-refs&lt;/code&gt;, split template in multiple files, put them in &lt;code&gt;./templates&lt;/code&gt; folder and now my entry point template &lt;code&gt;./templates/main.yaml&lt;/code&gt; looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;My App&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyAppLambdaRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LambdaAPIhandler.yaml#/Role'&lt;/span&gt;
  &lt;span class="na"&gt;MyAppLambdaAPIhandler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LambdaAPIhandler.yaml#/Handler'&lt;/span&gt;
  &lt;span class="na"&gt;MyAppDynamoDB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DynamoDB.yaml'&lt;/span&gt;
  &lt;span class="na"&gt;MyAppAPIGateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIGateway.yaml#/APIGateway'&lt;/span&gt;
  &lt;span class="na"&gt;MyAppFunctionLambdaPermission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APIGateway.yaml#/LambdaInvocationPermission'&lt;/span&gt;
  &lt;span class="na"&gt;MyAppCognitoUserPool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CognitoUserPool.yaml#/Pool'&lt;/span&gt;
  &lt;span class="na"&gt;MyAppCognitoClient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CognitoUserPool.yaml#/Client'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is much neater and manageable. But to make it work, we need to setup script to use &lt;code&gt;json-refs&lt;/code&gt; and merge those files into &lt;code&gt;./template.yaml&lt;/code&gt;. I decided to stick with Bash...&lt;/p&gt;

&lt;h2&gt;
  
  
  json-refs build script
&lt;/h2&gt;

&lt;p&gt;We need to point json-refs to &lt;code&gt;./templates/main.yaml&lt;/code&gt; and it will render all those &lt;code&gt;$ref&lt;/code&gt;s and produce merged &lt;code&gt;./template.yaml&lt;/code&gt; for Sam to use.&lt;br&gt;
I started by creating another folder &lt;code&gt;./templates/build&lt;/code&gt;, (also put it in &lt;code&gt;.gitignore&lt;/code&gt;). Then created a Bash script &lt;code&gt;./build-deploy.sh&lt;/code&gt; which started like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] deleting build files'&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./template/build/&lt;span class="k"&gt;*&lt;/span&gt;

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] copying templates to build folder'&lt;/span&gt;
    &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./template/&lt;span class="k"&gt;*&lt;/span&gt;.yaml ./template/build/

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] runing resolver on files in build folder and saving template to ./template.yaml'&lt;/span&gt;
    ./node_modules/.bin/json-refs resolve ./template/build/main.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./template.yaml

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] cleaning up template.yaml'&lt;/span&gt;
    &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/T00:00:00.000Z//g'&lt;/span&gt; ./template.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It's all quite self-explanatory, except maybe the last line where I remove &lt;code&gt;T00:00:00.000Z&lt;/code&gt; which gets appended to version-dates of the Sam template. This might be unnecessary,  but I like to keep thing as they are in AWS docs and versions don't have timestamps there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing AWS resource definition yaml files
&lt;/h2&gt;

&lt;p&gt;Again, relatively simple. AWS Docs are your best friend. It goes slow but steady. Some issues along the way though...&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue #2 - json-refs complains about custom tags like !Ref, !Sub...
&lt;/h3&gt;

&lt;p&gt;Very easy fixable once you find out that &lt;code&gt;!Ref&lt;/code&gt; can be replaced with &lt;code&gt;Ref:&lt;/code&gt;, &lt;code&gt;!Sub&lt;/code&gt; with &lt;code&gt;Fn:Sub&lt;/code&gt; and &lt;code&gt;!GetAtt&lt;/code&gt; with &lt;code&gt;Fn::GetAtt&lt;/code&gt;. They are identical but your new build script will not complain. Just don't forget colon (&lt;code&gt;:&lt;/code&gt;) after those.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue #3 - API Gateway definition gets huge and has repetitive entries,  in some cases (inside &lt;code&gt;DefinitionBody&lt;/code&gt;) Ref does not work
&lt;/h3&gt;

&lt;p&gt;My biggest fear was to write yaml for all API Gateway paths and methods, knowing how much time I spent on it in AWS Gateway UI. Exporting Swagger and pasting it in template required some changes. Some references were broken and I failed to make them work. It might be possible, but since I already had json-refs in place, I decided to use that, it seemed easier. At the end, it turned out writing API in yaml was easier. So much stuff can be reused, just define bits and pieces within &lt;code&gt;components&lt;/code&gt; in your yaml and reuse them with &lt;code&gt;$ref&lt;/code&gt;. I've put individual method responses to components, lambda invocation properties, even complete repetitive response objects for many paths. Just as an illustration, it can look like this now (shortened!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;APIGateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Api&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyAPIGateway&lt;/span&gt;
    &lt;span class="na"&gt;DefinitionBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.0.1"&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/me"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;x-amazon-apigateway-integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/x-amazon-apigateway-integrations/default-integration"&lt;/span&gt;
            &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/responses/defaultResponseGroup"&lt;/span&gt;

&lt;span class="c1"&gt;# components parsed by json-refs&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;defaultResponseGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;404"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;404&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;response"&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/headers/default"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/content/default"&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;response"&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/headers/default"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/content/default"&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Empty"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Of course, my definition has many paths, methods and responses, but putting some default properties and grouping them into &lt;code&gt;components&lt;/code&gt; made it really easy to write API, easier than using API Gateway UI!&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue #4 - circular references
&lt;/h3&gt;

&lt;p&gt;Well, I let Sam create unique names for my resources so their &lt;code&gt;Arn&lt;/code&gt;s are created when deployed. But I need LambdaFunction to reference  LambdaFunctionRole.Arn and LambdaFunctionRole to reference LambdaFunction.Arn. Sam cannot create both at the same time, they depend on each other. AWS docs have a good &lt;a href="https://aws.amazon.com/blogs/infrastructure-and-automation/handling-circular-dependency-errors-in-aws-cloudformation/"&gt;article&lt;/a&gt; on this and on more complex scenarios, but my solution was to name my LambdaFunction manually, so I can refer to it by name. It can be useful later in deployment process for other things too. Read on. Also check &lt;code&gt;DependsOn&lt;/code&gt; CloudFormation attribute docs, this will help ordernig creation of resources to avoid similar issues in referrences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating build &amp;amp; deployment to separate stages (test and prod)
&lt;/h2&gt;

&lt;p&gt;My initial impression was that I'll use &lt;code&gt;stageName&lt;/code&gt; and &lt;code&gt;alias&lt;/code&gt; properties of some resources to build &lt;code&gt;prod&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt; stages but it turns out best practice is to separate stages into different stacks. Actually, many people suggest using different accounts but that's not always practical. So, I decided that each stage will have it's own stack. I did use &lt;code&gt;stageName&lt;/code&gt; too to make it even more clear which resource belongs where, although it seems unnecessary because we will make &lt;code&gt;stackName&lt;/code&gt; also contain stage name to differentiate different stacks.&lt;br&gt;
General idea is to use single code base for both prod and test stage, but before the build process I need to be able to decide which stage I'm going to build and publish. So there must be a way to make template(s) dynamic, to make certain properties changeable from command line. CloudFormation parameters and Sam's &lt;code&gt;--parameter-overrides&lt;/code&gt; argument is what we need. First, I added parameterss to my &lt;code&gt;./templates/main.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;parAccountId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS Account Id&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;parRegion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS Region to deploy to&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;parStackName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The name of the stack&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myStack-test"&lt;/span&gt;
  &lt;span class="na"&gt;parStage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stage,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod"&lt;/span&gt;
    &lt;span class="na"&gt;AllowedValues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;... then those parameters need to be used in templates in place of hard-coded ones (i.e. &lt;code&gt;${parStackName}&lt;/code&gt;).&lt;br&gt;
Now is time to (maybe) make a step back. I'm actually editing this article after running into problems because I haven't done so. So, that step is to make naming convetion for your resources. Trust me, it will make your life easier if you just name all the resources manually, following that naming convention. Mine is:&lt;br&gt;
&lt;code&gt;&amp;lt;project-name&amp;gt;-&amp;lt;resource-name&amp;gt;-&amp;lt;stack&amp;gt;-&amp;lt;stage&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Go through all templates and implement this dynamic naming. Don't foget your sam-cli IAM role that you had to create at the beginning. This naming convention is going to make it a whole lot easier to avoid issues and resorting to '*'. Remember that Lambda function? Now part of its definition will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;Fn::Sub: "MyProject-MyAppLambdaAPIhandler-${parStackName}-${parStage}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, we can use &lt;code&gt;sam build&lt;/code&gt; and &lt;code&gt;sam deploy&lt;/code&gt; to build and deploy our resources. We will use --parameter-overrides argument to send those parameters to CloudFormation and they will affect template there (note! local &lt;code&gt;template.yaml&lt;/code&gt; will NOT be changed based on params! This will happen on CloudFormation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;    sam build &lt;span class="nt"&gt;--build-dir&lt;/span&gt; .aws-sam/build &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--template&lt;/span&gt; ./template.yaml &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="nv"&gt;parAccountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;accountId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parRegion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStackName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    sam deploy &lt;span class="nt"&gt;--template&lt;/span&gt; ./template.yaml &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--no-fail-on-empty-changeset&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--confirm-changeset&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--stack-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="nv"&gt;parAccountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;accountId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parRegion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStackName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Of course, we will put this in our &lt;code&gt;./build-deploy.sh&lt;/code&gt; script which will take arguments and build those parameters with them, from command line. You can check it in the final script source at the end of the article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue #5 - Sam requires S3 bucket to deploy
&lt;/h3&gt;

&lt;p&gt;Well, actually this S3 bucket is made automatically, but ONLY in "guided" deploy mode which is activated by &lt;code&gt;--guided&lt;/code&gt; argument of &lt;code&gt;sam-build&lt;/code&gt;. In this mode, sam asks questions about some deployment parameters and creates S3 bucket auto-magically, then stores your choices to &lt;code&gt;./samconfig.yaml&lt;/code&gt; for later (add this file to .gitignore!). So, in our final &lt;code&gt;./build-deploy.sh&lt;/code&gt; script we will check for existence of this file and if it is not there, we will start deploy in "guided" mode. Just check the final code for details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue #6 - Sam ignores --capabilities argument when in "guided" mode
&lt;/h3&gt;

&lt;p&gt;We need &lt;code&gt;--capabilities CAPABILITY_NAMED_IAM&lt;/code&gt; in my implementation and we do pass it to &lt;code&gt;sam build&lt;/code&gt; as an argument. However, if it is in "guided" mode, it ignores &lt;code&gt;--capabilities&lt;/code&gt; argument and asks you to enter the value manually (or uses wrong default). "Workaround" is also implemented in final &lt;code&gt;./build-deploy.sh&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outputs
&lt;/h2&gt;

&lt;p&gt;You will want to know some parameters created by AWS, which are not known before deploy is done. In example your API Gateway URI. We don't want anything hard-coded so there must be a way to fetch those after build &amp;amp; deploy. &lt;code&gt;Options&lt;/code&gt; to the rescue. Again in &lt;code&gt;./main.yaml&lt;/code&gt; I added this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stage&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;URL"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;Fn::Sub: "https://${MyAppAPIGateway}.execute-api.${parRegion}.amazonaws.com/${parStage}/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will create &lt;code&gt;apiURL&lt;/code&gt; output visible in AWS CloudFormation UI. But how to access it from our build script and make it available to other parts of our app (in my case frontend React app)? Again, with Sam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs[?OutputKey==`apiURL`].OutputValue'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;All we need to do now is to include this in our &lt;code&gt;./build-deploy.sh&lt;/code&gt; and add some code to save those values into a config file readable to our app (don't forget to .gitignore it too). Also don't forget to somehow differentiate same variables for different stages! Because ApiURL for prod stage and test stage should both be saved. You can see it all in final &lt;code&gt;./build-deploy.sh&lt;/code&gt; code where I also added more code to offer some help and arguments management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;###################################################################################&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Bash script do build &amp;amp; deploy to different stages&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;###################################################################################&lt;/span&gt;

show-help &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo 
    echo&lt;/span&gt; &lt;span class="s2"&gt;"--------------------------------"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" AWS deployment script "&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--------------------------------"&lt;/span&gt;
    &lt;span class="nb"&gt;echo 
    echo&lt;/span&gt; &lt;span class="s2"&gt;"usage: deploy.sh [options] &amp;lt;action&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo 
    echo&lt;/span&gt; &lt;span class="s2"&gt;"ACTION:"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    build           Builds AWS app and stores template to ./template.yaml (Default action)"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    deploy          Deploys AWS app to CloudFormation using ./template.yaml"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    build-deploy    Builds and deploys in single step"&lt;/span&gt;
    &lt;span class="nb"&gt;echo 
    echo&lt;/span&gt; &lt;span class="s2"&gt;"OPTIONS:"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  mandatory arguments:"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    --accountId=   AWS account id to deploy to CloudFormation with"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    --region=       AWS region to deploy to"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    --stackName=   stack name"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    --stage=STAGE  stack stage name to deploy to"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"                   STAGE is one of: prod, test"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;

check-arguments &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# action arg default&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;action &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"build"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# check empty mandatory args&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$accountId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$region&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stackName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error! Mandatory arguments missing"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="c"&gt;# check format of args&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error! --stage needs to be one of: prod, test"&lt;/span&gt;
            &lt;span class="nb"&gt;exit &lt;/span&gt;3
        &lt;span class="k"&gt;else
            return
        fi
    fi&lt;/span&gt;
    &lt;span class="c"&gt;# output error specs&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$accountId&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" --accountId is a mandatory argument"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$region&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" --region is a mandatory argument"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$stackName&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" --stackName is a mandatory argument"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$stage&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" --stage is a mandatory argument"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="o"&gt;}&lt;/span&gt;

aws-build &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] deleteing build files'&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./template/build/&lt;span class="k"&gt;*&lt;/span&gt;

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] copying templates to build folder'&lt;/span&gt;
    &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./template/&lt;span class="k"&gt;*&lt;/span&gt;.yaml ./template/build/

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] runing resolver on files in build folder and saving template to ./template.yaml'&lt;/span&gt;
    ./node_modules/.bin/json-refs resolve ./template/build/main.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./template.yaml

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[build] cleaning up template.yaml'&lt;/span&gt;
    &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/T00:00:00.000Z//g'&lt;/span&gt; ./template.yaml
&lt;span class="o"&gt;}&lt;/span&gt;

sam-build &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    sam build &lt;span class="nt"&gt;--build-dir&lt;/span&gt; .aws-sam/build &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--template&lt;/span&gt; ./template.yaml &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="nv"&gt;parAccountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;accountId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parRegion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStackName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

sam-deploy &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# check if samconfig.toml exists. If not, run --guided to autocreate S3 bucket and remember it&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"./samconfig.toml"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;isGuided&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;else
            &lt;/span&gt;&lt;span class="nv"&gt;isGuided&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--guided"&lt;/span&gt;
            &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"This is the first deploy, Sam will be started in --guided mode.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
                    NOTE! &lt;/span&gt;&lt;span class="se"&gt;\n\&lt;/span&gt;&lt;span class="s2"&gt;
                    Please accept default params except those: &lt;/span&gt;&lt;span class="se"&gt;\n\&lt;/span&gt;&lt;span class="s2"&gt;
                        Allow SAM CLI IAM role creation [Y/n]:   Select N! &lt;/span&gt;&lt;span class="se"&gt;\n\&lt;/span&gt;&lt;span class="s2"&gt;
                        Capabilities [['CAPABILITY_IAM']]:   Enter: CAPABILITY_NAMED_IAM &lt;/span&gt;&lt;span class="se"&gt;\n\&lt;/span&gt;&lt;span class="s2"&gt;
                    This is to avoid a bug(?) in Sam which (only on guided run) uses default capabilities instead of ones passed as an argument. &lt;/span&gt;&lt;span class="se"&gt;\n\&lt;/span&gt;&lt;span class="s2"&gt;
                    You can also leave defaults and (after erroring out) rerun deployment again. Secontd time it will use passed arguments and run ok. &lt;/span&gt;&lt;span class="se"&gt;\n\&lt;/span&gt;&lt;span class="s2"&gt;
                    &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Press ENTER to continue."&lt;/span&gt;
    &lt;span class="k"&gt;fi

    &lt;/span&gt;sam deploy &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;isGuided&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--template&lt;/span&gt; ./template.yaml &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--no-fail-on-empty-changeset&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--confirm-changeset&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--stack-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="nv"&gt;parAccountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;accountId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parRegion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStackName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;parStage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
              &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM
&lt;span class="o"&gt;}&lt;/span&gt;

save-outputs &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"getting outputs and storing them in configs..."&lt;/span&gt;
    &lt;span class="nv"&gt;apiURL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs[?OutputKey==`apiURLprod`].OutputValue'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;cognitoClientId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="nt"&gt;--stack-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs[?OutputKey==`cognitoClientId`].OutputValue'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$apiURL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cognitoClientId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error! Error getting output params &lt;/span&gt;&lt;span class="nv"&gt;$apiURL&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cognitoClientId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;5
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"apiURL": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$apiURL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'", "cognitoClientId": "'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cognitoClientId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./options-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.json
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="nt"&gt;--accountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;accountId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;#*=&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;#*=&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="nt"&gt;--stackName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;stackName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;#*=&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="nt"&gt;--stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;#*=&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="c"&gt;#allowed stage names are limited in template.yaml param definition, not here&lt;/span&gt;
        &lt;span class="p"&gt;;;&lt;/span&gt;
        build&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
        deploy&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
        build-deploy&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"build-deploy"&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;echo 
            echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: invalid argument: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Use --help for list of arguments"&lt;/span&gt;
            show-help
            &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;esac
    &lt;/span&gt;&lt;span class="nb"&gt;shift
&lt;/span&gt;&lt;span class="k"&gt;done

&lt;/span&gt;check-arguments
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running &lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt; command of stack: &lt;/span&gt;&lt;span class="nv"&gt;$stackName&lt;/span&gt;&lt;span class="s2"&gt;, stage: &lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt; to region: &lt;/span&gt;&lt;span class="nv"&gt;$region&lt;/span&gt;&lt;span class="s2"&gt; using AWS accountID: &lt;/span&gt;&lt;span class="nv"&gt;$accountId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"build"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;then
        &lt;/span&gt;aws-build
        sam-build
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"deploy"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;then
        &lt;/span&gt;sam-deploy
        save-outputs
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"build-deploy"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;then
        &lt;/span&gt;aws-build
        sam-build
        sam-deploy
        save-outputs
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now all that is left is to use our script in package.json to build&amp;amp;deploy (and/or other scenarios) like:&lt;br&gt;
&lt;code&gt;./build-deploy.sh --accountId=012345678 --region=eu-west-1 --stackName=MyApp-test --stage=test build-deploy&lt;/code&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>sam</category>
      <category>serverless</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
