<?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: ThinkNimble</title>
    <description>The latest articles on DEV Community by ThinkNimble (@thinknimble).</description>
    <link>https://dev.to/thinknimble</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%2Forganization%2Fprofile_image%2F2068%2Fd74fd3c0-f7de-49b9-b0a4-d30c51d2b102.png</url>
      <title>DEV Community: ThinkNimble</title>
      <link>https://dev.to/thinknimble</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thinknimble"/>
    <language>en</language>
    <item>
      <title>Automatically Caption Your Videos with Whisper and ffmpeg</title>
      <dc:creator>William Huster</dc:creator>
      <pubDate>Sat, 12 Aug 2023 14:00:00 +0000</pubDate>
      <link>https://dev.to/thinknimble/automatically-caption-your-videos-with-whisper-and-ffmpeg-3mmi</link>
      <guid>https://dev.to/thinknimble/automatically-caption-your-videos-with-whisper-and-ffmpeg-3mmi</guid>
      <description>&lt;p&gt;Big video handlers like YouTube and even Slack use AI to automatically caption videos that you upload. This is a great win for accessibility, and the AI is extremely accurate. It sometimes stumbles on uncommon words and bad audio signals, but these can be easily fixed by a human. I think most would agree that the benefit and cost savings of generating 95% accurate captions greatly outweighs the cost of having none at all!&lt;/p&gt;

&lt;p&gt;So at ThinkNimble we were talking about how we could build this feature ourselves using open source tools. I came up with this suggestion that uses &lt;a href="https://github.com/openai/whisper"&gt;OpenAI's Whisper&lt;/a&gt; and the venerable &lt;a href="https://www.ffmpeg.org/"&gt;&lt;code&gt;ffmpeg&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are two steps to this (1) generate captions from the video as an SRT file, and (2) bundle the captions with the video.&lt;/p&gt;

&lt;p&gt;Whisper in my experience is incredibly good at audio transcription. And it can directly transcribe mp4 files to SRT format. You don't have to extract the audio from the video or anything like that. Once you have an SRT file, &lt;code&gt;ffmpeg&lt;/code&gt; can easily bundle an mp4 and SRT file. I've tried this on some personal videos, and the results have been near perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Here is my project directory on GitHub. The README includes installation instructions, reproduced here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/whusterj/whisper-transcribe"&gt;https://github.com/whusterj/whisper-transcribe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only two hard dependencies are the &lt;code&gt;ffmpeg&lt;/code&gt; system package and the &lt;code&gt;openai-whisper&lt;/code&gt; Python package.&lt;/p&gt;

&lt;p&gt;This installation has been tested with &lt;code&gt;Python 3.10.12&lt;/code&gt; on Ubuntu 20.02. It should work on other Platforms as well, and OpenAI says Whisper should work with all Python versions 3.9 to 3.11.&lt;/p&gt;

&lt;p&gt;First, you will need &lt;code&gt;ffmpeg&lt;/code&gt; on your system, if you don't have it already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on Ubuntu or Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ffmpeg

&lt;span class="c"&gt;# on MacOS using Homebrew (https://brew.sh/)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other platforms, see the [Whisper GitHub repo][1].&lt;/p&gt;

&lt;p&gt;Now you can install the python requirements. Create a virtual environment, activate it, and pip install from requirements.txt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv/
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you encounter errors during installation, you may need to install &lt;code&gt;rust&lt;/code&gt; on your platform as well. If my requirements.txt fails to install for some reason, try just installing the &lt;code&gt;openai-whisper&lt;/code&gt; package - this should install its Python sub-dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;openai-whisper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Do It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Use &lt;code&gt;whisper&lt;/code&gt; to generate an SRT transcription of the video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;whisper infile.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; small.en &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--language&lt;/span&gt; English &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'srt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tested this on a two minute video and it took less than 30 seconds to produce a complete transcription. This output is a subtitle file for your video - an SRT file. SRT is a plaintext file format, so you can edit it with your word processor of choice. Open the SRT, and you will see that each line of the file contains start and end timecodes and the caption text. This can be interpreted by video players to display the caption at the right time in sync with the video.&lt;/p&gt;

&lt;p&gt;I used the &lt;code&gt;small.en&lt;/code&gt; language model, which is about 400MB to download. It may be less accurate than the larger models, but in my experience it does a really good job. The option &lt;code&gt;-f 'srt'&lt;/code&gt; specifies that you want an SRT file, but you have your choice of text output format.&lt;/p&gt;

&lt;p&gt;You may want to quickly review the SRT file for any misinterpreted words or names&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Use &lt;code&gt;ffmpeg&lt;/code&gt; to add the SRT as a subtitle track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; infile.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-i&lt;/span&gt; infile.srt &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-c&lt;/span&gt;:s mov_text &lt;span class="se"&gt;\&lt;/span&gt;
       outfile.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This took less than 2 seconds to run. It doesn't have to re-render the video, it just bundles the SRT alongside the video track inside of the mp4 container.&lt;/p&gt;

