diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 651d77e9a..d0bdd278a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -60,7 +60,9 @@ jobs:
           if [ $IS_FORK = true ]; then
             echo "Skipping the app signing: building from a fork."
           else
+            export BUILD_SUFFIX="linux";
             if [ "${{ runner.OS }}" = "macOS" ]; then
+              export BUILD_SUFFIX="mac";
               export CSC_LINK="${{ runner.temp }}/signing_certificate.p12"
               # APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
               # https://www.kencochrane.com/2020/08/01/build-and-sign-golang-binaries-for-macos-with-github-actions/#exporting-the-developer-certificate
@@ -69,6 +71,7 @@ jobs:
               export CSC_KEY_PASSWORD="${{ secrets.KEYCHAIN_PASSWORD }}"
 
             elif [ "${{ runner.OS }}" = "Windows" ]; then
+              export BUILD_SUFFIX="";
               export CSC_LINK="${{ runner.temp }}/signing_certificate.pfx"
               npm config set msvs_version 2017 --global
               echo "${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PFX }}" | base64 --decode > "$CSC_LINK"
@@ -78,7 +81,15 @@ jobs:
           fi
 
           yarn --cwd ./electron/packager/
-          yarn --cwd ./electron/packager/ package
+          yarn --cwd ./electron/packager/ package 
+
+          export BUILD_PREFIX="stable"
+          if [ "$IS_NIGHTLY" = true ]; then
+            export BUILD_PREFIX="nightly"
+          fi
+
+          mv electron/build/dist/latest-$BUILD_SUFFIX.yml electron/build/dist/$BUILD_PREFIX-$BUILD_SUFFIX.yml
+          rm electron/build/dist/alpha* electron/build/dist/beta*
 
       - name: Upload [GitHub Actions]
         uses: actions/upload-artifact@v2
@@ -98,7 +109,9 @@ jobs:
           - path: '*Linux_64bit.zip'
             name: Linux_X86-64
           - path: '*macOS_64bit.dmg'
-            name: macOS
+            name: macOS_dmg
+          - path: '*macOS_64bit.zip'
+            name: macOS_zip
           - path: '*Windows_64bit.exe'
             name: Windows_X86-64_interactive_installer
           - path: '*Windows_64bit.msi'
diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json
index 2d6ff2b63..3f21181c1 100644
--- a/arduino-ide-extension/package.json
+++ b/arduino-ide-extension/package.json
@@ -65,6 +65,7 @@
     "css-element-queries": "^1.2.0",
     "dateformat": "^3.0.3",
     "deepmerge": "2.0.1",
+    "electron-updater": "^4.6.5",
     "fuzzy": "^0.1.3",
     "glob": "^7.1.6",
     "google-protobuf": "^3.11.4",
@@ -82,6 +83,7 @@
     "ps-tree": "^1.2.0",
     "query-string": "^7.0.1",
     "react-disable": "^0.1.0",
+    "react-markdown": "^8.0.0",
     "react-select": "^3.0.4",
     "react-tabs": "^3.1.2",
     "react-window": "^1.8.6",
diff --git a/arduino-ide-extension/scripts/compose-changelog.js b/arduino-ide-extension/scripts/compose-changelog.js
index 36b5c94c0..aba9dae46 100755
--- a/arduino-ide-extension/scripts/compose-changelog.js
+++ b/arduino-ide-extension/scripts/compose-changelog.js
@@ -1,36 +1,42 @@
 // @ts-check
 