&lt;p&gt;You will need a player like VLC that can show you the subtitle tracks. It is also possible to use ffmpeg to "burn in" the subtitles from your SRT file, but this requires re-rendering the whole video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate It All!
&lt;/h2&gt;

&lt;p&gt;The above would be the "by hand" procedure, but perhaps you can see how easily this process might be automated in bulk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4RmkEBB1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images.williamhuster.com/posts/2023-08-12-closed-captioning-data-pipeline-sketch.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4RmkEBB1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images.williamhuster.com/posts/2023-08-12-closed-captioning-data-pipeline-sketch.jpg" alt="Automatic Video Captioning Pipeline Sketch" width="800" height="459"&gt;&lt;/a&gt;&lt;br&gt;
The idea would be that you can drop the mp4 files you want subtitled into an S3 bucket. Then you'd have a scheduler script detect them and queue up jobs for one or more workers. This isn't strictly necessary, but would allow you to fan out the process to as many workers as you like to process more videos faster. The jobs would run the above commands and produce captioned video files automatically, which are then saved back to S3 somewhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  Show Captions in HTML without Embedding
&lt;/h2&gt;

&lt;p&gt;If you're captioning videos for viewing in web browsers, there's an even easier way.&lt;/p&gt;

&lt;p&gt;After my initial write-up, I learned that it's also possible to attach the track to the video using HTML5's built-in &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tags. This cuts out step two above, meaning you won't have to use ffmpeg to embed the track in the video file.&lt;/p&gt;

&lt;p&gt;In this case, you'll want to generate a &lt;code&gt;.vtt&lt;/code&gt; file instead of the &lt;code&gt;.srt&lt;/code&gt; above. Fortunately, Whisper supports this format, too!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;whisper infile.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; small.en &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--language&lt;/span&gt; English &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'vtt'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've generated your captions, you can use them with the HTML5 video player like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"myvideo.mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"myvideo.mp4"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;track&lt;/span&gt;
    &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"English"&lt;/span&gt;
    &lt;span class="na"&gt;kind=&lt;/span&gt;&lt;span class="s"&gt;"captions"&lt;/span&gt;
    &lt;span class="na"&gt;srclang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/path/to/captions.vtt"&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>How to automatically create and destroy Heroku Apps using Bitbucket Pipelines</title>
      <dc:creator>Corey Sutphin</dc:creator>
      <pubDate>Mon, 30 Mar 2020 15:03:19 +0000</pubDate>
      <link>https://dev.to/thinknimble/how-to-automatically-create-and-destroy-heroku-apps-using-bitbucket-pipelines-2509</link>
      <guid>https://dev.to/thinknimble/how-to-automatically-create-and-destroy-heroku-apps-using-bitbucket-pipelines-2509</guid>
      <description>&lt;p&gt;You may have heard of &lt;a href="https://www.heroku.com/platform" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;, a service that greatly simplifies the process of building, deploying, and hosting your web applications. If you've already used Heroku, then you know that deploying is as easy as running &lt;code&gt;git push heroku master&lt;/code&gt;. In 2016 Heroku launched a feature called &lt;a href="https://devcenter.heroku.com/articles/github-integration-review-apps" rel="noopener noreferrer"&gt;Review Apps&lt;/a&gt;, which are temporary, disposable apps that are created whenever a pull request is opened. They integrate directly with your Github repository, and are created when the pull request is opened and destroyed when it is closed. They provide you with a temporary and isolated environment to test your changes in, which becomes very useful in situations where multiple changes are being committed to a repository at once.  If you are using Bitbucket as your version control platform then unfortunately Review Apps are not available to you, however with a little effort we can mimic their functionality. In this guide we will learn how to use the Heroku CLI along with Bitbucket Pipelines to listen for pull requests and programatically create and destroy copies of your application!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that for the rest of this guide when we say app, we are referring to a Heroku app.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Read This If...
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;You are an individual who wants the ability to host multiple versions of an application with different versions at one time.&lt;/li&gt;
&lt;li&gt;You are a team working on multiple features at once, and you want to be able to test the different features in isolation of each other.&lt;/li&gt;
&lt;li&gt;If you were looking to use Review Apps for your project, but are using Bitbucket!&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Over at &lt;a href="https://www.thinknimble.com/" rel="noopener noreferrer"&gt;ThinkNimble&lt;/a&gt; we needed a tool to dynamically spin up versions of our applications containing new features to test them in isolation. Because Review Apps are not integrated with Bitbucket, we had to get creative and chose to create a script that creates an app when a pull request is opened and destroys that app when the pull request is merged. We use Bitbucket Pipelines, a CI/CD platform that integrates directly with your repository, to hook into the pull-request lifecycle of Bitbucket. Pipelines allows you to run services such as automated testing or deployments whenever branches are updated or pull requests are created. These services are configured using a YAML file included at the root of your Bitbucket repository called &lt;code&gt;bitbucket-pipelines.yml&lt;/code&gt;. We also utilize the Heroku CLI through a bash script to interact with the Heroku API for managing apps. At the end of the guide we share the full &lt;code&gt;bitbucket-pipelines.yml&lt;/code&gt; configuration file and bash script necessary to get this automation configured.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;A Heroku API Key with the ability to create and delete apps. You can generate an API key for yourself on your &lt;a href="https://dashboard.heroku.com/account" rel="noopener noreferrer"&gt;Heroku account page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A Bitbucket repository(repo) with &lt;a href="https://bitbucket.org/blog/an-introduction-to-bitbucket-pipelines" rel="noopener noreferrer"&gt;Bitbucket Pipelines&lt;/a&gt; enabled.&lt;/li&gt;
&lt;li&gt;A Bitbucket app password with the ability to read pull request and commit history for your repo. This can be generated by going to the Settings screen of your Bitbucket account. From there go to Access Management -&amp;gt; App Passwords and generate a new password with at least &lt;code&gt;read&lt;/code&gt; permissions for repositories and pull-requests. Concatenate this with your username in the format &lt;code&gt;BitbucketUsername:AppPassword&lt;/code&gt; and save it somewhere secure you can access later. This will be your authentication string to use the Bitbucket API.&lt;/li&gt;
&lt;li&gt;A pre-configured Heroku app for your application.&lt;/li&gt;
&lt;li&gt;Some previous experience with bash is recommended, but not required.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Setting up Heroku and Bitbucket
&lt;/h1&gt;