-
 (async () => {
   const { Octokit } = require('@octokit/rest');
-  const fs = require("fs");
-  const path = require("path");
+  const fs = require('fs');
+  const path = require('path');
 
   const octokit = new Octokit({
     userAgent: 'Arduino IDE compose-changelog.js',
   });
 
-  const response = await octokit.rest.repos.listReleases({
-    owner: 'arduino',
-    repo: 'arduino-ide',
-  }).catch(err => {
-    console.error(err);
-    process.exit(1);
-  })
+  const response = await octokit.rest.repos
+    .listReleases({
+      owner: 'arduino',
+      repo: 'arduino-ide',
+    })
+    .catch((err) => {
+      console.error(err);
+      process.exit(1);
+    });
 
   const releases = response.data;
 
-  let fullChangelog = releases.reduce((acc, item) => {
+  let fullChangelog = releases.reduce((acc, item, index) => {
     // Process each line separately
-    const body = item.body.split('\n').map(processLine).join('\n')
+    const body = item.body.split('\n').map(processLine).join('\n');
     // item.name is the name of the release changelog
-    return acc + `# ${item.name}\n\n${body}\n\n---\n\n`;
+    return (
+      acc +
+      `## ${item.name}\n\n${body}${
+        index !== releases.length - 1 ? '\n\n---\n\n' : '\n'
+      }`
+    );
   }, '');
 
-  const args = process.argv.slice(2)
+  const args = process.argv.slice(2);
   if (args.length == 0) {
-    console.error("Missing argument to destination file")
-    process.exit(1)
+    console.error('Missing argument to destination file');
+    process.exit(1);
   }
   const changelogFile = path.resolve(args[0]);
 
@@ -38,19 +44,18 @@
     changelogFile,
     fullChangelog,
     {
-      flag: "w+",
+      flag: 'w+',
     },
-    err => {
+    (err) => {
       if (err) {
         console.error(err);
         process.exit(1);
       }
-      console.log("Changelog written to", changelogFile);
+      console.log('Changelog written to', changelogFile);
     }
-  )
+  );
 })();
 
-
 // processLine applies different substitutions to line string.
 // We're assuming that there are no more than one substitution
 // per line to be applied.
@@ -61,7 +66,8 @@ const processLine = (line) => {
   // * [#123](https://github.com/arduino/arduino-ide/pull/123/)
   // * [#123](https://github.com/arduino/arduino-ide/issues/123/)
   // If it does return the line as is.
-  let r = /(\(|\[)#\d+(\)|\])(\(|\[)https:\/\/github\.com\/arduino\/arduino-ide\/(pull|issues)\/(\d+)\/?(\)|\])/gm;
+  let r =
+    /(\(|\[)#\d+(\)|\])(\(|\[)https:\/\/github\.com\/arduino\/arduino-ide\/(pull|issues)\/(\d+)\/?(\)|\])/gm;
   if (r.test(line)) {
     return line;
   }
@@ -70,9 +76,12 @@ const processLine = (line) => {
   // * #123
   // If it does it's changed to:
   // * [#123](https://github.com/arduino/arduino-ide/pull/123)
-  r = /#(\d+)/gm;
+  r = /(?<![\w\d\/_]{1})#((\d)+)(?![\w\d\/_]{1})/gm;
   if (r.test(line)) {
-    return line.replace(r, `[#$1](https://github.com/arduino/arduino-ide/pull/$1)`)
+    return line.replace(
+      r,
+      `[#$1](https://github.com/arduino/arduino-ide/pull/$1)`
+    );
   }
 
   // Check if a link with one of the following format exists:
@@ -85,7 +94,8 @@ const processLine = (line) => {
   // * [#123](https://github.com/arduino/arduino-ide/issues/123)
   // * [#123](https://github.com/arduino/arduino-ide/pull/123/)
   // * [#123](https://github.com/arduino/arduino-ide/issues/123/)
-  r = /(https:\/\/github\.com\/arduino\/arduino-ide\/(pull|issues)\/(\d+)\/?)/gm;
+  r =
+    /(https:\/\/github\.com\/arduino\/arduino-ide\/(pull|issues)\/(\d+)\/?)/gm;
   if (r.test(line)) {
     return line.replace(r, `[#$3]($1)`);
   }
@@ -95,11 +105,12 @@ const processLine = (line) => {
   // * https://github.com/arduino/arduino-ide/compare/2.0.0-rc2...2.0.0-rc3/
   // If it does it's changed to:
   // * [`2.0.0-rc2...2.0.0-rc3`](https://github.com/arduino/arduino-ide/compare/2.0.0-rc2...2.0.0-rc3)
-  r = /(https:\/\/github\.com\/arduino\/arduino-ide\/compare\/([^\/]*))\/?\s?/gm;
+  r =
+    /(https:\/\/github\.com\/arduino\/arduino-ide\/compare\/([^\/]*))\/?\s?/gm;
   if (r.test(line)) {
-    return line.replace(r, '[`$2`]($1)');;
+    return line.replace(r, '[`$2`]($1)');
   }
 
   // If nothing matches just return the line as is
   return line;
-}
+};
diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx
index b493bd3bf..7b0184bdd 100644
--- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx
+++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx
@@ -68,8 +68,12 @@ import { ArduinoPreferences } from './arduino-preferences';
 import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
 import { SaveAsSketch } from './contributions/save-as-sketch';
 import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution';
+import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
+import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog';
+import { IDEUpdater } from '../common/protocol/ide-updater';
 
 const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages';
+export const SKIP_IDE_VERSION = 'skipIDEVersion';
 
 @injectable()
 export class ArduinoFrontendContribution
@@ -78,8 +82,7 @@ export class ArduinoFrontendContribution
     TabBarToolbarContribution,
     CommandContribution,
     MenuContribution,
-    ColorContribution
-{
+    ColorContribution {
   @inject(ILogger)
   protected logger: ILogger;
 
@@ -157,6 +160,15 @@ export class ArduinoFrontendContribution
   @inject(LocalStorageService)
   protected readonly localStorageService: LocalStorageService;
 
+  @inject(IDEUpdaterCommands)
+  protected readonly updater: IDEUpdaterCommands;
+
+  @inject(IDEUpdaterDialog)
+  protected readonly updaterDialog: IDEUpdaterDialog;
+
+  @inject(IDEUpdater)
+  protected readonly updaterService: IDEUpdater;
+
   protected invalidConfigPopup:
     | Promise<void | 'No' | 'Yes' | undefined>
     | undefined;
@@ -251,7 +263,7 @@ export class ArduinoFrontendContribution
     });
   }
 
-  onStart(app: FrontendApplication): void {
+  async onStart(app: FrontendApplication): Promise<void> {
     // Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
     for (const viewContribution of [
       this.fileNavigatorContributions,
@@ -266,6 +278,19 @@ export class ArduinoFrontendContribution
         viewContribution.initializeLayout(app);
       }
     }
+
+    this.updaterService.init(
+      this.arduinoPreferences.get('arduino.ide.updateChannel')
+    );
+    this.updater.checkForUpdates().then(async (updateInfo) => {
+      if (!updateInfo) return;
+      const versionToSkip = await this.localStorageService.getData<string>(
+        SKIP_IDE_VERSION
+      );
+      if (versionToSkip === updateInfo.version) return;
+      this.updaterDialog.open(updateInfo);
+    });
+
     const start = async ({ selectedBoard }: BoardsConfig.Config) => {
       if (selectedBoard) {
         const { name, fqbn } = selectedBoard;
diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
index 700012bbd..5ceae9179 100644
--- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
+++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
@@ -262,6 +262,19 @@ import {
   UserFieldsDialogWidget,
 } from './dialogs/user-fields/user-fields-dialog';
 import { nls } from '@theia/core/lib/common';
+import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
+import {
+  IDEUpdater,
+  IDEUpdaterClient,
+  IDEUpdaterPath,
+} from '../common/protocol/ide-updater';
+import { IDEUpdaterClientImpl } from './ide-updater/ide-updater-client-impl';
+import {
+  IDEUpdaterDialog,
+  IDEUpdaterDialogProps,
+  IDEUpdaterDialogWidget,
+} from './dialogs/ide-updater/ide-updater-dialog';
+import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
 
 const ElementQueries = require('css-element-queries/src/ElementQueries');
 
@@ -407,8 +420,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
   bind(SerialService)
     .toDynamicValue((context) => {
       const connection = context.container.get(WebSocketConnectionProvider);
-      const client =
-        context.container.get<SerialServiceClient>(SerialServiceClient);
+      const client = context.container.get<SerialServiceClient>(
+        SerialServiceClient
+      );
       return connection.createProxy(SerialServicePath, client);
     })
     .inSingletonScope();
@@ -472,12 +486,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
     .inSingletonScope();
   rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope();
   rebind(TabBarToolbarFactory).toFactory(
-    ({ container: parentContainer }) =>
-      () => {
-        const container = parentContainer.createChild();
-        container.bind(TabBarToolbar).toSelf().inSingletonScope();
-        return container.get(TabBarToolbar);
-      }
+    ({ container: parentContainer }) => () => {
+      const container = parentContainer.createChild();
+      container.bind(TabBarToolbar).toSelf().inSingletonScope();
+      return container.get(TabBarToolbar);
+    }
   );
   bind(OutputWidget).toSelf().inSingletonScope();
   rebind(TheiaOutputWidget).toService(OutputWidget);
@@ -642,13 +655,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
 
   // Enable the dirty indicator on uncloseable widgets.
   rebind(TabBarRendererFactory).toFactory((context) => () => {
-    const contextMenuRenderer =
-      context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
+    const contextMenuRenderer = context.container.get<ContextMenuRenderer>(
+      ContextMenuRenderer
+    );
     const decoratorService = context.container.get<TabBarDecoratorService>(
       TabBarDecoratorService
     );
-    const iconThemeService =
-      context.container.get<IconThemeService>(IconThemeService);
+    const iconThemeService = context.container.get<IconThemeService>(
+      IconThemeService
+    );
     return new TabBarRenderer(
       contextMenuRenderer,
       decoratorService,
@@ -756,9 +771,32 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
     title: 'UploadCertificate',
   });
 
+  bind(IDEUpdaterDialogWidget).toSelf().inSingletonScope();
+  bind(IDEUpdaterDialog).toSelf().inSingletonScope();
+  bind(IDEUpdaterDialogProps).toConstantValue({
+    title: 'IDEUpdater',
+  });
+
   bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
   bind(UserFieldsDialog).toSelf().inSingletonScope();
   bind(UserFieldsDialogProps).toConstantValue({
     title: 'UserFields',
   });
+
+  bind(IDEUpdaterCommands).toSelf().inSingletonScope();
+  bind(CommandContribution).toService(IDEUpdaterCommands);
+
+  // Frontend binding for the IDE Updater service
+  bind(IDEUpdaterClientImpl).toSelf().inSingletonScope();
+  bind(IDEUpdaterClient).toService(IDEUpdaterClientImpl);
+  bind(IDEUpdater)
+    .toDynamicValue((context) => {
+      const client = context.container.get(IDEUpdaterClientImpl);
+      return ElectronIpcConnectionProvider.createProxy(
+        context.container,
+        IDEUpdaterPath,
+        client
+      );
+    })
+    .inSingletonScope();
 });
diff --git a/arduino-ide-extension/src/browser/arduino-preferences.ts b/arduino-ide-extension/src/browser/arduino-preferences.ts
index 5e1013a1d..910d724bc 100644
--- a/arduino-ide-extension/src/browser/arduino-preferences.ts
+++ b/arduino-ide-extension/src/browser/arduino-preferences.ts
@@ -9,6 +9,11 @@ import {
 import { nls } from '@theia/core/lib/common';
 import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol';
 
+export enum UpdateChannel {
+  Stable = 'stable',
+  Nightly = 'nightly',
+}
+
 export const ArduinoConfigSchema: PreferenceSchema = {
   type: 'object',
   properties: {
@@ -64,13 +69,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
       ),
       default: 0,
     },
-    'arduino.ide.autoUpdate': {
-      type: 'boolean',
+    'arduino.ide.updateChannel': {
+      type: 'string',
+      enum: Object.values(UpdateChannel) as UpdateChannel[],
+      default: UpdateChannel.Stable,
       description: nls.localize(
-        'arduino/preferences/ide.autoUpdate',
-        'True to enable automatic update checks. The IDE will check for updates automatically and periodically.'
+        'arduino/preferences/ide.updateChannel',
+        "Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build."
       ),
-      default: true,
     },
     'arduino.board.certificates': {
       type: 'string',
@@ -171,7 +177,7 @@ export interface ArduinoConfiguration {
   'arduino.upload.verify': boolean;
   'arduino.window.autoScale': boolean;
   'arduino.window.zoomLevel': number;
-  'arduino.ide.autoUpdate': boolean;
+  'arduino.ide.updateChannel': UpdateChannel;
   'arduino.board.certificates': string;
   'arduino.sketchbook.showAllFiles': boolean;
   'arduino.cloud.enabled': boolean;
@@ -188,16 +194,10 @@ export interface ArduinoConfiguration {
 export const ArduinoPreferences = Symbol('ArduinoPreferences');
 export type ArduinoPreferences = PreferenceProxy<ArduinoConfiguration>;
 
-export function createArduinoPreferences(
-  preferences: PreferenceService
-): ArduinoPreferences {
-  return createPreferenceProxy(preferences, ArduinoConfigSchema);
-}
-
 export function bindArduinoPreferences(bind: interfaces.Bind): void {
   bind(ArduinoPreferences).toDynamicValue((ctx) => {
     const preferences = ctx.container.get<PreferenceService>(PreferenceService);
-    return createArduinoPreferences(preferences);
+    return createPreferenceProxy(preferences, ArduinoConfigSchema);
   });
   bind(PreferenceContribution).toConstantValue({
     schema: ArduinoConfigSchema,
diff --git a/arduino-ide-extension/src/browser/components/ProgressBar.tsx b/arduino-ide-extension/src/browser/components/ProgressBar.tsx
new file mode 100644
index 000000000..f91c9f991
--- /dev/null
+++ b/arduino-ide-extension/src/browser/components/ProgressBar.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+
+export type ProgressBarProps = {
+  percent?: number;
+  showPercentage?: boolean;
+};
+
+export default function ProgressBar({
+  percent = 0,
+  showPercentage = false,
+}: ProgressBarProps): React.ReactElement {
+  const roundedPercent = Math.round(percent);
+  return (
+    <div className="progress-bar">
+      <div className="progress-bar--outer">
+        <div
+          className="progress-bar--inner"
+          style={{ width: `${roundedPercent}%` }}
+        />
+      </div>
+      {showPercentage && (
+        <div className="progress-bar--percentage">
+          <div className="progress-bar--percentage-text">{roundedPercent}%</div>
+        </div>
+      )}
+    </div>
+  );
+}
diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx
new file mode 100644
index 000000000..3c1c4911e
--- /dev/null
+++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx
@@ -0,0 +1,159 @@
+import { nls } from '@theia/core/lib/common';
+import { shell } from 'electron';
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import ReactMarkdown from 'react-markdown';
+import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
+import ProgressBar from '../../components/ProgressBar';
+
+export type IDEUpdaterComponentProps = {
+  updateInfo: UpdateInfo;
+  downloadFinished?: boolean;
+  downloadStarted?: boolean;
+  progress?: ProgressInfo;
+  error?: Error;
+  onDownload: () => void;
+  onClose: () => void;
+  onSkipVersion: () => void;
+  onCloseAndInstall: () => void;
+};
+
+export const IDEUpdaterComponent = ({
+  updateInfo: { version, releaseNotes },
+  downloadStarted = false,
+  downloadFinished = false,
+  progress,
+  error,
+  onDownload,
+  onClose,
+  onSkipVersion,
+  onCloseAndInstall,
+}: IDEUpdaterComponentProps): React.ReactElement => {
+  const changelogDivRef = React.useRef() as React.MutableRefObject<
+    HTMLDivElement
+  >;
+  React.useEffect(() => {
+    if (!!releaseNotes) {
+      let changelog: string;
+      if (typeof releaseNotes === 'string') changelog = releaseNotes;
+      else
+        changelog = releaseNotes.reduce((acc, item) => {
+          return item.note ? (acc += `${item.note}\n\n`) : acc;
+        }, '');
+      ReactDOM.render(
+        <ReactMarkdown
+          components={{
+            a: ({ href, children, ...props }) => (
+              <a onClick={() => href && shell.openExternal(href)} {...props}>
+                {children}
+              </a>
+            ),
+          }}
+        >
+          {changelog}
+        </ReactMarkdown>,
+        changelogDivRef.current
+      );
+    }
+  }, [releaseNotes]);
+  const closeButton = (
+    <button onClick={onClose} type="button" className="theia-button secondary">
+      {nls.localize('arduino/ide-updater/notNowButton', 'Not now')}
+    </button>
+  );
+
+  return (
+    <div className="ide-updater-dialog--content">
+      {downloadFinished ? (
+        <div className="ide-updater-dialog--downloaded">
+          <div>
+            {nls.localize(
+              'arduino/ide-updater/versionDownloaded',
+              'Arduino IDE {0} has been downloaded.',
+              version
+            )}
+          </div>
+          <div>
+            {nls.localize(
+              'arduino/ide-updater/closeToInstallNotice',
+              'Close the software and install the update on your machine.'
+            )}
+          </div>
+          <div className="buttons-container">
+            {closeButton}
+            <button
+              onClick={onCloseAndInstall}
+              type="button"
+              className="theia-button close-and-install"
+            >
+              {nls.localize(
+                'arduino/ide-updater/closeAndInstallButton',
+                'Close and Install'
+              )}
+            </button>
+          </div>
+        </div>
+      ) : downloadStarted ? (
+        <div className="ide-updater-dialog--downloading">
+          <div>
+            {nls.localize(
+              'arduino/ide-updater/downloadingNotice',
+              'Downloading the latest version of the Arduino IDE.'
+            )}
+          </div>
+          <ProgressBar percent={progress?.percent} showPercentage />
+        </div>
+      ) : (
+        <div className="ide-updater-dialog--pre-download">
+          <div className="ide-updater-dialog--logo-container">
+            <div className="ide-updater-dialog--logo"></div>
+          </div>
+          <div className="ide-updater-dialog--new-version-text dialogSection">
+            <div className="dialogRow">
+              <div className="bold">
+                {nls.localize(
+                  'arduino/ide-updater/updateAvailable',
+                  'Update Available'
+                )}
+              </div>
+            </div>
+            <div className="dialogRow">
+              {nls.localize(
+                'arduino/ide-updater/newVersionAvailable',
+                'A new version of Arduino IDE ({0}) is available for download.',
+                version
+              )}
+            </div>
+            {releaseNotes && (
+              <div className="dialogRow">
+                <div className="changelog-container" ref={changelogDivRef} />
+              </div>
+            )}
+            <div className="buttons-container">
+              <button
+                onClick={onSkipVersion}
+                type="button"
+                className="theia-button secondary skip-version"
+              >
+                {nls.localize(
+                  'arduino/ide-updater/skipVersionButton',
+                  'Skip Version'
+                )}
+              </button>
+              <div className="push"></div>
+              {closeButton}
+              <button
+                onClick={onDownload}
+                type="button"
+                className="theia-button primary"
+              >
+                {nls.localize('arduino/ide-updater/downloadButton', 'Download')}
+              </button>
+            </div>
+          </div>
+        </div>
+      )}
+      {!!error && <div className="error-container">{error}</div>}
+    </div>
+  );
+};
diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx
new file mode 100644
index 000000000..56b8c84f5
--- /dev/null
+++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx
@@ -0,0 +1,166 @@
+import * as React from 'react';
+import { inject, injectable } from 'inversify';
+import { DialogProps } from '@theia/core/lib/browser/dialogs';
+import { AbstractDialog } from '../../theia/dialogs/dialogs';
+import { Widget } from '@phosphor/widgets';
+import { Message } from '@phosphor/messaging';
+import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
+import { nls } from '@theia/core';
+import { IDEUpdaterComponent } from './ide-updater-component';
+import { IDEUpdaterCommands } from '../../ide-updater/ide-updater-commands';
+import {
+  IDEUpdaterClient,
+  ProgressInfo,
+  UpdateInfo,
+} from '../../../common/protocol/ide-updater';
+import { LocalStorageService } from '@theia/core/lib/browser';
+import { SKIP_IDE_VERSION } from '../../arduino-frontend-contribution';
+
+@injectable()
+export class IDEUpdaterDialogWidget extends ReactWidget {
+  protected isOpen = new Object();
+  updateInfo: UpdateInfo;
+  progressInfo: ProgressInfo | undefined;
+  error: Error | undefined;
+  downloadFinished: boolean;
+  downloadStarted: boolean;
+  onClose: () => void;
+
+  @inject(IDEUpdaterCommands)
+  protected readonly updater: IDEUpdaterCommands;
+
+  @inject(IDEUpdaterClient)
+  protected readonly updaterClient: IDEUpdaterClient;
+
+  @inject(LocalStorageService)
+  protected readonly localStorageService: LocalStorageService;
+
+  init(updateInfo: UpdateInfo, onClose: () => void): void {
+    this.updateInfo = updateInfo;
+    this.progressInfo = undefined;
+    this.error = undefined;
+    this.downloadStarted = false;
+    this.downloadFinished = false;
+    this.onClose = onClose;
+
+    this.updaterClient.onError((e) => {
+      this.error = e;
+      this.update();
+    });
+    this.updaterClient.onDownloadProgressChanged((e) => {
+      this.progressInfo = e;
+      this.update();
+    });
+    this.updaterClient.onDownloadFinished((e) => {
+      this.downloadFinished = true;
+      this.update();
+    });
+  }
+
+  async onSkipVersion(): Promise<void> {
+    this.localStorageService.setData<string>(
+      SKIP_IDE_VERSION,
+      this.updateInfo.version
+    );
+    this.close();
+  }
+
+  close(): void {
+    super.close();
+    this.onClose();
+  }
+
+  onDispose(): void {
+    if (this.downloadStarted && !this.downloadFinished)
+      this.updater.stopDownload();
+  }
+
+  async onDownload(): Promise<void> {
+    this.progressInfo = undefined;
+    this.downloadStarted = true;
+    this.error = undefined;
+    this.updater.downloadUpdate();
+    this.update();
+  }
+
+  onCloseAndInstall(): void {
+    this.updater.quitAndInstall();
+  }
+
+  protected render(): React.ReactNode {
+    return !!this.updateInfo ? (
+      <form>
+        <IDEUpdaterComponent
+          updateInfo={this.updateInfo}
+          downloadStarted={this.downloadStarted}
+          downloadFinished={this.downloadFinished}
+          progress={this.progressInfo}
+          onClose={this.close.bind(this)}
+          onSkipVersion={this.onSkipVersion.bind(this)}
+          onDownload={this.onDownload.bind(this)}
+          onCloseAndInstall={this.onCloseAndInstall.bind(this)}
+        />
+      </form>
+    ) : null;
+  }
+}
+
+@injectable()
+export class IDEUpdaterDialogProps extends DialogProps {}
+
+@injectable()
+export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
+  @inject(IDEUpdaterDialogWidget)
+  protected readonly widget: IDEUpdaterDialogWidget;
+
+  constructor(
+    @inject(IDEUpdaterDialogProps)
+    protected readonly props: IDEUpdaterDialogProps
+  ) {
+    super({
+      title: nls.localize(
+        'arduino/updater/ideUpdaterDialog',
+        'Software Update'
+      ),
+    });
+    this.contentNode.classList.add('ide-updater-dialog');
+    this.acceptButton = undefined;
+  }
+
+  get value(): UpdateInfo {
+    return this.widget.updateInfo;
+  }
+
+  protected onAfterAttach(msg: Message): void {
+    if (this.widget.isAttached) {
+      Widget.detach(this.widget);
+    }
+    Widget.attach(this.widget, this.contentNode);
+    super.onAfterAttach(msg);
+    this.update();
+  }
+
+  async open(
+    data: UpdateInfo | undefined = undefined
+  ): Promise<UpdateInfo | undefined> {
+    if (data && data.version) {
+      this.widget.init(data, this.close.bind(this));
+      return super.open();
+    }
+  }
+
+  protected onUpdateRequest(msg: Message): void {
+    super.onUpdateRequest(msg);
+    this.widget.update();
+  }
+
+  protected onActivateRequest(msg: Message): void {
+    super.onActivateRequest(msg);
+    this.widget.activate();
+  }
+
+  close(): void {
+    this.widget.dispose();
+    super.close();
+  }
+}
diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx
index 62c166c5e..e7fa7a060 100644
--- a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx
@@ -260,18 +260,6 @@ export class SettingsComponent extends React.Component<
             'Verify code after upload'
           )}
         </label>
-        <label className="flex-line">
-          <input
-            type="checkbox"
-            checked={this.state.checkForUpdates}
-            onChange={this.checkForUpdatesDidChange}
-            disabled={true}
-          />
-          {nls.localize(
-            'arduino/preferences/checkForUpdates',
-            'Check for updates on startup'
-          )}
-        </label>
         <label className="flex-line">
           <input
             type="checkbox"
@@ -444,7 +432,9 @@ export class SettingsComponent extends React.Component<
     );
   }
 
-  protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
+  protected noopKeyDown = (
+    event: React.KeyboardEvent<HTMLInputElement>
+  ): void => {
     if (this.isControlKey(event)) {
       return;
     }
@@ -454,7 +444,7 @@ export class SettingsComponent extends React.Component<
 
   protected numbersOnlyKeyDown = (
     event: React.KeyboardEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (this.isControlKey(event)) {
       return;
     }
@@ -466,7 +456,7 @@ export class SettingsComponent extends React.Component<
     }
   };
 
-  protected browseSketchbookDidClick = async () => {
+  protected browseSketchbookDidClick = async (): Promise<void> => {
     const uri = await this.props.fileDialogService.showOpenDialog({
       title: nls.localize(
         'arduino/preferences/newSketchbookLocation',
@@ -483,7 +473,7 @@ export class SettingsComponent extends React.Component<
     }
   };
 
-  protected editAdditionalUrlDidClick = async () => {
+  protected editAdditionalUrlDidClick = async (): Promise<void> => {
     const additionalUrls = await new AdditionalUrlsDialog(
       this.state.additionalUrls,
       this.props.windowService
@@ -495,7 +485,7 @@ export class SettingsComponent extends React.Component<
 
   protected editorFontSizeDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     const { value } = event.target;
     if (value) {
       this.setState({ editorFontSize: parseInt(value, 10) });
@@ -504,7 +494,7 @@ export class SettingsComponent extends React.Component<
 
   protected additionalUrlsDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({
       additionalUrls: event.target.value.split(',').map((url) => url.trim()),
     });
@@ -512,13 +502,13 @@ export class SettingsComponent extends React.Component<
 
   protected autoScaleInterfaceDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({ autoScaleInterface: event.target.checked });
   };
 
   protected interfaceScaleDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     const { value } = event.target;
     const percentage = parseInt(value, 10);
     if (isNaN(percentage)) {
@@ -532,31 +522,25 @@ export class SettingsComponent extends React.Component<
 
   protected verifyAfterUploadDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({ verifyAfterUpload: event.target.checked });
   };
 
-  protected checkForUpdatesDidChange = (
-    event: React.ChangeEvent<HTMLInputElement>
-  ) => {
-    this.setState({ checkForUpdates: event.target.checked });
-  };
-
   protected sketchbookShowAllFilesDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({ sketchbookShowAllFiles: event.target.checked });
   };
 
   protected autoSaveDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
   };
 
   protected quickSuggestionsOtherDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     // need to persist react events through lifecycle https://reactjs.org/docs/events.html#event-pooling
     const newVal = event.target.checked ? true : false;
 
@@ -570,7 +554,9 @@ export class SettingsComponent extends React.Component<
     });
   };
 
-  protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
+  protected themeDidChange = (
+    event: React.ChangeEvent<HTMLSelectElement>
+  ): void => {
     const { selectedIndex } = event.target.options;
     const theme = ThemeService.get().getThemes()[selectedIndex];
     if (theme) {
@@ -580,14 +566,14 @@ export class SettingsComponent extends React.Component<
 
   protected languageDidChange = (
     event: React.ChangeEvent<HTMLSelectElement>
-  ) => {
+  ): void => {
     const selectedLanguage = event.target.value;
     this.setState({ currentLanguage: selectedLanguage });
   };
 
   protected compilerWarningsDidChange = (
     event: React.ChangeEvent<HTMLSelectElement>
-  ) => {
+  ): void => {
     const { selectedIndex } = event.target.options;
     const compilerWarnings = CompilerWarningLiterals[selectedIndex];
     if (compilerWarnings) {
@@ -597,26 +583,28 @@ export class SettingsComponent extends React.Component<
 
   protected verboseOnCompileDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({ verboseOnCompile: event.target.checked });
   };
 
   protected verboseOnUploadDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     this.setState({ verboseOnUpload: event.target.checked });
   };
 
   protected sketchpathDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     const sketchbookPath = event.target.value;
     if (sketchbookPath) {
       this.setState({ sketchbookPath });
     }
   };
 
-  protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+  protected noProxyDidChange = (
+    event: React.ChangeEvent<HTMLInputElement>
+  ): void => {
     if (event.target.checked) {
       this.setState({ network: 'none' });
     } else {
@@ -626,7 +614,7 @@ export class SettingsComponent extends React.Component<
 
   protected manualProxyDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (event.target.checked) {
       this.setState({ network: Network.Default() });
     } else {
@@ -636,7 +624,7 @@ export class SettingsComponent extends React.Component<
 
   protected httpProtocolDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (this.state.network !== 'none') {
       const network = this.cloneProxySettings;
       network.protocol = event.target.checked ? 'http' : 'socks';
@@ -646,7 +634,7 @@ export class SettingsComponent extends React.Component<
 
   protected socksProtocolDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (this.state.network !== 'none') {
       const network = this.cloneProxySettings;
       network.protocol = event.target.checked ? 'socks' : 'http';
@@ -656,7 +644,7 @@ export class SettingsComponent extends React.Component<
 
   protected hostnameDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (this.state.network !== 'none') {
       const network = this.cloneProxySettings;
       network.hostname = event.target.value;
@@ -664,7 +652,9 @@ export class SettingsComponent extends React.Component<
     }
   };
 
-  protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+  protected portDidChange = (
+    event: React.ChangeEvent<HTMLInputElement>
+  ): void => {
     if (this.state.network !== 'none') {
       const network = this.cloneProxySettings;
       network.port = event.target.value;
@@ -674,7 +664,7 @@ export class SettingsComponent extends React.Component<
 
   protected usernameDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (this.state.network !== 'none') {
       const network = this.cloneProxySettings;
       network.username = event.target.value;
@@ -684,7 +674,7 @@ export class SettingsComponent extends React.Component<
 
   protected passwordDidChange = (
     event: React.ChangeEvent<HTMLInputElement>
-  ) => {
+  ): void => {
     if (this.state.network !== 'none') {
       const network = this.cloneProxySettings;
       network.password = event.target.value;
diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings.tsx
index d4a4e0642..8490f1e04 100644
--- a/arduino-ide-extension/src/browser/dialogs/settings/settings.tsx
+++ b/arduino-ide-extension/src/browser/dialogs/settings/settings.tsx
@@ -18,24 +18,22 @@ import {
 import { nls } from '@theia/core/lib/common';
 import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
 
-const EDITOR_SETTING = 'editor';
-const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
-const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
-const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
-const ARDUINO_SETTING = 'arduino';
-const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
-// const IDE_SETTING = `${ARDUINO_SETTING}.ide`;
-const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
-const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
-const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
-const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
-const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
-// const AUTO_UPDATE_SETTING = `${IDE_SETTING}.autoUpdate`;
-const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
-const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
-const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
-const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
-const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
+export const EDITOR_SETTING = 'editor';
+export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
+export const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
+export const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
+export const ARDUINO_SETTING = 'arduino';
+export const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
+export const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
+export const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
+export const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
+export const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
+export const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
+export const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
+export const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
+export const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
+export const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
+export const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
 
 export interface Settings extends Index {
   editorFontSize: number; // `editor.fontSize`
@@ -48,7 +46,6 @@ export interface Settings extends Index {
 
   autoScaleInterface: boolean; // `arduino.window.autoScale`
   interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
-  checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
   verboseOnCompile: boolean; // `arduino.compile.verbose`
   compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
   verboseOnUpload: boolean; // `arduino.upload.verbose`
@@ -93,7 +90,6 @@ export class SettingsService {
 
   @postConstruct()
   protected async init(): Promise<void> {
-    await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
     const settings = await this.loadSettings();
     this._settings = deepClone(settings);
     this.ready.resolve();
@@ -110,7 +106,6 @@ export class SettingsService {
       quickSuggestions,
       autoScaleInterface,
       interfaceScale,
-      // checkForUpdates,
       verboseOnCompile,
       compilerWarnings,
       verboseOnUpload,
@@ -135,7 +130,6 @@ export class SettingsService {
       }),
       this.preferenceService.get<boolean>(AUTO_SCALE_SETTING, true),
       this.preferenceService.get<number>(ZOOM_LEVEL_SETTING, 0),
-      // this.preferenceService.get<string>(AUTO_UPDATE_SETTING, true),
       this.preferenceService.get<boolean>(COMPILE_VERBOSE_SETTING, true),
       this.preferenceService.get<any>(COMPILE_WARNINGS_SETTING, 'None'),
       this.preferenceService.get<boolean>(UPLOAD_VERBOSE_SETTING, true),
@@ -154,7 +148,6 @@ export class SettingsService {
       quickSuggestions,
       autoScaleInterface,
       interfaceScale,
-      // checkForUpdates,
       verboseOnCompile,
       compilerWarnings,
       verboseOnUpload,
@@ -234,7 +227,6 @@ export class SettingsService {
       quickSuggestions,
       autoScaleInterface,
       interfaceScale,
-      // checkForUpdates,
       verboseOnCompile,
       compilerWarnings,
       verboseOnUpload,
@@ -284,7 +276,6 @@ export class SettingsService {
         interfaceScale,
         PreferenceScope.User
       ),
-      // this.preferenceService.set(AUTO_UPDATE_SETTING, checkForUpdates, PreferenceScope.User),
       this.preferenceService.set(
         COMPILE_VERBOSE_SETTING,
         verboseOnCompile,
diff --git a/arduino-ide-extension/src/browser/ide-updater/ide-updater-client-impl.ts b/arduino-ide-extension/src/browser/ide-updater/ide-updater-client-impl.ts
new file mode 100644
index 000000000..8f4df3d6e
--- /dev/null
+++ b/arduino-ide-extension/src/browser/ide-updater/ide-updater-client-impl.ts
@@ -0,0 +1,40 @@
+import { Emitter } from '@theia/core';
+import { injectable } from '@theia/core/shared/inversify';
+import { UpdateInfo, ProgressInfo } from 'electron-updater';
+import { IDEUpdaterClient } from '../../common/protocol/ide-updater';
+
+@injectable()
+export class IDEUpdaterClientImpl implements IDEUpdaterClient {
+  protected readonly onErrorEmitter = new Emitter<Error>();
+  protected readonly onCheckingForUpdateEmitter = new Emitter<void>();
+  protected readonly onUpdateAvailableEmitter = new Emitter<UpdateInfo>();
+  protected readonly onUpdateNotAvailableEmitter = new Emitter<UpdateInfo>();
+  protected readonly onDownloadProgressEmitter = new Emitter<ProgressInfo>();
+  protected readonly onDownloadFinishedEmitter = new Emitter<UpdateInfo>();
+
+  readonly onError = this.onErrorEmitter.event;
+  readonly onCheckingForUpdate = this.onCheckingForUpdateEmitter.event;
+  readonly onUpdateAvailable = this.onUpdateAvailableEmitter.event;
+  readonly onUpdateNotAvailable = this.onUpdateNotAvailableEmitter.event;
+  readonly onDownloadProgressChanged = this.onDownloadProgressEmitter.event;
+  readonly onDownloadFinished = this.onDownloadFinishedEmitter.event;
+
+  notifyError(message: Error): void {
+    this.onErrorEmitter.fire(message);
+  }
+  notifyCheckingForUpdate(message: void): void {
+    this.onCheckingForUpdateEmitter.fire(message);
+  }
+  notifyUpdateAvailable(message: UpdateInfo): void {
+    this.onUpdateAvailableEmitter.fire(message);
+  }
+  notifyUpdateNotAvailable(message: UpdateInfo): void {
+    this.onUpdateNotAvailableEmitter.fire(message);
+  }
+  notifyDownloadProgressChanged(message: ProgressInfo): void {
+    this.onDownloadProgressEmitter.fire(message);
+  }
+  notifyDownloadFinished(message: UpdateInfo): void {
+    this.onDownloadFinishedEmitter.fire(message);
+  }
+}
diff --git a/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts b/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts
new file mode 100644
index 000000000..3450e9b1b
--- /dev/null
+++ b/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts
@@ -0,0 +1,71 @@
+import {
+  Command,
+  CommandContribution,
+  CommandRegistry,
+  MessageService,
+} from '@theia/core';
+import { injectable, inject } from 'inversify';
+import { IDEUpdater, UpdateInfo } from '../../common/protocol/ide-updater';
+
+@injectable()
+export class IDEUpdaterCommands implements CommandContribution {
+  constructor(
+    @inject(IDEUpdater)
+    private readonly updater: IDEUpdater,
+    @inject(MessageService)
+    protected readonly messageService: MessageService
+  ) {}
+
+  registerCommands(registry: CommandRegistry): void {
+    registry.registerCommand(IDEUpdaterCommands.CHECK_FOR_UPDATES, {
+      execute: this.checkForUpdates.bind(this),
+    });
+    registry.registerCommand(IDEUpdaterCommands.DOWNLOAD_UPDATE, {
+      execute: this.downloadUpdate.bind(this),
+    });
+    registry.registerCommand(IDEUpdaterCommands.STOP_DOWNLOAD, {
+      execute: this.stopDownload.bind(this),
+    });
+    registry.registerCommand(IDEUpdaterCommands.INSTALL_UPDATE, {
+      execute: this.quitAndInstall.bind(this),
+    });
+  }
+
+  async checkForUpdates(): Promise<UpdateInfo | void> {
+    return await this.updater.checkForUpdates();
+  }
+
+  async downloadUpdate(): Promise<void> {
+    await this.updater.downloadUpdate();
+  }
+
+  async stopDownload(): Promise<void> {
+    await this.updater.stopDownload();
+  }
+
+  quitAndInstall(): void {
+    this.updater.quitAndInstall();
+  }
+}
+export namespace IDEUpdaterCommands {
+  export const CHECK_FOR_UPDATES: Command = {
+    id: 'arduino-ide-check-for-updates',
+    category: 'Arduino',
+    label: 'Check for Arduino IDE updates',
+  };
+  export const DOWNLOAD_UPDATE: Command = {
+    id: 'arduino-ide-download-update',
+    category: 'Arduino',
+    label: 'Download Arduino IDE updates',
+  };
+  export const STOP_DOWNLOAD: Command = {
+    id: 'arduino-ide-stop-download',
+    category: 'Arduino',
+    label: 'Stop download of Arduino IDE updates',
+  };
+  export const INSTALL_UPDATE: Command = {
+    id: 'arduino-ide-install-update',
+    category: 'Arduino',
+    label: 'Install Arduino IDE updates',
+  };
+}
diff --git a/arduino-ide-extension/src/browser/style/ide-logo.png b/arduino-ide-extension/src/browser/style/ide-logo.png
new file mode 100644
index 000000000..eba4b6f39
Binary files /dev/null and b/arduino-ide-extension/src/browser/style/ide-logo.png differ
diff --git a/arduino-ide-extension/src/browser/style/ide-updater-dialog.css b/arduino-ide-extension/src/browser/style/ide-updater-dialog.css
new file mode 100644
index 000000000..8215b0808
--- /dev/null
+++ b/arduino-ide-extension/src/browser/style/ide-updater-dialog.css
@@ -0,0 +1,67 @@
+.ide-updater-dialog {
+  width: 546px;
+}
+
+.ide-updater-dialog .bold {
+  font-weight: bold;
+}
+
+.ide-updater-dialog--pre-download {
+  display: flex;
+}
+
+.ide-updater-dialog--logo-container {
+  margin-right: 28px;
+}
+
+.ide-updater-dialog--logo {
+  background: url('./ide-logo.png') round;
+  width: 52px;
+  height: 52px;
+}
+
+.dialogContent.ide-updater-dialog
+  .ide-updater-dialog--content
+  .ide-updater-dialog--new-version-text.dialogSection {
+  margin-top: 0;
+}
+
+.ide-updater-dialog .changelog-container {
+  background: white;
+  border: 1px solid #dae3e3;
+  border-radius: 2px;
+  font-size: 12px;
+  height: 180px;
+  overflow: auto;
+  padding: 0 12px;
+  cursor: text;
+}
+
+.ide-updater-dialog .changelog-container a {
+  color: #018184;
+}
+
+.ide-updater-dialog .changelog-container a:hover {
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.ide-updater-dialog .changelog-container code {
+  background: #ecf1f1;
+  border-radius: 2px;
+  padding: 0 2px;
+}
+
+.ide-updater-dialog .changelog-container a code {
+  color: #018184;
+}
+
+.ide-updater-dialog .buttons-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 28px;
+}
+
+.ide-updater-dialog .buttons-container .push {
+  margin-right: auto;
+}
diff --git a/arduino-ide-extension/src/browser/style/index.css b/arduino-ide-extension/src/browser/style/index.css
index 9e52aeb07..bf2907139 100644
--- a/arduino-ide-extension/src/browser/style/index.css
+++ b/arduino-ide-extension/src/browser/style/index.css
@@ -9,6 +9,7 @@
 @import './editor.css';
 @import './settings-dialog.css';
 @import './firmware-uploader-dialog.css';
+@import './ide-updater-dialog.css';
 @import './certificate-uploader-dialog.css';
 @import './user-fields-dialog.css';
 @import './debug.css';
@@ -16,6 +17,7 @@
 @import './cloud-sketchbook.css';
 @import './fonts.css';
 @import './custom-codicon.css';
+@import './progress-bar.css';
 
 .theia-input.warning:focus {
   outline-width: 1px;
diff --git a/arduino-ide-extension/src/browser/style/progress-bar.css b/arduino-ide-extension/src/browser/style/progress-bar.css
new file mode 100644
index 000000000..2fdbd4b7f
--- /dev/null
+++ b/arduino-ide-extension/src/browser/style/progress-bar.css
@@ -0,0 +1,32 @@
+.progress-bar {
+  margin-top: 20px;
+}
+
+.progress-bar--outer {
+  background: #e5e5e5;
+  border-radius: 11px;
+  height: 6px;
+  position: relative;
+  overflow: hidden;
+}
+
+.progress-bar--inner {
+  transition: width 1s;
+  height: 100%;
+  background: #008184;
+  border-radius: 11px;
+}
+
+.progress-bar--percentage {
+  align-items: flex-end;
+  display: flex;
+  height: 40px;
+  justify-content: center;
+  margin-top: 10px;
+  width: 100%;
+}
+
+.progress-bar--percentage-text {
+  font-size: 14px;
+  line-height: 24px;
+}
diff --git a/arduino-ide-extension/src/common/protocol/ide-updater.ts b/arduino-ide-extension/src/common/protocol/ide-updater.ts
new file mode 100644
index 000000000..2cc874471
--- /dev/null
+++ b/arduino-ide-extension/src/common/protocol/ide-updater.ts
@@ -0,0 +1,71 @@
+import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
+import { Event } from '@theia/core/lib/common/event';
+import { UpdateChannel } from '../../browser/arduino-preferences';
+
+export interface ProgressInfo {
+  total: number;
+  delta: number;
+  transferred: number;
+  percent: number;
+  bytesPerSecond: number;
+}
+
+export interface ReleaseNoteInfo {
+  readonly version: string;
+  readonly note: string | null;
+}
+
+export interface BlockMapDataHolder {
+  size?: number;
+  blockMapSize?: number;
+  readonly sha512: string;
+  readonly isAdminRightsRequired?: boolean;
+}
+
+export interface UpdateFileInfo extends BlockMapDataHolder {
+  url: string;
+}
+
+export type UpdateInfo = {
+  readonly version: string;
+  readonly files: Array<UpdateFileInfo>;
+  releaseName?: string | null;
+  releaseNotes?: string | Array<ReleaseNoteInfo> | null;
+  releaseDate: string;
+  readonly stagingPercentage?: number;
+};
+
+export interface ProgressInfo {
+  total: number;
+  delta: number;
+  transferred: number;
+  percent: number;
+  bytesPerSecond: number;
+}
+
+export const IDEUpdaterPath = '/services/ide-updater';
+export const IDEUpdater = Symbol('IDEUpdater');
+export interface IDEUpdater extends JsonRpcServer<IDEUpdaterClient> {
+  init(channel: UpdateChannel): void;
+  checkForUpdates(): Promise<UpdateInfo | void>;
+  downloadUpdate(): Promise<void>;
+  quitAndInstall(): void;
+  stopDownload(): void;
+  disconnectClient(client: IDEUpdaterClient): void;
+}
+
+export const IDEUpdaterClient = Symbol('IDEUpdaterClient');
+export interface IDEUpdaterClient {
+  onError: Event<Error>;
+  onCheckingForUpdate: Event<void>;
+  onUpdateAvailable: Event<UpdateInfo>;
+  onUpdateNotAvailable: Event<UpdateInfo>;
+  onDownloadProgressChanged: Event<ProgressInfo>;
+  onDownloadFinished: Event<UpdateInfo>;
+  notifyError(message: Error): void;
+  notifyCheckingForUpdate(message: void): void;
+  notifyUpdateAvailable(message: UpdateInfo): void;
+  notifyUpdateNotAvailable(message: UpdateInfo): void;
+  notifyDownloadProgressChanged(message: ProgressInfo): void;
+  notifyDownloadFinished(message: UpdateInfo): void;
+}
diff --git a/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts b/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts
index c8ebb13ca..537030925 100644
--- a/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts
+++ b/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts
@@ -2,7 +2,10 @@ import { ContainerModule } from 'inversify';
 import { JsonRpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory';
 import { ElectronConnectionHandler } from '@theia/core/lib/electron-common/messaging/electron-connection-handler';
 import { ElectronMainWindowService } from '@theia/core/lib/electron-common/electron-main-window-service';
-import { ElectronMainApplication as TheiaElectronMainApplication } from '@theia/core/lib/electron-main/electron-main-application';
+import {
+  ElectronMainApplication as TheiaElectronMainApplication,
+  ElectronMainApplicationContribution,
+} from '@theia/core/lib/electron-main/electron-main-application';
 import {
   SplashService,
   splashServicePath,
@@ -10,6 +13,12 @@ import {
 import { SplashServiceImpl } from './splash/splash-service-impl';
 import { ElectronMainApplication } from './theia/electron-main-application';
 import { ElectronMainWindowServiceImpl } from './theia/electron-main-window-service';
+import {
+  IDEUpdater,
+  IDEUpdaterClient,
+  IDEUpdaterPath,
+} from '../common/protocol/ide-updater';
+import { IDEUpdaterImpl } from '../node/ide-updater/ide-updater-impl';
 
 export default new ContainerModule((bind, unbind, isBound, rebind) => {
   bind(ElectronMainApplication).toSelf().inSingletonScope();
@@ -28,4 +37,23 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
         )
     )
     .inSingletonScope();
+
+  // IDE updater bindings
+  bind(IDEUpdaterImpl).toSelf().inSingletonScope();
+  bind(IDEUpdater).toService(IDEUpdaterImpl);
+  bind(ElectronMainApplicationContribution).toService(IDEUpdater);
+  bind(ElectronConnectionHandler)
+    .toDynamicValue(
+      (context) =>
+        new JsonRpcConnectionHandler<IDEUpdaterClient>(
+          IDEUpdaterPath,
+          (client) => {
+            const server = context.container.get<IDEUpdater>(IDEUpdater);
+            server.setClient(client);
+            client.onDidCloseConnection(() => server.disconnectClient(client));
+            return server;
+          }
+        )
+    )
+    .inSingletonScope();
 });
diff --git a/arduino-ide-extension/src/node/grpc-client-provider.ts b/arduino-ide-extension/src/node/grpc-client-provider.ts
index 8c1ff0dc3..759c82278 100644
--- a/arduino-ide-extension/src/node/grpc-client-provider.ts
+++ b/arduino-ide-extension/src/node/grpc-client-provider.ts
@@ -70,11 +70,11 @@ export abstract class GrpcClientProvider<C> {
   protected abstract close(client: C): void;
 
   protected get channelOptions(): Record<string, unknown> {
-    const pjson = require('../../package.json') || { "version": "0.0.0" }
+    const pjson = require('../../package.json') || { version: '0.0.0' };
     return {
       'grpc.max_send_message_length': 512 * 1024 * 1024,
       'grpc.max_receive_message_length': 512 * 1024 * 1024,
-      'grpc.primary_user_agent': `arduino-ide/${pjson.version}`
+      'grpc.primary_user_agent': `arduino-ide/${pjson.version}`,
     };
   }
 }
diff --git a/arduino-ide-extension/src/node/ide-updater/ide-updater-impl.ts b/arduino-ide-extension/src/node/ide-updater/ide-updater-impl.ts
new file mode 100644
index 000000000..1b871bba9
--- /dev/null
+++ b/arduino-ide-extension/src/node/ide-updater/ide-updater-impl.ts
@@ -0,0 +1,128 @@
+import { injectable } from '@theia/core/shared/inversify';
+import { UpdateInfo, CancellationToken, autoUpdater } from 'electron-updater';
+import fetch, { Response } from 'node-fetch';
+import { UpdateChannel } from '../../browser/arduino-preferences';
+import {
+  IDEUpdater,
+  IDEUpdaterClient,
+} from '../../common/protocol/ide-updater';
+
+const CHANGELOG_BASE_URL = 'https://downloads.arduino.cc/arduino-ide/changelog';
+const IDE_DOWNLOAD_BASE_URL = 'https://downloads.arduino.cc/arduino-ide';
+
+@injectable()
+export class IDEUpdaterImpl implements IDEUpdater {
+  private updater = autoUpdater;
+  private cancellationToken?: CancellationToken;
+  protected theiaFEClient?: IDEUpdaterClient;
+  protected clients: Array<IDEUpdaterClient> = [];
+
+  init(channel: UpdateChannel) {
+    this.updater.channel = channel;
+    this.updater.setFeedURL({
+      provider: 'generic',
+      url: `${IDE_DOWNLOAD_BASE_URL}/${
+        channel === UpdateChannel.Nightly ? 'nightly' : ''
+      }`,
+      channel,
+    });
+
+    this.updater.on('checking-for-update', (e) =>
+      this.clients.forEach((c) => c.notifyCheckingForUpdate(e))
+    );
+    this.updater.on('update-available', (e) =>
+      this.clients.forEach((c) => c.notifyUpdateAvailable(e))
+    );
+    this.updater.on('update-not-available', (e) =>
+      this.clients.forEach((c) => c.notifyUpdateNotAvailable(e))
+    );
+    this.updater.on('download-progress', (e) =>
+      this.clients.forEach((c) => c.notifyDownloadProgressChanged(e))
+    );
+    this.updater.on('update-downloaded', (e) =>
+      this.clients.forEach((c) => c.notifyDownloadFinished(e))
+    );
+    this.updater.on('error', (e) =>
+      this.clients.forEach((c) => c.notifyError(e))
+    );
+  }
+
+  setClient(client: IDEUpdaterClient | undefined): void {
+    if (client) this.clients.push(client);
+  }
+
+  async checkForUpdates(): Promise<UpdateInfo | void> {
+    const {
+      updateInfo,
+      cancellationToken,
+    } = await this.updater.checkForUpdates();
+
+    this.cancellationToken = cancellationToken;
+    if (
+      this.updater.currentVersion.compare(updateInfo.version) === -1 ||
+      true
+    ) {
+      /*
+        'latest.txt' points to the latest changelog that has been generated by the CI,
+        so we need to make a first GET request to get the filename of the changelog
+        and a second GET to the actual changelog file
+      */
+      try {
+        let response: Response | null = await fetch(
+          `${CHANGELOG_BASE_URL}/latest.txt`
+        );
+        const latestChangelogFileName = response.ok
+          ? await response.text()
+          : null;
+        response = latestChangelogFileName
+          ? await fetch(`${CHANGELOG_BASE_URL}/${latestChangelogFileName}`)
+          : null;
+        const changelog = response?.ok ? await response?.text() : null;
+
+        // We only want to see the release notes of newer versions
+        const currentVersionIndex = changelog?.indexOf(
+          `\r\n\r\n---\r\n\r\n## ${this.updater.currentVersion}\r\n\r\n`
+        );
+        const newChangelog =
+          currentVersionIndex && currentVersionIndex > 0
+            ? changelog?.slice(0, currentVersionIndex)
+            : changelog;
+        updateInfo.releaseNotes = newChangelog;
+      } catch {
+        /*
+          if the request for the changelog fails, we'll just avoid to show it
+          to the user, but we will still show the update info
+        */
+      }
+      return updateInfo;
+    }
+  }
+
+  async downloadUpdate(): Promise<void> {
+    try {
+      await this.updater.downloadUpdate(this.cancellationToken);
+    } catch (e) {
+      if (e.message === 'cancelled') return;
+      throw e;
+    }
+  }
+
+  stopDownload(): void {
+    this.cancellationToken?.cancel();
+  }
+
+  quitAndInstall(): void {
+    this.updater.quitAndInstall();
+  }
+
+  disconnectClient(client: IDEUpdaterClient): void {
+    const index = this.clients.indexOf(client);
+    if (index !== -1) {
+      this.clients.splice(index, 1);
+    }
+  }
+
+  dispose(): void {
+    this.clients.forEach(this.disconnectClient.bind(this));
+  }
+}
diff --git a/browser-app/package.json b/browser-app/package.json
index 731a07623..4295971bb 100644
--- a/browser-app/package.json
+++ b/browser-app/package.json
@@ -7,7 +7,7 @@
     "@theia/core": "1.22.1",
     "@theia/debug": "1.22.1",
     "@theia/editor": "1.22.1",
-	"@theia/editor-preview": "1.22.1",
+    "@theia/editor-preview": "1.22.1",
     "@theia/file-search": "1.22.1",
     "@theia/filesystem": "1.22.1",
     "@theia/keymaps": "1.22.1",
@@ -60,4 +60,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/electron-app/package.json b/electron-app/package.json
index 5cf039a8f..a500f5eae 100644
--- a/electron-app/package.json
+++ b/electron-app/package.json
@@ -64,4 +64,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/electron/build/template-package.json b/electron/build/template-package.json
index 38bc38bc8..4f21f78ff 100644
--- a/electron/build/template-package.json
+++ b/electron/build/template-package.json
@@ -54,6 +54,8 @@
   "build": {
     "productName": "Arduino IDE",
     "asar": false,
+    "detectUpdateChannel": false,
+    "generateUpdatesFilesForAllChannels": true,
     "npmRebuild": false,
     "directories": {
       "buildResources": "resources"
@@ -91,13 +93,17 @@
       "entitlements": "resources/entitlements.mac.plist",
       "entitlementsInherit": "resources/entitlements.mac.plist",
       "target": [
-        "dmg"
+        "dmg",
+        "zip"
       ]
     },
     "linux": {
       "target": [
         {
           "target": "zip"
+        },
+        {
+          "target": "AppImage"
         }
       ],
       "category": "Development",
@@ -147,4 +153,4 @@
     "vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
     "cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix"
   }
-}
+}
\ No newline at end of file
diff --git a/electron/packager/config.js b/electron/packager/config.js
index 2bf92c362..e74d9d84d 100644
--- a/electron/packager/config.js
+++ b/electron/packager/config.js
@@ -8,87 +8,113 @@ const dateFormat = require('dateformat');
 const { isNightly, isRelease, git } = require('./utils');
 
 function artifactName() {
-    const { platform, arch } = process;
-    const id = (() => {
-        if (isRelease) {
-            return getVersion();
-        } else if (isNightly) {
-            return `nightly-${timestamp()}`
-        } else {
-            return getVersion();
+  const { platform, arch } = process;
+  const id = (() => {
+    if (isRelease) {
+      return getVersion();
+    } else if (isNightly) {
+      return `nightly-${timestamp()}`;
+    } else {
+      return getVersion();
+    }
+  })();
+  const name = 'arduino-ide';
+  switch (platform) {
+    case 'win32': {
+      if (arch === 'x64') {
+        return `${name}_${id}_Windows_64bit.\$\{ext}`;
+      }
+      throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`);
+    }
+    case 'darwin': {
+      return `${name}_${id}_macOS_64bit.\$\{ext}`;
+    }
+    case 'linux': {
+      switch (arch) {
+        case 'arm': {
+          return `${name}_${id}_Linux_ARMv7.\$\{ext}`;
         }
-    })();
-    const name = 'arduino-ide';
-    switch (platform) {
-        case 'win32': {
-            if (arch === 'x64') {
-                return `${name}_${id}_Windows_64bit.\$\{ext}`;
-            }
-            throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`);
+        case 'arm64': {
+          return `${name}_${id}_Linux_ARM64.\$\{ext}`;
         }
-        case 'darwin': {
-            return `${name}_${id}_macOS_64bit.\$\{ext}`;
+        case 'x64': {
+          return `${name}_${id}_Linux_64bit.\$\{ext}`;
         }
-        case 'linux': {
-            switch (arch) {
-                case 'arm': {
-                    return `${name}_${id}_Linux_ARMv7.\$\{ext}`;
-                }
-                case 'arm64': {
-                    return `${name}_${id}_Linux_ARM64.\$\{ext}`;
-                }
-                case 'x64': {
-                    return `${name}_${id}_Linux_64bit.\$\{ext}`;
-                }
-                default: {
-                    throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`);
-                }
-            }
+        default: {
+          throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`);
         }
-        default: throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`);
+      }
     }
+    default:
+      throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`);
+  }
 }
 
 function electronPlatform() {
-    switch (process.platform) {
-        case 'win32': {
-            return 'win';
-        }
-        case 'darwin': {
-            return 'mac';
-        }
-        case 'linux': {
-            return 'linux';
-        }
-        default: throw new Error(`Unsupported platform: ${process.platform}.`);
+  switch (process.platform) {
+    case 'win32': {
+      return 'win';
+    }
+    case 'darwin': {
+      return 'mac';
+    }
+    case 'linux': {
+      return 'linux';
     }
+    default:
+      throw new Error(`Unsupported platform: ${process.platform}.`);
+  }
 }
 
 function getVersion() {
-    const repositoryRootPath = git('rev-parse --show-toplevel');
-    let version = JSON.parse(fs.readFileSync(path.join(repositoryRootPath, 'package.json'), { encoding: 'utf8' })).version;
-    if (!semver.valid(version)) {
-        throw new Error(`Could not read version from root package.json. Version was: '${version}'.`);
+  const repositoryRootPath = git('rev-parse --show-toplevel');
+  let version = JSON.parse(
+    fs.readFileSync(path.join(repositoryRootPath, 'package.json'), {
+      encoding: 'utf8',
+    })
+  ).version;
+  if (!semver.valid(version)) {
+    throw new Error(
+      `Could not read version from root package.json. Version was: '${version}'.`
+    );
+  }
+  if (!isRelease) {
+    if (isNightly) {
+      version = `${version}-nightly.${timestamp()}`;
+    } else {
+      version = `${version}-snapshot.${currentCommitish()}`;
     }
     if (!isRelease) {
-        if (isNightly) {
-            version = `${version}-nightly-${timestamp()}`;
-        } else {
-            version = `${version}-snapshot-${currentCommitish()}`;
-        }
-        if (!semver.valid(version)) {
-            throw new Error(`Invalid patched version: '${version}'.`);
-        }
+      if (isNightly) {
+        version = `${version}-nightly-${timestamp()}`;
+      } else {
+        version = `${version}-snapshot-${currentCommitish()}`;
+      }
+      if (!semver.valid(version)) {
+        throw new Error(`Invalid patched version: '${version}'.`);
+      }
     }
-    return version;
+  }
+  return version;
+}
+
+function getChannel() {
+  if (isRelease) {
+    return 'stable';
+  }
+  if (isNightly) {
+    return 'nightly';
+  }
+
+  return 'none';
 }
 
 function timestamp() {
-    return dateFormat(new Date(), 'yyyymmdd');
+  return dateFormat(new Date(), 'yyyymmdd');
 }
 
 function currentCommitish() {
-    return git('rev-parse --short HEAD');
+  return git('rev-parse --short HEAD');
 }
 
 // function currentBranch() {
@@ -96,28 +122,38 @@ function currentCommitish() {
 // }
 
 function generateTemplate(buildDate) {
-    // do `export PUBLISH=true yarn package` if you want to mimic CI build locally.
-    // const electronPublish = release || (isCI && currentBranch() === 'main') || process.env.PUBLISH === 'true';
-    const version = getVersion();
-    const productName = 'Arduino IDE';
-    const name = 'arduino-ide';
-    let customizations = {
-        name,
-        description: productName,
-        version,
-        build: {
-            productName,
-            appId: 'arduino.ProIDE',
-            [electronPlatform()]: {
-                artifactName: artifactName()
-            }
-        }
-    };
-    if (buildDate) {
-        customizations = merge(customizations, { theia: { frontend: { config: { buildDate } } } });
-    }
-    const template = require('../build/template-package.json');
-    return merge(template, customizations);
+  // do `export PUBLISH=true yarn package` if you want to mimic CI build locally.
+  // const electronPublish = release || (isCI && currentBranch() === 'main') || process.env.PUBLISH === 'true';
+  const version = getVersion();
+  const productName = 'Arduino IDE';
+  const name = 'arduino-ide';
+  const updateChannel = getChannel();
+  let customizations = {
+    name,
+    description: productName,
+    version,
+    theia: {
+      frontend: {
+        config: {
+          'arduino.ide.updateChannel': updateChannel,
+        },
+      },
+    },
+    build: {
+      productName,
+      appId: 'cc.arduino.IDE2',
+      [electronPlatform()]: {
+        artifactName: artifactName(),
+      },
+    },
+  };
+  if (buildDate) {
+    customizations = merge(customizations, {
+      theia: { frontend: { config: { buildDate } } },
+    });
+  }
+  const template = require('../build/template-package.json');
+  return merge(template, customizations);
 }
 
 module.exports = { generateTemplate };
diff --git a/electron/packager/index.js b/electron/packager/index.js
index 70ddc014f..17fc272a7 100644
--- a/electron/packager/index.js
+++ b/electron/packager/index.js
@@ -13,7 +13,7 @@
     const template = require('./config').generateTemplate(new Date().toISOString());
     const utils = require('./utils');
     const merge = require('deepmerge');
-    const { isRelease, isElectronPublish } = utils;
+    const { isRelease, isElectronPublish, getChannelFile } = utils;
     const { version } = template;
     const { productName } = template.build;
 
@@ -277,6 +277,13 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
         const targetFolder = path('..', 'build', 'dist', 'build-artifacts');
         mkdir('-p', targetFolder);
         const filesToCopy = [];
+        const channelFile = getChannelFile(platform);
+        // Channel file might be an empty string if we're not building a
+        // nightly or a full release. This can happen when building a package
+        // locally or a tester build when creating a new PR on GH.
+        if (channelFile) {
+            filesToCopy.push(channelFile)
+        }
         switch (platform) {
             case 'linux': {
                 filesToCopy.push(...glob.sync('**/arduino-ide*.{zip,AppImage}', { cwd }).map(p => join(cwd, p)));
diff --git a/electron/packager/utils.js b/electron/packager/utils.js
index 40001782f..d8e7faf14 100644
--- a/electron/packager/utils.js
+++ b/electron/packager/utils.js
@@ -12,35 +12,42 @@ const fromFile = require('file-type').fromFile;
  * Resolves to an array of `npm` package names that are declared in the `package.json` but **not** used by the project.
  */
 function collectUnusedDependencies(pathToProject = process.cwd()) {
-    const p = path.isAbsolute(pathToProject) ? pathToProject : path.resolve(process.cwd(), pathToProject);
-    console.log(`⏱️  >>> Collecting unused backend dependencies for ${p}...`);
-    return new Promise(resolve => {
-        depcheck(p, {
-            ignoreDirs: [
-                'frontend'
-            ],
-            parsers: {
-                '*.js': depcheck.parser.es6,
-                '*.jsx': depcheck.parser.jsx
-            },
-            detectors: [
-                depcheck.detector.requireCallExpression,
-                depcheck.detector.importDeclaration
-            ],
-            specials: [
-                depcheck.special.eslint,
-                depcheck.special.webpack
-            ]
-        }, unused => {
-            const { dependencies } = unused
-            if (dependencies && dependencies.length > 0) {
-                console.log(`👌  <<< The following unused dependencies have been found: ${JSON.stringify(dependencies, null, 2)}`);
-            } else {
-                console.log('👌  <<< No unused dependencies have been found.');
-            }
-            resolve(dependencies);
-        });
-    })
+  const p = path.isAbsolute(pathToProject)
+    ? pathToProject
+    : path.resolve(process.cwd(), pathToProject);
+  console.log(`⏱️  >>> Collecting unused backend dependencies for ${p}...`);
+  return new Promise((resolve) => {
+    depcheck(
+      p,
+      {
+        ignoreDirs: ['frontend'],
+        parsers: {
+          '*.js': depcheck.parser.es6,
+          '*.jsx': depcheck.parser.jsx,
+        },
+        detectors: [
+          depcheck.detector.requireCallExpression,
+          depcheck.detector.importDeclaration,
+        ],
+        specials: [depcheck.special.eslint, depcheck.special.webpack],
+      },
+      (unused) => {
+        const { dependencies } = unused;
+        if (dependencies && dependencies.length > 0) {
+          console.log(
+            `👌  <<< The following unused dependencies have been found: ${JSON.stringify(
+              dependencies,
+              null,
+              2
+            )}`
+          );
+        } else {
+          console.log('👌  <<< No unused dependencies have been found.');
+        }
+        resolve(dependencies);
+      }
+    );
+  });
 }
 
 /**
@@ -50,101 +57,111 @@ function collectUnusedDependencies(pathToProject = process.cwd()) {
  * If `pathToZip` is not a ZIP, rejects. `targetFolderName` is the destination folder not the new archive location.
  */
 function adjustArchiveStructure(pathToZip, targetFolderName, noCleanup) {
-    return new Promise(async (resolve, reject) => {
-        if (!await isZip(pathToZip)) {
-            reject(new Error(`Expected a ZIP file.`));
-            return;
-        }
-        if (!fs.existsSync(targetFolderName)) {
-            reject(new Error(`${targetFolderName} does not exist.`));
-            return;
-        }
-        if (!fs.lstatSync(targetFolderName).isDirectory()) {
-            reject(new Error(`${targetFolderName} is not a directory.`));
-            return;
-        }
-        console.log(`⏱️  >>> Adjusting ZIP structure ${pathToZip}...`);
+  return new Promise(async (resolve, reject) => {
+    if (!(await isZip(pathToZip))) {
+      reject(new Error(`Expected a ZIP file.`));
+      return;
+    }
+    if (!fs.existsSync(targetFolderName)) {
+      reject(new Error(`${targetFolderName} does not exist.`));
+      return;
+    }
+    if (!fs.lstatSync(targetFolderName).isDirectory()) {
+      reject(new Error(`${targetFolderName} is not a directory.`));
+      return;
+    }
+    console.log(`⏱️  >>> Adjusting ZIP structure ${pathToZip}...`);
 
-        const root = basename(pathToZip);
-        const resources = await list(pathToZip);
-        const hasBaseFolder = resources.find(name => name === root);
-        if (hasBaseFolder) {
-            if (resources.filter(name => name.indexOf(path.sep) === -1).length > 1) {
-                console.warn(`${pathToZip} ZIP has the desired root folder ${root}, however the ZIP contains other entries too: ${JSON.stringify(resources)}`);
-            }
-            console.log(`👌  <<< The ZIP already has the desired ${root} folder.`);
-            resolve(pathToZip);
-            return;
-        }
+    const root = basename(pathToZip);
+    const resources = await list(pathToZip);
+    const hasBaseFolder = resources.find((name) => name === root);
+    if (hasBaseFolder) {
+      if (
+        resources.filter((name) => name.indexOf(path.sep) === -1).length > 1
+      ) {
+        console.warn(
+          `${pathToZip} ZIP has the desired root folder ${root}, however the ZIP contains other entries too: ${JSON.stringify(
+            resources
+          )}`
+        );
+      }
+      console.log(`👌  <<< The ZIP already has the desired ${root} folder.`);
+      resolve(pathToZip);
+      return;
+    }
 
-        const track = temp.track();
-        try {
-            const unzipOut = path.join(track.mkdirSync(), root);
-            fs.mkdirSync(unzipOut);
-            await unpack(pathToZip, unzipOut);
-            const adjustedZip = path.join(targetFolderName, path.basename(pathToZip));
-            await pack(unzipOut, adjustedZip);
-            console.log(`👌  <<< Adjusted the ZIP structure. Moved the modified ${basename(pathToZip)} to the ${targetFolderName} folder.`);
-            resolve(adjustedZip);
-        } finally {
-            if (!noCleanup) {
-                track.cleanupSync();
-            }
-        }
-    });
+    const track = temp.track();
+    try {
+      const unzipOut = path.join(track.mkdirSync(), root);
+      fs.mkdirSync(unzipOut);
+      await unpack(pathToZip, unzipOut);
+      const adjustedZip = path.join(targetFolderName, path.basename(pathToZip));
+      await pack(unzipOut, adjustedZip);
+      console.log(
+        `👌  <<< Adjusted the ZIP structure. Moved the modified ${basename(
+          pathToZip
+        )} to the ${targetFolderName} folder.`
+      );
+      resolve(adjustedZip);
+    } finally {
+      if (!noCleanup) {
+        track.cleanupSync();
+      }
+    }
+  });
 }
 
 /**
  * Returns the `basename` of `pathToFile` without the file extension.
  */
 function basename(pathToFile) {
-    const name = path.basename(pathToFile);
-    const ext = path.extname(pathToFile);
-    return name.substr(0, name.length - ext.length);
+  const name = path.basename(pathToFile);
+  const ext = path.extname(pathToFile);
+  return name.substr(0, name.length - ext.length);
 }
 
 function unpack(what, where) {
-    return new Promise((resolve, reject) => {
-        zip.unpack(what, where, error => {
-            if (error) {
-                reject(error);
-                return;
-            }
-            resolve();
-        })
+  return new Promise((resolve, reject) => {
+    zip.unpack(what, where, (error) => {
+      if (error) {
+        reject(error);
+        return;
+      }
+      resolve();
     });
+  });
 }
 
 function pack(what, where) {
-    return new Promise((resolve, reject) => {
-        zip.pack(what, where, error => {
-            if (error) {
-                reject(error);
-                return;
-            }
-            resolve();
-        })
+  return new Promise((resolve, reject) => {
+    zip.pack(what, where, (error) => {
+      if (error) {
+        reject(error);
+        return;
+      }
+      resolve();
     });
+  });
 }
 
 function list(what) {
-    return new Promise((resolve, reject) => {
-        zip.list(what, (error, result) => {
-            if (error) {
-                reject(error);
-                return;
-            }
-            resolve(result.map(({ name }) => name));
-        })
+  return new Promise((resolve, reject) => {
+    zip.list(what, (error, result) => {
+      if (error) {
+        reject(error);
+        return;
+      }
+      resolve(result.map(({ name }) => name));
     });
+  });
 }
 
 async function isZip(pathToFile) {
-    if (!fs.existsSync(pathToFile)) {
-        throw new Error(`${pathToFile} does not exist`);
-    }
-    const type = await fromFile(pathToFile);
-    return type && type.ext === 'zip';
+  if (!fs.existsSync(pathToFile)) {
+    throw new Error(`${pathToFile} does not exist`);
+  }
+  const type = await fromFile(pathToFile);
+  return type && type.ext === 'zip';
 }
 
 const isElectronPublish = false; // TODO: support auto-updates
@@ -152,20 +169,62 @@ const isNightly = process.env.IS_NIGHTLY === 'true';
 const isRelease = process.env.IS_RELEASE === 'true';
 
 function git(command) {
-    try {
-        const gitPath = shell.which('git');
-        const error = shell.error();
-        if (error) {
-            throw new Error(error);
-        }
-        const { stderr, stdout } = shell.exec(`"${gitPath}" ${command}`, { silent: true });
-        if (stderr) {
-            throw new Error(stderr.toString().trim());
-        }
-        return stdout.toString().trim();
-    } catch (e) {
-        throw e;
+  try {
+    const gitPath = shell.which('git');
+    const error = shell.error();
+    if (error) {
+      throw new Error(error);
+    }
+    const { stderr, stdout } = shell.exec(`"${gitPath}" ${command}`, {
+      silent: true,
+    });
+    if (stderr) {
+      throw new Error(stderr.toString().trim());
     }
+    return stdout.toString().trim();
+  } catch (e) {
+    throw e;
+  }
+}
+
+// getChannelFile returns the name of the channel file to be released
+// together with the IDE file.
+// The channel file depends on the platform and whether we're creating
+// a nightly build or a full release.
+// In all other cases, like when building a tester build for a PR,
+// an empty string is returned since we don't need a channel file.
+// The channel files are necessary for updates check with electron-updater
+// to work correctly.
+// For more information: https://www.electron.build/auto-update
+function getChannelFile(platform) {
+  let currentChannel = '';
+  if (isNightly) {
+    currentChannel = 'nightly';
+  } else if (isRelease) {
+    currentChannel = 'stable';
+  } else {
+    // We're not creating a nightly build nor releasing
+    // a new version, no need for a channel file.
+    return '';
+  }
+  return (
+    currentChannel +
+    {
+      linux: '-linux.yml',
+      win32: '.yml',
+      darwin: '-mac.yml',
+    }[platform]
+  );
 }
 
-module.exports = { collectUnusedDependencies, adjustArchiveStructure, isZip, unpack, isNightly, isRelease, isElectronPublish, git };
+module.exports = {
+  collectUnusedDependencies,
+  adjustArchiveStructure,
+  isZip,
+  unpack,
+  isNightly,
+  isRelease,
+  isElectronPublish,
+  git,
+  getChannelFile,
+};
diff --git a/electron/packager/yarn.lock b/electron/packager/yarn.lock
index 473faa674..396dd6c1d 100644
--- a/electron/packager/yarn.lock
+++ b/electron/packager/yarn.lock
@@ -1679,4 +1679,4 @@ yargs@^15.0.2:
     string-width "^4.2.0"
     which-module "^2.0.0"
     y18n "^4.0.0"
-    yargs-parser "^18.1.1"
+    yargs-parser "^18.1.1"
\ No newline at end of file
diff --git a/i18n/en.json b/i18n/en.json
index dd93d03a7..614dbdf2b 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -31,7 +31,7 @@
             "upload.verbose": "True for verbose upload output. False by default.",
             "window.autoScale": "True if the user interface automatically scales with the font size.",
             "window.zoomLevel": "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.",
-            "ide.autoUpdate": "True to enable automatic update checks. The IDE will check for updates automatically and periodically.",
+            "ide.updateChannel": "Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build.",
             "board.certificates": "List of certificates that can be uploaded to boards",
             "sketchbook.showAllFiles": "True to show all sketch files inside the sketch. It is false by default.",
             "cloud.enabled": "True if the sketch sync functions are enabled. Defaults to true.",
@@ -55,7 +55,6 @@
             "compile": "compile",
             "upload": "upload",
             "verifyAfterUpload": "Verify code after upload",
-            "checkForUpdates": "Check for updates on startup",
             "editorQuickSuggestions": "Editor Quick Suggestions",
             "additionalManagerURLs": "Additional Boards Manager URLs",
             "noProxy": "No proxy",
@@ -256,6 +255,20 @@
         "dialog": {
             "dontAskAgain": "Don't ask again"
         },
+        "ide-updater": {
+            "notNowButton": "Not now",
+            "versionDownloaded": "Arduino IDE {0} has been downloaded.",
+            "closeToInstallNotice": "Close the software and install the update on your machine.",
+            "closeAndInstallButton": "Close and Install",
+            "downloadingNotice": "Downloading the latest version of the Arduino IDE.",
+            "updateAvailable": "Update Available",
+            "newVersionAvailable": "A new version of Arduino IDE ({0}) is available for download.",
+            "skipVersionButton": "Skip Version",
+            "downloadButton": "Download"
+        },
+        "updater": {
+            "ideUpdaterDialog": "Software Update"
+        },
         "userFields": {
             "cancel": "Cancel",
             "upload": "Upload"
diff --git a/yarn.lock b/yarn.lock
index ad3488736..56e34bc9b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2912,6 +2912,13 @@
   resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc"
   integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==
 
+"@types/debug@^4.0.0":
+  version "4.1.7"
+  resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
+  integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==
+  dependencies:
+    "@types/ms" "*"
+
 "@types/deepmerge@^2.2.0":
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/@types/deepmerge/-/deepmerge-2.2.0.tgz#6f63896c217f3164782f52d858d9f3a927139f64"
@@ -3015,6 +3022,13 @@
   resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.7.4.tgz#1621c50ceaf5aefa699851da8e0ea606a2943a39"
   integrity sha512-6PjMFKl13cgB4kRdYtvyjKl8VVa0PXS2IdVxHhQ8GEKbxBkyJtSbaIeK1eZGjDKN7dvUh4vkOvU9FMwYNv4GQQ==
 
+"@types/hast@^2.0.0":
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
+  integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==
+  dependencies:
+    "@types/unist" "*"
+
 "@types/js-yaml@^3.12.2":
   version "3.12.6"
   resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.6.tgz#7f10c926aa41e189a2755c4c7fcf8e4573bd7ac1"
@@ -3091,7 +3105,14 @@
     "@types/linkify-it" "*"
     "@types/mdurl" "*"
 
-"@types/mdurl@*":
+"@types/mdast@^3.0.0":
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
+  integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==
+  dependencies:
+    "@types/unist" "*"
+
+"@types/mdurl@*", "@types/mdurl@^1.0.0":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
   integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
@@ -3140,6 +3161,11 @@
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
   integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
 
+"@types/ms@*":
+  version "0.7.31"
+  resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
+  integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
+
 "@types/multer@^1.4.7":
   version "1.4.7"
   resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e"
@@ -3337,7 +3363,7 @@
   resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
   integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
 
-"@types/semver@^7.3.8":
+"@types/semver@^7.3.6", "@types/semver@^7.3.8":
   version "7.3.9"
   resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
   integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
@@ -3410,6 +3436,11 @@
   resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.0.tgz#aee6e868fcef74f2b8c71614b6df81a601a42f17"
   integrity sha512-I8MnZqNXsOLHsU111oHbn3khtvKMi5Bn4qVFsIWSJcCP1KKDiXX5AEw8UPk0nSopeC+Hvxt6yAy1/a5PailFqg==
 
+"@types/unist@*", "@types/unist@^2.0.0":
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
+  integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+
 "@types/uuid@^7.0.3":
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.4.tgz#00a5749810b4ad80bff73a61f9cc9d0d521feb3c"
@@ -4473,6 +4504,11 @@ back@~0.1.5:
   resolved "https://registry.yarnpkg.com/back/-/back-0.1.5.tgz#342b96b804657b03ec9a31f248a11f200608dcc2"
   integrity sha1-NCuWuARlewPsmjHySKEfIAYI3MI=
 
+bail@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d"
+  integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
+
 balanced-match@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -4721,6 +4757,14 @@ buffers@~0.1.1:
   resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
   integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s=
 
+builder-util-runtime@8.9.2:
+  version "8.9.2"
+  resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz#a9669ae5b5dcabfe411ded26678e7ae997246c28"
+  integrity sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==
+  dependencies:
+    debug "^4.3.2"
+    sax "^1.2.4"
+
 builtins@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
@@ -4988,6 +5032,11 @@ changes-stream@^2.2.0:
     http-https "~1.0.0"
     readable-stream "1.0.x"
 
+character-entities@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.1.tgz#98724833e1e27990dee0bd0f2b8a859c3476aac7"
+  integrity sha512-OzmutCf2Kmc+6DrFrrPS8/tDh2+DpnrfzdICHWhcVC9eOd0N1PXmQEE1a8iM4IziIAG+8tmTq3K+oo0ubH6RRQ==
+
 chardet@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -5252,6 +5301,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
+comma-separated-tokens@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98"
+  integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==
+
 commander@^2.20.0, commander@^2.8.1:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -5767,6 +5821,13 @@ debug@^3.1.0, debug@^3.2.6:
   dependencies:
     ms "^2.1.1"
 
+debug@^4.0.0, debug@^4.3.2:
+  version "4.3.3"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
+  integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
+  dependencies:
+    ms "2.1.2"
+
 debug@~0.8.0:
   version "0.8.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-0.8.1.tgz#20ff4d26f5e422cb68a1bacbbb61039ad8c1c130"
@@ -5790,6 +5851,13 @@ decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
+decode-named-character-reference@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.1.tgz#57b2bd9112659cacbc449d3577d7dadb8e1f3d1b"
+  integrity sha512-YV/0HQHreRwKb7uBopyIkLG17jG6Sv2qUchk9qSoVJ2f+flwRsPNBO0hAnjt6mTNYUT+vw9Gy2ihXg4sUWPi2w==
+  dependencies:
+    character-entities "^2.0.0"
+
 decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -5977,6 +6045,11 @@ deprecation@^2.0.0, deprecation@^2.3.1:
   resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
   integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
 
+dequal@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
+  integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
+
 destroy@~1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
@@ -6210,6 +6283,20 @@ electron-to-chromium@^1.3.649:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.695.tgz#955f419cf99137226180cc4cca2e59015a4e248d"
   integrity sha512-lz66RliUqLHU1Ojxx1A4QUxKydjiQ79Y4dZyPobs2Dmxj5aVL2TM3KoQ2Gs7HS703Bfny+ukI3KOxwAB0xceHQ==
 
+electron-updater@^4.6.5:
+  version "4.6.5"
+  resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.6.5.tgz#e9a75458bbfd6bb41a58a829839e150ad2eb2d3d"
+  integrity sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA==
+  dependencies:
+    "@types/semver" "^7.3.6"
+    builder-util-runtime "8.9.2"
+    fs-extra "^10.0.0"
+    js-yaml "^4.1.0"
+    lazy-val "^1.0.5"
+    lodash.escaperegexp "^4.1.2"
+    lodash.isequal "^4.5.0"
+    semver "^7.3.5"
+
 electron@^15.3.5:
   version "15.3.6"
   resolved "https://registry.yarnpkg.com/electron/-/electron-15.3.6.tgz#19b9aee1e063b1983b3d7f535567d90e0e1b4d04"
@@ -6760,7 +6847,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
     assign-symbols "^1.0.0"
     is-extendable "^1.0.1"
 
-extend@^3.0.2, extend@~3.0.2:
+extend@^3.0.0, extend@^3.0.2, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -7200,6 +7287,15 @@ fs-exists-sync@^0.1.0:
   resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
   integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
 
+fs-extra@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1"
+  integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==
+  dependencies:
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
 fs-extra@^4.0.2:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
@@ -7893,6 +7989,11 @@ hash.js@^1.1.7:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
+hast-util-whitespace@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c"
+  integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==
+
 he@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -8242,6 +8343,11 @@ init-package-json@^1.10.3:
     validate-npm-package-license "^3.0.1"
     validate-npm-package-name "^3.0.0"
 
+inline-style-parser@0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
+  integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
+
 inquirer@^6.2.0:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
@@ -8356,7 +8462,7 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
-is-buffer@~2.0.3:
+is-buffer@^2.0.0, is-buffer@~2.0.3:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
   integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
@@ -8582,6 +8688,11 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
 
+is-plain-obj@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.0.0.tgz#06c0999fd7574edf5a906ba5644ad0feb3a84d22"
+  integrity sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw==
+
 is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -8787,6 +8898,13 @@ js-yaml@^3.13.1:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
+js-yaml@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+  integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+  dependencies:
+    argparse "^2.0.1"
+
 jsbn@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
@@ -8915,6 +9033,15 @@ jsonfile@^4.0.0:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+jsonfile@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+  integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+  dependencies:
+    universalify "^2.0.0"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonparse@^1.2.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@@ -9002,6 +9129,11 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
+kleur@^4.0.3:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d"
+  integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==
+
 lazy-cache@^2.0.1, lazy-cache@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
@@ -9009,6 +9141,11 @@ lazy-cache@^2.0.1, lazy-cache@^2.0.2:
   dependencies:
     set-getter "^0.1.0"
 
+lazy-val@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d"
+  integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==
+
 lcid@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -9250,11 +9387,21 @@ lodash.debounce@^4.0.8:
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
   integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
 
+lodash.escaperegexp@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
+  integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
+
 lodash.get@^4.4.2:
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
   integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
 
+lodash.isequal@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+  integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
 lodash.ismatch@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
@@ -9534,7 +9681,55 @@ matcher@^3.0.0:
   dependencies:
     escape-string-regexp "^4.0.0"
 
-mdurl@^1.0.1:
+mdast-util-definitions@^5.0.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817"
+  integrity sha512-5hcR7FL2EuZ4q6lLMUK5w4lHT2H3vqL9quPvYZ/Ku5iifrirfMHiGdhxdXMUbUkDmz5I+TYMd7nbaxUhbQkfpQ==
+  dependencies:
+    "@types/mdast" "^3.0.0"
+    "@types/unist" "^2.0.0"
+    unist-util-visit "^3.0.0"
+
+mdast-util-from-markdown@^1.0.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz#84df2924ccc6c995dec1e2368b2b208ad0a76268"
+  integrity sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==
+  dependencies:
+    "@types/mdast" "^3.0.0"
+    "@types/unist" "^2.0.0"
+    decode-named-character-reference "^1.0.0"
+    mdast-util-to-string "^3.1.0"
+    micromark "^3.0.0"
+    micromark-util-decode-numeric-character-reference "^1.0.0"
+    micromark-util-decode-string "^1.0.0"
+    micromark-util-normalize-identifier "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+    unist-util-stringify-position "^3.0.0"
+    uvu "^0.5.0"
+
+mdast-util-to-hast@^12.1.0:
+  version "12.1.1"
+  resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.1.1.tgz#89a2bb405eaf3b05eb8bf45157678f35eef5dbca"
+  integrity sha512-qE09zD6ylVP14jV4mjLIhDBOrpFdShHZcEsYvvKGABlr9mGbV7mTlRWdoFxL/EYSTNDiC9GZXy7y8Shgb9Dtzw==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    "@types/mdast" "^3.0.0"
+    "@types/mdurl" "^1.0.0"
+    mdast-util-definitions "^5.0.0"
+    mdurl "^1.0.0"
+    micromark-util-sanitize-uri "^1.0.0"
+    unist-builder "^3.0.0"
+    unist-util-generated "^2.0.0"
+    unist-util-position "^4.0.0"
+    unist-util-visit "^4.0.0"
+
+mdast-util-to-string@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz#56c506d065fbf769515235e577b5a261552d56e9"
+  integrity sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==
+
+mdurl@^1.0.0, mdurl@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
   integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
@@ -9631,6 +9826,201 @@ methods@^1.1.2, methods@~1.1.2:
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
 
+micromark-core-commonmark@^1.0.1:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz#edff4c72e5993d93724a3c206970f5a15b0585ad"
+  integrity sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==
+  dependencies:
+    decode-named-character-reference "^1.0.0"
+    micromark-factory-destination "^1.0.0"
+    micromark-factory-label "^1.0.0"
+    micromark-factory-space "^1.0.0"
+    micromark-factory-title "^1.0.0"
+    micromark-factory-whitespace "^1.0.0"
+    micromark-util-character "^1.0.0"
+    micromark-util-chunked "^1.0.0"
+    micromark-util-classify-character "^1.0.0"
+    micromark-util-html-tag-name "^1.0.0"
+    micromark-util-normalize-identifier "^1.0.0"
+    micromark-util-resolve-all "^1.0.0"
+    micromark-util-subtokenize "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.1"
+    uvu "^0.5.0"
+
+micromark-factory-destination@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e"
+  integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==
+  dependencies:
+    micromark-util-character "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+
+micromark-factory-label@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz#6be2551fa8d13542fcbbac478258fb7a20047137"
+  integrity sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==
+  dependencies:
+    micromark-util-character "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+    uvu "^0.5.0"
+
+micromark-factory-space@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz#cebff49968f2b9616c0fcb239e96685cb9497633"
+  integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==
+  dependencies:
+    micromark-util-character "^1.0.0"
+    micromark-util-types "^1.0.0"
+
+micromark-factory-title@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz#7e09287c3748ff1693930f176e1c4a328382494f"
+  integrity sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==
+  dependencies:
+    micromark-factory-space "^1.0.0"
+    micromark-util-character "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+    uvu "^0.5.0"
+
+micromark-factory-whitespace@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz#e991e043ad376c1ba52f4e49858ce0794678621c"
+  integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==
+  dependencies:
+    micromark-factory-space "^1.0.0"
+    micromark-util-character "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+
+micromark-util-character@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.1.0.tgz#d97c54d5742a0d9611a68ca0cd4124331f264d86"
+  integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==
+  dependencies:
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+
+micromark-util-chunked@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06"
+  integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==
+  dependencies:
+    micromark-util-symbol "^1.0.0"
+
+micromark-util-classify-character@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz#cbd7b447cb79ee6997dd274a46fc4eb806460a20"
+  integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==
+  dependencies:
+    micromark-util-character "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+
+micromark-util-combine-extensions@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz#91418e1e74fb893e3628b8d496085639124ff3d5"
+  integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==
+  dependencies:
+    micromark-util-chunked "^1.0.0"
+    micromark-util-types "^1.0.0"
+
+micromark-util-decode-numeric-character-reference@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz#dcc85f13b5bd93ff8d2868c3dba28039d490b946"
+  integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==
+  dependencies:
+    micromark-util-symbol "^1.0.0"
+
+micromark-util-decode-string@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz#942252ab7a76dec2dbf089cc32505ee2bc3acf02"
+  integrity sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==
+  dependencies:
+    decode-named-character-reference "^1.0.0"
+    micromark-util-character "^1.0.0"
+    micromark-util-decode-numeric-character-reference "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+
+micromark-util-encode@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383"
+  integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==
+
+micromark-util-html-tag-name@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.0.0.tgz#75737e92fef50af0c6212bd309bc5cb8dbd489ed"
+  integrity sha512-NenEKIshW2ZI/ERv9HtFNsrn3llSPZtY337LID/24WeLqMzeZhBEE6BQ0vS2ZBjshm5n40chKtJ3qjAbVV8S0g==
+
+micromark-util-normalize-identifier@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz#4a3539cb8db954bbec5203952bfe8cedadae7828"
+  integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==
+  dependencies:
+    micromark-util-symbol "^1.0.0"
+
+micromark-util-resolve-all@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz#a7c363f49a0162e931960c44f3127ab58f031d88"
+  integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==
+  dependencies:
+    micromark-util-types "^1.0.0"
+
+micromark-util-sanitize-uri@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz#27dc875397cd15102274c6c6da5585d34d4f12b2"
+  integrity sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==
+  dependencies:
+    micromark-util-character "^1.0.0"
+    micromark-util-encode "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+
+micromark-util-subtokenize@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105"
+  integrity sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==
+  dependencies:
+    micromark-util-chunked "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.0"
+    uvu "^0.5.0"
+
+micromark-util-symbol@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e"
+  integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==
+
+micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20"
+  integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==
+
+micromark@^3.0.0:
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.0.10.tgz#1eac156f0399d42736458a14b0ca2d86190b457c"
+  integrity sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==
+  dependencies:
+    "@types/debug" "^4.0.0"
+    debug "^4.0.0"
+    decode-named-character-reference "^1.0.0"
+    micromark-core-commonmark "^1.0.1"
+    micromark-factory-space "^1.0.0"
+    micromark-util-character "^1.0.0"
+    micromark-util-chunked "^1.0.0"
+    micromark-util-combine-extensions "^1.0.0"
+    micromark-util-decode-numeric-character-reference "^1.0.0"
+    micromark-util-encode "^1.0.0"
+    micromark-util-normalize-identifier "^1.0.0"
+    micromark-util-resolve-all "^1.0.0"
+    micromark-util-sanitize-uri "^1.0.0"
+    micromark-util-subtokenize "^1.0.0"
+    micromark-util-symbol "^1.0.0"
+    micromark-util-types "^1.0.1"
+    uvu "^0.5.0"
+
 micromatch@^3.1.10, micromatch@^3.1.4:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -9914,6 +10304,11 @@ move-file@^1.1.0:
     make-dir "^3.0.0"
     path-exists "^3.0.0"
 
+mri@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
+  integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -11210,6 +11605,15 @@ promzard@^0.3.0:
   dependencies:
     read "1"
 
+prop-types@^15.0.0:
+  version "15.8.1"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+  integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+  dependencies:
+    loose-envify "^1.4.0"
+    object-assign "^4.1.1"
+    react-is "^16.13.1"
+
 prop-types@^15.5.0, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
@@ -11219,6 +11623,11 @@ prop-types@^15.5.0, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0,
     object-assign "^4.1.1"
     react-is "^16.8.1"
 
+property-information@^6.0.0:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22"
+  integrity sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==
+
 proto-list@~1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@@ -11497,16 +11906,41 @@ react-input-autosize@^3.0.0:
   dependencies:
     prop-types "^15.5.8"
 
-react-is@^16.8.1:
+react-is@^16.13.1, react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
+react-is@^17.0.0:
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
 react-lifecycles-compat@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
+react-markdown@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.0.tgz#3243296a59ddb0f451d262cc2e11123674b416c2"
+  integrity sha512-qbrWpLny6Ef2xHqnYqtot948LXP+4FtC+MWIuaN1kvSnowM+r1qEeEHpSaU0TDBOisQuj+Qe6eFY15cNL3gLAw==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    "@types/unist" "^2.0.0"
+    comma-separated-tokens "^2.0.0"
+    hast-util-whitespace "^2.0.0"
+    prop-types "^15.0.0"
+    property-information "^6.0.0"
+    react-is "^17.0.0"
+    remark-parse "^10.0.0"
+    remark-rehype "^10.0.0"
+    space-separated-tokens "^2.0.0"
+    style-to-object "^0.3.0"
+    unified "^10.0.0"
+    unist-util-visit "^4.0.0"
+    vfile "^5.0.0"
+
 react-perfect-scrollbar@^1.5.3:
   version "1.5.8"
   resolved "https://registry.yarnpkg.com/react-perfect-scrollbar/-/react-perfect-scrollbar-1.5.8.tgz#380959387a325c5c9d0268afc08b3f73ed5b3078"
@@ -11863,6 +12297,25 @@ relative@^3.0.2:
   dependencies:
     isobject "^2.0.0"
 
+remark-parse@^10.0.0:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775"
+  integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==
+  dependencies:
+    "@types/mdast" "^3.0.0"
+    mdast-util-from-markdown "^1.0.0"
+    unified "^10.0.0"
+
+remark-rehype@^10.0.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-10.1.0.tgz#32dc99d2034c27ecaf2e0150d22a6dcccd9a6279"
+  integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    "@types/mdast" "^3.0.0"
+    mdast-util-to-hast "^12.1.0"
+    unified "^10.0.0"
+
 remarkable@^1.6.2, remarkable@^1.7.1:
   version "1.7.4"
   resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00"
@@ -12128,6 +12581,13 @@ rxjs@^6.6.7:
   dependencies:
     tslib "^1.9.0"
 
+sade@^1.7.3:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
+  integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
+  dependencies:
+    mri "^1.1.0"
+
 safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -12576,6 +13036,11 @@ source-map@~0.7.2:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
   integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
 
+space-separated-tokens@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b"
+  integrity sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==
+
 spawn-rx@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-3.0.0.tgz#1d33511e13ec26337da51d78630e08beb57a6767"
@@ -12959,6 +13424,13 @@ style-loader@^2.0.0:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
+style-to-object@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46"
+  integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==
+  dependencies:
+    inline-style-parser "0.1.1"
+
 success-symbol@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897"
@@ -13358,6 +13830,11 @@ trim-repeated@^1.0.0:
   dependencies:
     escape-string-regexp "^1.0.2"
 
+trough@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/trough/-/trough-2.0.2.tgz#94a3aa9d5ce379fc561f6244905b3f36b7458d96"
+  integrity sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w==
+
 ts-md5@^1.2.2:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/ts-md5/-/ts-md5-1.2.7.tgz#b76471fc2fd38f0502441f6c3b9494ed04537401"
@@ -13585,6 +14062,19 @@ unicode-property-aliases-ecmascript@^1.0.4:
   resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
   integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
 
+unified@^10.0.0:
+  version "10.1.1"
+  resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.1.tgz#345e349e3ab353ab612878338eb9d57b4dea1d46"
+  integrity sha512-v4ky1+6BN9X3pQrOdkFIPWAaeDsHPE1svRDxq7YpTc2plkIqFMwukfqM+l0ewpP9EfwARlt9pPFAeWYhHm8X9w==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    bail "^2.0.0"
+    extend "^3.0.0"
+    is-buffer "^2.0.0"
+    is-plain-obj "^4.0.0"
+    trough "^2.0.0"
+    vfile "^5.0.0"
+
 union-value@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -13609,6 +14099,69 @@ unique-slug@^2.0.0:
   dependencies:
     imurmurhash "^0.1.4"
 
+unist-builder@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04"
+  integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==
+  dependencies:
+    "@types/unist" "^2.0.0"
+
+unist-util-generated@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113"
+  integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==
+
+unist-util-is@^5.0.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236"
+  integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==
+
+unist-util-position@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.1.tgz#f8484b2da19a897a0180556d160c28633070dbb9"
+  integrity sha512-mgy/zI9fQ2HlbOtTdr2w9lhVaiFUHWQnZrFF2EUoVOqtAUdzqMtNiD99qA5a1IcjWVR8O6aVYE9u7Z2z1v0SQA==
+
+unist-util-stringify-position@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz#d517d2883d74d0daa0b565adc3d10a02b4a8cde9"
+  integrity sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==
+  dependencies:
+    "@types/unist" "^2.0.0"
+
+unist-util-visit-parents@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2"
+  integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-is "^5.0.0"
+
+unist-util-visit-parents@^5.0.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz#44bbc5d25f2411e7dfc5cecff12de43296aa8521"
+  integrity sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-is "^5.0.0"
+
+unist-util-visit@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-3.1.0.tgz#9420d285e1aee938c7d9acbafc8e160186dbaf7b"
+  integrity sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-is "^5.0.0"
+    unist-util-visit-parents "^4.0.0"
+
+unist-util-visit@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.0.tgz#f41e407a9e94da31594e6b1c9811c51ab0b3d8f5"
+  integrity sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-is "^5.0.0"
+    unist-util-visit-parents "^5.0.0"
+
 universal-user-agent@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557"
@@ -13626,6 +14179,11 @@ universalify@^0.1.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
+universalify@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+  integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -13769,6 +14327,16 @@ uuid@^8.0.0, uuid@^8.3.2:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
+uvu@^0.5.0:
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae"
+  integrity sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==
+  dependencies:
+    dequal "^2.0.0"
+    diff "^5.0.0"
+    kleur "^4.0.3"
+    sade "^1.7.3"
+
 v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
@@ -13815,6 +14383,24 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+vfile-message@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.0.tgz#5437035aa43185ff4b9210d32fada6c640e59143"
+  integrity sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-stringify-position "^3.0.0"
+
+vfile@^5.0.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.0.tgz#4990c78cb3157005590ee8c930b71cd7fa6a006e"
+  integrity sha512-Tj44nY/48OQvarrE4FAjUfrv7GZOYzPbl5OD65HxVKwLJKMPU7zmfV8cCgCnzKWnSfYG2f3pxu+ALqs7j22xQQ==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    is-buffer "^2.0.0"
+    unist-util-stringify-position "^3.0.0"
+    vfile-message "^3.0.0"
+
 vhost@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/vhost/-/vhost-3.0.2.tgz#2fb1decd4c466aa88b0f9341af33dc1aff2478d5"