&lt;p&gt;We need to allow Pipelines to access our Heroku API key, Heroku app name, and Bitbucket authentication string when running our various automation scripts. In the settings section of your repo navigate to Settings -&amp;gt; Pipelines -&amp;gt; Repository Variables. Add a &lt;code&gt;HEROKU_API_KEY&lt;/code&gt;, &lt;code&gt;HEROKU_APP_NAME&lt;/code&gt;, and &lt;code&gt;BITBUCKET_AUTH_STRING&lt;/code&gt; repository variable, filling in the appropriate values. Make sure you check 'Secured' when adding sensitive variables so their values will be masked from any log outputs.&lt;/p&gt;

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

&lt;p&gt;This will allow us to access these variables in our pipeline scripts as &lt;code&gt;$HEROKU_API_KEY&lt;/code&gt;. Since these repository variables are set as environment variables, this &lt;code&gt;HEROKU_API_KEY&lt;/code&gt; variable will also be used to authenticate our requests through the Heroku CLI.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bitbucket Pipelines
&lt;/h1&gt;

&lt;p&gt;First let's take a look at a bare-bones Pipelines configuration file that pushes code to an app whenever the &lt;code&gt;master&lt;/code&gt; branch of our repository is updated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bitbucket-pipelines.yml&lt;/span&gt;

&lt;span class="c1"&gt;# Change this to your project's node version&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:12.0.0&lt;/span&gt;

&lt;span class="na"&gt;clone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy the master branch to our app.&lt;/span&gt;
&lt;span class="c1"&gt;# Pulls the Heroku API Key and the name of the app from our&lt;/span&gt;
&lt;span class="c1"&gt;# configured environment variables.&lt;/span&gt;
&lt;span class="na"&gt;herokuAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;herokuAppDeployment&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;Deploy Heroku App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master -f&lt;/span&gt;

&lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Deploy the master branch to our app whenever it is updated&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*herokuAppDeployment&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We are not going to be covering all of the set-up of a Pipelines configuration file, but at a high-level we are defining a pipeline for the &lt;code&gt;master&lt;/code&gt; branch that will run our &lt;code&gt;herokuAppDeployment&lt;/code&gt; script. This script will push the branch up to the git remote of our app. We now need to add a new pipeline to this file that will run on pull requests created/updated from a branch. This pipeline will create a new app for testing the changes added in the branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;reviewAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppDeployment&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;Deploy a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Deploy Review App"&lt;/span&gt;

&lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Whenever a pull-request is opened or updated, create/update a Heroku app for the pull-request.&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&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="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*reviewAppDeployment&lt;/span&gt;

  &lt;span class="c1"&gt;# Deploy the master branch to our app whenever it is updated&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*herokuAppDeployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the structure set up to hook into opened and updated pull requests, let's start fleshing out the &lt;code&gt;reviewAppDeployment&lt;/code&gt; script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;reviewAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppDeployment&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;Deploy a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Download the Heroku CLI&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl https://cli-assets.heroku.com/install.sh | sh&lt;/span&gt;
    &lt;span class="c1"&gt;# Format the name of the app that will be created.&lt;/span&gt;
    &lt;span class="c1"&gt;# EX: With a branch named 'chatbot-ui' and a repo called 'thinknimble', this will become 'thinknimble-chatbot-ui'.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEW_APP_NAME=$BITBUCKET_REPO_SLUG-$BRANCH_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# Export the Heroku authentication token so it can be accessed in other scripts&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export HEROKU_API_KEY&lt;/span&gt;
    &lt;span class="c1"&gt;# If an app exists for this branch, push the new code up.&lt;/span&gt;
    &lt;span class="c1"&gt;# Else, create a copy of the main app and push the new branch to it.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;heroku apps:info -a $NEW_APP_NAME &amp;amp;&amp;amp; git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$NEW_APP_NAME.git $BITBUCKET_BRANCH:master -f || /bin/bash copy-app.sh $HEROKU_APP_NAME $NEW_APP_NAME $BITBUCKET_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first few steps of the function we download the Heroku CLI and construct the name of the app to be created from the name of the branch and the name of the repo. This name is important as it is later retrieved in order to find the app to delete when the pull request is merged. The final line of the function is where the magic happens. We first poll Heroku to see if the app exists. If it does, push the new code up to the app. If it doesn't, we need to create the new app by copying our main app. We have put the collection of commands required to create a copy of our main app in a bash script called &lt;code&gt;copy-app.sh&lt;/code&gt; located at the root of our application. You ou could include all of the commands in the .yml file itself.&lt;/p&gt;

&lt;p&gt;We now have a script that will run whenever a pull request is opened from a branch, and either update the app created from the branch or create a new app for the branch if it doesn't already exist .In the next section we'll cover the contents of this script and how it creates the new apps.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating Apps Using the Heroku CLI
&lt;/h1&gt;

&lt;p&gt;When a pull request is opened for a new branch, we use Heroku CLI commands to create a new app, copy over the configuration and data from the main app, and deploy the source code of the branch. &lt;strong&gt;Some of the steps in this script may not be needed for your specific application, or you may need to add additional steps to configure other services.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy-app.sh&lt;/span&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Script to copy an existing app and deploy a specific branch to it.&lt;/span&gt;
&lt;span class="c"&gt;# 1. Creates the new app&lt;/span&gt;
&lt;span class="c"&gt;# 2. Copies over all environment variables from existing app&lt;/span&gt;
&lt;span class="c"&gt;# 3. Provisions a new Postgres database and copies the rows over from the existing app&lt;/span&gt;
&lt;span class="c"&gt;# 4. Deploys the specified branch to the new app&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# USAGE:&lt;/span&gt;
&lt;span class="c"&gt;#   ./copy-app.sh parentApp newApp branchName&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="c"&gt;# Defines the parameters passed in to the script. Requires the name of the&lt;/span&gt;
&lt;span class="c"&gt;# parent app, the name of the new app, and the name of the branch to push to the new app to build.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nv"&gt;parentAppName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;newAppName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;branchName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;

&lt;span class="c"&gt;# Create the new app&lt;/span&gt;
heroku create &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Add the standard buildpacks for your app. Here we add NodeJS first, then python.&lt;/span&gt;
heroku buildpacks:add heroku/nodejs &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
heroku buildpacks:add heroku/python &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Copy environemnt variables from parent app to new app&lt;/span&gt;
&lt;span class="nv"&gt;configFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"config-temp.txt"&lt;/span&gt;
heroku config &lt;span class="nt"&gt;-s&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;$parentAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$configFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$configFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; | xargs heroku config:set &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"./&lt;/span&gt;&lt;span class="nv"&gt;$configFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Remove the DATABASE_URL environment variable as it will be set when we provision a new database&lt;/span&gt;
heroku config:unset DATABASE_URL &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Update the ALLOWED_HOSTS and CURRENT_DOMAIN environment variables&lt;/span&gt;
heroku config:set &lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;.herokuapp.com"&lt;/span&gt; &lt;span class="nv"&gt;CURRENT_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;.herokuapp.com"&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;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Provision new Postgres database&lt;/span&gt;
heroku addons:create heroku-postgresql:hobby-dev &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Push the source code of the branch up to the new app to build&lt;/span&gt;
git push https://heroku:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEROKU_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;@git.heroku.com/&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;.git &lt;span class="nv"&gt;$branchName&lt;/span&gt;:master &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Copy rows from parent app's database&lt;/span&gt;
heroku pg:copy &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$parentAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;::DATABASE_URL DATABASE_URL &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--confirm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of these configuration steps may not make sense for your specific application. Include any additional steps needed to configure your feature app in this file. While we use a pre-existing main app to copy over environment variables and testing data, you could include the testing configuration and seed data for the application in the source code itself to remove this dependency. This main app also does not need to be an app running the production version of your application as well, it could be a staging app itself where changes are vetted before being pushed to the production environment.&lt;/p&gt;

&lt;h1&gt;
  
  
  Destroying Apps
&lt;/h1&gt;

&lt;p&gt;At this point we have a Pipelines configuration file and bash script that will create apps for us to test changes on whenever a pull request is opened, but as of now you will have to go in and manually delete the new apps once the PR from the branch is merged. To fix this we have implemented a script that uses the Bitbucket API to retrieve the pull request when it is merged, find the name of the created app for the branch, and subsequently delete that app. The only modifications needed are to the &lt;code&gt;bitbucket-pipelines.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bitbucket-pipelines.yml&lt;/span&gt;

&lt;span class="c1"&gt;# Change this to your project's node version&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:12.0.0&lt;/span&gt;

&lt;span class="na"&gt;clone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;


&lt;span class="c1"&gt;# Deploy the master branch to our app.&lt;/span&gt;
&lt;span class="c1"&gt;# Pulls the Heroku API Key and the name of the app from our&lt;/span&gt;
&lt;span class="c1"&gt;# configured environment variables.&lt;/span&gt;
&lt;span class="na"&gt;herokuAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;herokuAppDeployment&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;Deploy Heroku App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master -f&lt;/span&gt;

&lt;span class="na"&gt;reviewAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppDeployment&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;Deploy a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Download the Heroku CLI&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl https://cli-assets.heroku.com/install.sh | sh&lt;/span&gt;
    &lt;span class="c1"&gt;# Format the name of the app that will be created.&lt;/span&gt;
    &lt;span class="c1"&gt;# EX: With a branch named 'chatbot-ui' and a repo called 'thinknimble', this will become 'thinknimble-chatbot-ui'.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEW_APP_NAME=$BITBUCKET_REPO_SLUG-$BRANCH_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# Export the Heroku authentication token so it can be accessed in other scripts&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export HEROKU_API_KEY&lt;/span&gt;
    &lt;span class="c1"&gt;# If an app exists for this branch, push the new code up.&lt;/span&gt;
    &lt;span class="c1"&gt;# Else, create a copy of the main app and push the new branch to it.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;heroku apps:info -a $NEW_APP_NAME &amp;amp;&amp;amp; git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$NEW_APP_NAME.git $BITBUCKET_BRANCH:master -f || /bin/bash copy-app.sh $HEROKU_APP_NAME $NEW_APP_NAME $BITBUCKET_BRANCH&lt;/span&gt;

&lt;span class="na"&gt;reviewAppCleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppCleanup&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;Cleanup a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl https://cli-assets.heroku.com/install.sh | sh&lt;/span&gt;
    &lt;span class="c1"&gt;# Pull the commit that triggered this script, which for a pull request will be the merge commit. &lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the associated pull-request to get the review app name to delete.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BRANCH=`curl -X GET -G "https://$BITBUCKET_AUTH_STRING@api.bitbucket.org/2.0/repositories/thinknimble/$BITBUCKET_REPO_SLUG/pullrequests" --data-urlencode 'q=merge_commit.hash="'"$BITBUCKET_COMMIT"'"' | python -c "import json,sys;obj=json.loads(sys.stdin.read());value=obj.get('values', [{}])[0].get('source', {}).get('branch', {}).get('name', '');print(value)"`&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BRANCH_NAME=`echo $BRANCH | egrep "(.*)" -o`&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FEATURE_APP_NAME=$BITBUCKET_REPO_SLUG-$BRANCH_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# Poll Heroku to see if an app with the constructed name exists.&lt;/span&gt;
    &lt;span class="c1"&gt;# If it does than delete it, if it does not then just return.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;heroku apps:info -a $FEATURE_APP_NAME &amp;amp;&amp;amp; heroku apps:destroy -a $FEATURE_APP_NAME -c $FEATURE_APP_NAME || echo "No feature server to cleanup"&lt;/span&gt;

&lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Whenever a pull-request is opened or updated, create/update a Heroku app for the pull-request.&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&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="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*reviewAppDeployment&lt;/span&gt;
  &lt;span class="c1"&gt;# Deploy the master branch to our main app whenever it is updated.&lt;/span&gt;
  &lt;span class="c1"&gt;# Then, update any Review Apps associated with the pull-request merged into `master`.&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*herokuAppDeployment&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*revewAppCleanup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a closer look at this &lt;code&gt;review                                                       AppCleanup&lt;/code&gt; script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;reviewAppCleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppCleanup&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;Cleanup a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl https://cli-assets.heroku.com/install.sh | sh&lt;/span&gt;
    &lt;span class="c1"&gt;# Pull the commit that triggered this script, which for a pull request will be the merge commit. &lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the associated pull-request to get the review app name to delete.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BRANCH=`curl -X GET -G "https://$BITBUCKET_AUTH_STRING@api.bitbucket.org/2.0/repositories/thinknimble/$BITBUCKET_REPO_SLUG/pullrequests" --data-urlencode 'q=merge_commit.hash="'"$BITBUCKET_COMMIT"'"' | python -c "import json,sys;obj=json.loads(sys.stdin.read());value=obj.get('values', [{}])[0].get('source', {}).get('branch', {}).get('name', '');print(value)"`&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BRANCH_NAME=`echo $BRANCH | egrep "(.*)" -o`&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FEATURE_APP_NAME=$BITBUCKET_REPO_SLUG-$BRANCH_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# Poll Heroku to see if an app with the constructed name exists.&lt;/span&gt;
    &lt;span class="c1"&gt;# If it does than delete it, if it does not then just return.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;heroku apps:info -a $FEATURE_APP_NAME &amp;amp;&amp;amp; heroku apps:destroy -a $FEATURE_APP_NAME -c $FEATURE_APP_NAME || echo "No feature server to cleanup"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We utilize Bitbucket's REST API to retrieve the pull-request associated with a specific commit. This commit is provided by Pipelines, and when the pipeline was triggered by a pull-request being merged the commit will be the merge commit. Once we have the name of the pull-request, we construct the name of the created app, looking for the name that was constructed in the &lt;code&gt;reviewAppDeployment&lt;/code&gt; script. We poll Heroku to see if an app with the constructed name exists. If it does, destroy it. Otherwise just print out that there is no app to cleanup and return.&lt;/p&gt;

&lt;p&gt;Now you can merge you pull-request, check Heroku, and see that the created app has been taken down! We now have an automated pipeline to create apps for pull requests, and then destroy them when the branch is merged.&lt;/p&gt;

&lt;h1&gt;
  
  
  Security Considerations
&lt;/h1&gt;

&lt;p&gt;Since you are destroying apps, you have to ensure that no important apps are accidentally destroyed. The script has a very specific naming convention for naming these review apps, so the risk of deleting an app would require an unfortunate naming error. In our organization we've nullified this risk by using a Heroku account that only has permission to delete apps from a separate Heroku team created solely for the purpose of housing these temporary review apps and other playground environments. You can modify the commands in the &lt;code&gt;copy-app.sh&lt;/code&gt; command above to create apps only for a specific team by including the &lt;code&gt;-t team-name&lt;/code&gt; flag to the commands to create the app and associated pipeline. The scripts in &lt;code&gt;bitbucket-pipelines.yml&lt;/code&gt; will simply return if the user does not have access to delete or create an app. See the &lt;a href="https://devcenter.heroku.com/categories/command-line" rel="noopener noreferrer"&gt;Heroku CLI documentation&lt;/a&gt; for more configuration options.&lt;/p&gt;

&lt;h1&gt;
  
  
  Looking Ahead
&lt;/h1&gt;

&lt;p&gt;While an integrated solution such as Review Apps for Github would have been preferred, this combination of Bitbucket Pipelines and Heroku CLI commands does the job for our team. There are some known limitations and improvements we would like to make going forward.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heroku app names are limited to a maximum of 30 characters, so if your constructed app name is longer than that the script will error out. We could store a shortened slug or just simply truncate names when they reach the character limit.&lt;/li&gt;
&lt;li&gt;Some Heroku app configuration must be done manually, for instance copying over scheduled background tasks. This means that for a basic application in our stack this is fully automatic, but becomes more manual as app architecture becomes more complicated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;You can test this for your own application by including this Pipelines configuration file and bash script at the root of your repository, and opening up a pull request. You should now see a new app named after your branch! Let me know if you were able to use the information in this post to automate the creation and deletion of apps for Heroku.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bitbucket-pipelines.yml&lt;/span&gt;

&lt;span class="c1"&gt;# Place this at the root of your repository&lt;/span&gt;

&lt;span class="c1"&gt;# Change this to your project's node version&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:12.0.0&lt;/span&gt;

&lt;span class="na"&gt;clone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;


&lt;span class="c1"&gt;# Deploy the master branch to our app.&lt;/span&gt;
&lt;span class="c1"&gt;# Pulls the Heroku API Key and the name of the app from our&lt;/span&gt;
&lt;span class="c1"&gt;# configured environment variables.&lt;/span&gt;
&lt;span class="na"&gt;herokuAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;herokuAppDeployment&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;Deploy Heroku App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master -f&lt;/span&gt;

&lt;span class="na"&gt;reviewAppDeployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppDeployment&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;Deploy a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Download the Heroku CLI&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl https://cli-assets.heroku.com/install.sh | sh&lt;/span&gt;
    &lt;span class="c1"&gt;# Format the name of the app that will be created.&lt;/span&gt;
    &lt;span class="c1"&gt;# EX: With a branch named 'chatbot-ui' and a repo called 'thinknimble', this will become 'thinknimble-chatbot-ui'.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEW_APP_NAME=$BITBUCKET_REPO_SLUG-$BRANCH_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# Export the Heroku authentication token so it can be accessed in other scripts&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export HEROKU_API_KEY&lt;/span&gt;
    &lt;span class="c1"&gt;# If an app exists for this branch, push the new code up.&lt;/span&gt;
    &lt;span class="c1"&gt;# Else, create a copy of the main app and push the new branch to it.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;heroku apps:info -a $NEW_APP_NAME &amp;amp;&amp;amp; git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$NEW_APP_NAME.git $BITBUCKET_BRANCH:master -f || /bin/bash copy-app.sh $HEROKU_APP_NAME $NEW_APP_NAME $BITBUCKET_BRANCH&lt;/span&gt;

&lt;span class="na"&gt;reviewAppCleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;reviewAppCleanup&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;Cleanup a Review App&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl https://cli-assets.heroku.com/install.sh | sh&lt;/span&gt;
    &lt;span class="c1"&gt;# Pull the commit that triggered this script, which for a pull request will be the merge commit. &lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the associated pull-request to get the review app name to delete.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BRANCH=`curl -X GET -G "https://$BITBUCKET_AUTH_STRING@api.bitbucket.org/2.0/repositories/thinknimble/$BITBUCKET_REPO_SLUG/pullrequests" --data-urlencode 'q=merge_commit.hash="'"$BITBUCKET_COMMIT"'"' | python -c "import json,sys;obj=json.loads(sys.stdin.read());value=obj.get('values', [{}])[0].get('source', {}).get('branch', {}).get('name', '');print(value)"`&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BRANCH_NAME=`echo $BRANCH | egrep "(.*)" -o`&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FEATURE_APP_NAME=$BITBUCKET_REPO_SLUG-$BRANCH_NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# Poll Heroku to see if an app with the constructed name exists.&lt;/span&gt;
    &lt;span class="c1"&gt;# If it does than delete it, if it does not then just return.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;heroku apps:info -a $FEATURE_APP_NAME &amp;amp;&amp;amp; heroku apps:destroy -a $FEATURE_APP_NAME -c $FEATURE_APP_NAME || echo "No feature server to cleanup"&lt;/span&gt;

&lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Whenever a pull-request is opened or updated, create/update a Heroku app for the pull-request.&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&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="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*reviewAppDeployment&lt;/span&gt;
  &lt;span class="c1"&gt;# Deploy the master branch to our main app whenever it is updated.&lt;/span&gt;
  &lt;span class="c1"&gt;# Then, update any Review Apps associated with the pull-request merged into `master`.&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*herokuAppDeployment&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*revewAppCleanup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy-app.sh&lt;/span&gt;

&lt;span class="c"&gt;# Place this at the root of your repository&lt;/span&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Script to copy an existing app and deploy a specific branch to it.&lt;/span&gt;
&lt;span class="c"&gt;# 1. Creates the new app&lt;/span&gt;
&lt;span class="c"&gt;# 2. Copies over all environment variables from existing app&lt;/span&gt;
&lt;span class="c"&gt;# 3. Provisions a new Postgres database and copies the rows over from the existing app&lt;/span&gt;
&lt;span class="c"&gt;# 4. Deploys the specified branch to the new app&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# USAGE:&lt;/span&gt;
&lt;span class="c"&gt;#   ./copy-app.sh parentApp newApp branchName&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="c"&gt;# Defines the parameters passed in to the script. Requires the name of the&lt;/span&gt;
&lt;span class="c"&gt;# parent app, the name of the new app, and the name of the branch to push to the new app to build.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="nv"&gt;parentAppName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;newAppName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;branchName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;

&lt;span class="c"&gt;# Create the new app&lt;/span&gt;
heroku create &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Add the standard buildpacks for your app. Here we add NodeJS first, then python.&lt;/span&gt;
heroku buildpacks:add heroku/nodejs &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
heroku buildpacks:add heroku/python &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Copy environemnt variables from parent app to new app&lt;/span&gt;
&lt;span class="nv"&gt;configFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"config-temp.txt"&lt;/span&gt;
heroku config &lt;span class="nt"&gt;-s&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;$parentAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$configFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$configFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; | xargs heroku config:set &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"./&lt;/span&gt;&lt;span class="nv"&gt;$configFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Remove the DATABASE_URL environment variable as it will be set when we provision a new database&lt;/span&gt;
heroku config:unset DATABASE_URL &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Update the ALLOWED_HOSTS and CURRENT_DOMAIN environment variables&lt;/span&gt;
heroku config:set &lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;.herokuapp.com"&lt;/span&gt; &lt;span class="nv"&gt;CURRENT_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;.herokuapp.com"&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;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Provision new Postgres database&lt;/span&gt;
heroku addons:create heroku-postgresql:hobby-dev &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Push the source code of the branch up to the new app to build&lt;/span&gt;
git push https://heroku:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HEROKU_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;@git.heroku.com/&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;.git &lt;span class="nv"&gt;$branchName&lt;/span&gt;:master &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Copy rows from parent app's database&lt;/span&gt;
heroku pg:copy &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$parentAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;::DATABASE_URL DATABASE_URL &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--confirm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$newAppName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devops</category>
      <category>bash</category>
      <category>heroku</category>
      <category>bitbucket</category>
    </item>
    <item>
      <title>Create a VueJS Currency Filter</title>
      <dc:creator>William Huster</dc:creator>
      <pubDate>Wed, 03 Jan 2018 02:34:00 +0000</pubDate>
      <link>https://dev.to/thinknimble/create-a-vuejs-currency-filter-3711</link>
      <guid>https://dev.to/thinknimble/create-a-vuejs-currency-filter-3711</guid>
      <description>&lt;p&gt;Displaying formatted currency amounts is a common requirement of web apps, but VueJS does not provide any filters out of the box. So here’s what you can do if you need to add a currency filter to your Vue project.&lt;/p&gt;

&lt;p&gt;First, you'll need to know about VueJS filters and how they work. Read more here: &lt;a href="https://vuejs.org/v2/guide/filters.html"&gt;https://vuejs.org/v2/guide/filters.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the currency-formatter NPM Package
&lt;/h2&gt;

&lt;p&gt;If you are using webpack or a similar build tool and have access to node.js packages, then you can install the currency-formatter package from npm. By the way, I highly recommend using the &lt;a href="https://cli.vuejs.org/"&gt;Vue CLI&lt;/a&gt; for your projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;currency-formatter &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can create a VueJS filter in your app code like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;currencyFormatter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency-formatter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formatNumberAsUSD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatNumberAsUSD&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;currencyFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Vue.filter&lt;/code&gt; registers a new global Vue filter called 'currency' and &lt;code&gt;formatNumberAsUSD&lt;/code&gt; function does the work. The formatting function does three things, it (a) makes sure it has a truthy value and returns a blank string if not, (b) attempts to convert the value to a &lt;code&gt;Number&lt;/code&gt;, and (c) uses &lt;code&gt;currencyFormatter.format&lt;/code&gt; to format the number as USD.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Number.prototype.toLocaleString()
&lt;/h2&gt;

&lt;p&gt;If you are not using a build tool with access to node.js packages, or you mistrust external dependencies, you might try using the Number.toLocalString() method below or &lt;a href="https://github.com/smirzaei/currency-formatter/blob/master/index.js"&gt;take a peek at currency-formatter source code on Github&lt;/a&gt; and borrow from it. Note that while this is supported in modern browsers, it may not be available in older browsers you might need to target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;currencyFormatter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency-formatter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formatNumberAsUSD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatNumberAsUSD&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;toLocaleString&lt;/code&gt; and the currency-formatter package can handle currencies besides USD, too. &lt;a href="https://www.npmjs.com/package/currency-formatter"&gt;Check out the currency-formatter npm page&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;No matter the approach you chose, you have now created a 'currency' filter that you can use in your VueJS templates like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the output should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$12.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excellent!&lt;/p&gt;

</description>
      <category>vue</category>
      <category>components</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Presenting: 🚨 Alert! Alert!</title>
      <dc:creator>William Huster</dc:creator>
      <pubDate>Tue, 18 Nov 2014 12:00:00 +0000</pubDate>
      <link>https://dev.to/thinknimble/presenting-alert-alert-667</link>
      <guid>https://dev.to/thinknimble/presenting-alert-alert-667</guid>
      <description>&lt;p&gt;I built a little, dependency-free JavaScript library to add UI notifications to any web project. I call it: "Alert! Alert!"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://codepen.io/whusterj/pen/qEWMwG"&gt;Demo on Codepen&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/whusterj/alert-alert"&gt;Code on Github&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/vue-alert-alert"&gt;VueJS Plug-in&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll need to compile the LESS CSS before use … I’ll eventually add a Gruntfile or something.&lt;/p&gt;

&lt;p&gt;It’s very simple to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7000&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last param is optional. If a timeout is given, the notification will disappear after the given number of milliseconds.&lt;/p&gt;

&lt;p&gt;It’s nothing groundbreaking, but I was frustrated by the options out there that seem to over-complicate such a trivial thing.&lt;/p&gt;




&lt;h3&gt;
  
  
  Complete Source Code
&lt;/h3&gt;

&lt;p&gt;Here is the complete source code, which totals about 150 lines of JavaScript and CSS (LESS).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;alert-alert.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;CONTAINER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aa-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;ALERT_CLASS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aa-notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_alert&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;/////////////////////////////////////////&lt;/span&gt;
  &lt;span class="c1"&gt;// functions&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_alert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_genNotificationContainer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;_genAlertDiv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_genNotificationContainer&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;containerDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;containerDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CONTAINER_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;containerDiv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;containerDiv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_genAlertDiv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;alertDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;alertDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ALERT_CLASS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;alertDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="nx"&gt;alertDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_alertClickHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;alertDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;_removeAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alertDiv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;alertDiv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_removeAlert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_alertClickHandler&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;_removeAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;alert-alert.css&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#aa-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.aa-notification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.aa-notification&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.06&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;gba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.aa-notification.info&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.aa-notification.success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.aa-notification.warning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;215&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.aa-notification.error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>css</category>
    </item>
  </channel>
</rss>
