<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
    <title>laedit | Jérémie Bertrand</title>
    <link>https://laedit.net</link>
    <atom:link href="https://laedit.net/rss/" rel="self" type="application/rss+xml" />
    <description>Personal blog of Jérémie Bertrand</description>
    <pubDate>Thu, 25 Jan 2024 05:41:12 +0000</pubDate>
    <lastBuildDate>Thu, 25 Jan 2024 05:41:12 +0000</lastBuildDate>

    
    <item>
      <title>Automatically publish a webextension to Firefox, Edge and GitHub</title>
      <link>https://laedit.net/2023/08/14/automatically-publish-webextension-to-firefox-edge-and-github.html</link>
      <pubDate>Mon, 14 Aug 2023 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2023/08/14/automatically-publish-webextension-to-firefox-edge-and-github.html</guid>
      <description>&lt;p&gt;I wanted to automatically publish my webextension &lt;a href=&quot;https://github.com/laedit/new-tab-moment&quot;&gt;New Tab - Moment&lt;/a&gt; for some time but the lack of possibilities to specify the release notes has stopped me so far.&lt;/p&gt;
&lt;p&gt;But there is now a new &lt;a href=&quot;https://addons-server.readthedocs.io/en/latest/topics/api/addons.html&quot;&gt;api on mozilla&apos;s addons&lt;/a&gt; which provides an endpoint for that, despite it being still not frozen and can change at any time.&lt;/p&gt;
&lt;p&gt;So after some thinking, &lt;a href=&quot;https://github.com/mozilla/web-ext/issues/2686&quot;&gt;trials&lt;/a&gt; and &lt;a href=&quot;https://github.com/mozilla/web-ext/issues/2691&quot;&gt;errors&lt;/a&gt; I have now the following system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The release notes are in the &lt;a href=&quot;https://keepachangelog.com&quot;&gt;Keep a changelog&lt;/a&gt; format&lt;/li&gt;
&lt;li&gt;A release Firefox action will:
&lt;ul&gt;
&lt;li&gt;build the webextension for firefox and edge and outputs two zips, one for each browser&lt;/li&gt;
&lt;li&gt;extract the latest release notes&lt;/li&gt;
&lt;li&gt;sign and publish the firefox zip along with the release notes on &lt;a href=&quot;https://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;download the resulting .xpi file&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A release GitHub action will create a GitHub release with the release notes, the zips and the xpi files&lt;/li&gt;
&lt;li&gt;A release Edge action will upload the edge zip to &lt;a href=&quot;https://partner.microsoft.com/en-us/dashboard/microsoftedge/overview&quot;&gt;Microsoft Partner Center Edge dashboard&lt;/a&gt; as a draft and submit it for publication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/resources/automatically-publish-webextension-to-firefox-edge-and-github/Release-schema.png&quot; alt=&quot;Release schema&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And now for the details.&lt;/p&gt;
&lt;p class=&quot;info&quot;&gt;The following scripts are for GitHub actions but can easily be ported to another system, like the one from &lt;a href=&quot;https://sourcehut.org/&quot;&gt;Sourcehut&lt;/a&gt; for example.&lt;br /&gt;
They also presume that the webextension has been created on each browser store hence they only do an update.&lt;/p&gt;
&lt;p class=&quot;warning&quot;&gt;I use some external actions in these jobs and since they are still &lt;a href=&quot;https://github.com/github/roadmap/issues/592&quot;&gt;not immutable&lt;/a&gt; I reviewed the source code before trusting them with sensitive secrets, but I specify the commit I reviewed in the &lt;code&gt;uses&lt;/code&gt; instead of a version to be sure I will use the exact code I have validated.&lt;br /&gt;
That said it is always preferable to trust no one with your secrets and if possible write the code that uses them.&lt;/p&gt;
&lt;h3&gt;Release to Firefox&lt;/h3&gt;
&lt;p&gt;First the build is done through a shared action, used for a build workflow triggered on pushes and pull requests, and by the release workflow triggered on tags.&lt;br /&gt;
It only call the &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;package&lt;/code&gt; scripts of my &lt;code&gt;package.json&lt;/code&gt; then gets the version number from the generated firefox zip to rename the firefox and edge zips and upload them as artifacts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;- run: |
    yarn build
    yarn package
    filename=`ls web-ext-artifacts/firefox/new_tab_-_moment-*.zip | head`
    versionZip=${filename##*-}
    version=${versionZip%.*}
    cp web-ext-artifacts/firefox/new_tab_-_moment-$version.zip web-ext-artifacts/new_tab_-_moment-$version.firefox.zip
    cp web-ext-artifacts/edge/new_tab_-_moment-$version.zip web-ext-artifacts/new_tab_-_moment-$version.edge.zip
  shell: bash

- uses: actions/upload-artifact@v3
  with:
    path: web-ext-artifacts/new_tab_-_moment-*.zip
    if-no-files-found: error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; script is in charge to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;transpile all typescript files to a &lt;code&gt;build/base&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;copy all other files (html, css, woff2, png) to the same &lt;code&gt;build/base&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;for each browser it will create a specific folder &lt;code&gt;build/{browser}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;copy all the files from &lt;code&gt;build/base&lt;/code&gt; to each browser folder&lt;/li&gt;
&lt;li&gt;then create the specific manifest for each browser since some properties aren&apos;t supported by all
For the details I leave you to my &amp;quot;to be improved&amp;quot; &lt;a href=&quot;https://github.com/laedit/new-tab-moment/blob/master/package.json&quot;&gt;package.json&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After that the &lt;code&gt;package&lt;/code&gt; script calls &lt;a href=&quot;https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#web-ext-build&quot;&gt;&lt;code&gt;web-ext build&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#web-ext-lint&quot;&gt;&lt;code&gt;web-ext lint&lt;/code&gt;&lt;/a&gt; for each browser folder.&lt;/p&gt;
&lt;p&gt;After the build, the release to Firefox action creates the version metadata containing the release notes then signs the webextension on the Mozilla&apos;s addons website and finally uploads the resulting .xpi on the build&apos;s artefacts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;  - name: Extract release notes
    id: extract-release-notes
    uses: ffurrer2/extract-release-notes@4db7ff8e9cc8a442ab103fd3ddfaebd0f8f36e4c

  - name: Create version metadata
    run: |
        release=&apos;${{ steps.extract-release-notes.outputs.release_notes }}&apos;
        cat &amp;lt;&amp;lt;EOF &amp;gt; ./version-metadata.json
        {
          &amp;quot;version&amp;quot;: {
            &amp;quot;release_notes&amp;quot;: {
              &amp;quot;en-US&amp;quot;: $(echo &amp;quot;${release//### }&amp;quot; | jq -sR .)
            }
          }
        }
        EOF

  - run: yarn web-ext sign --api-key ${{ secrets.AMO_ISSUER }} --api-secret ${{ secrets.AMO_SECRET }} --use-submission-api --channel=listed --source-dir build/firefox --amo-metadata ./version-metadata.json

  - uses: actions/upload-artifact@v3
    with:
      path: web-ext-artifacts/new_tab_moment-${{ github.ref_name }}.xpi
      if-no-files-found: error

  outputs:
    release_notes: ${{ steps.extract-release-notes.outputs.release_notes }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two steps focuses on the version metadata: first the release notes of the last version is extracted thanks to &lt;a href=&quot;https://github.com/ffurrer2/extract-release-notes/&quot;&gt;ffurrer2&apos;s github action&lt;/a&gt; then it is inserted in the json file of the version metadata.&lt;/p&gt;
&lt;p&gt;Then the &lt;a href=&quot;https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#web-ext-sign&quot;&gt;&lt;code&gt;web-ext sign&lt;/code&gt;&lt;/a&gt; command of the well-known webextension tool &lt;a href=&quot;https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/&quot;&gt;web-ext&lt;/a&gt; to upload the webextension to &lt;a href=&quot;https://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt;, sign it and publish it if all went well.&lt;br /&gt;
The argument &lt;code&gt;--amo-metadata [metadata file path]&lt;/code&gt; (which has to be used with &lt;code&gt;--use-submission-api&lt;/code&gt;) allows to specify the version metadata and thus the release notes.&lt;/p&gt;
&lt;h3&gt;Release to GitHub&lt;/h3&gt;
&lt;p&gt;The GitHub release runs after the firefox one. It downloads the artifacts uploaded by the previous jobs and creates a new GitHub release with them and the release notes through the &lt;a href=&quot;https://github.com/softprops/action-gh-release&quot;&gt;softprops/action-gh-release&lt;/a&gt; action.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;release-github:
  needs: [release-firefox]
  runs-on: ubuntu-latest
  steps:
    - uses: actions/download-artifact@v3

    - name: Create Release
      uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
      with:
        tag_name: ${{ github.ref }}
        name: ${{ github.ref_name }}
        body: ${{ needs.release-firefox.outputs.release_notes }}
        draft: false
        prerelease: false
        token: ${{ secrets.GITHUB_TOKEN }}
        files: |
          artifact/new_tab_-_moment-${{ github.ref_name }}.*.zip
          artifact/new_tab_moment-${{ github.ref_name }}.xpi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that a &lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens&quot;&gt;GitHub access token&lt;/a&gt; is needed.&lt;/p&gt;
&lt;h3&gt;Release to Edge&lt;/h3&gt;
&lt;p&gt;The Edge release also runs after the firefox one, in parallel of the GitHub release. It downloads the artifacts uploaded by the previous jobs and submit the edge zip as a new version of the edge addon through the &lt;a href=&quot;https://github.com/wdzeng/edge-addon&quot;&gt;wdzeng/edge-addon&lt;/a&gt; action.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;release-edge:
  needs: [release-firefox]
  runs-on: ubuntu-latest
  steps:
    - uses: actions/download-artifact@v3

    - uses: wdzeng/edge-addon@b1ce0984067e0a0107065e0af237710906d94531
      with:
        product-id: ${{ secrets.EDGE_PRODUCT }}
        zip-path: artifact/new_tab_-_moment-${{ github.ref_name }}.edge.zip
        client-id: ${{ secrets.EDGE_CLIENT }}
        client-secret: ${{ secrets.EDGE_SECRET }}
        access-token-url: ${{ secrets.EDGE_TOKEN_URL }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here you go, it is not perfect but largely sufficient for my little addon and maybe yours.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Switching from BlobBackup to rclone</title>
      <link>https://laedit.net/2023/06/18/switching-from-blobbackup-to-rclone.html</link>
      <pubDate>Sun, 18 Jun 2023 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2023/06/18/switching-from-blobbackup-to-rclone.html</guid>
      <description>&lt;p&gt;In the &lt;a href=&quot;/2021/11/28/switching-from-hubic-to-blobbackup.html&quot;&gt;previous post&lt;/a&gt; I used &lt;a href=&quot;https://blobbackup.com/&quot;&gt;BlobBackup&lt;/a&gt; to sync my backup data to a cloud storage and an external drive. Since then the original client hasn&apos;t been updated and an account is now mandatory to download and use it. Even if the offer is attractive I didn&apos;t want to change my cloud backup location and prefer to know where my data is stored.&lt;/p&gt;
&lt;p&gt;So after an extensive search I choose &lt;a href=&quot;https://rclone.org/&quot;&gt;rclone&lt;/a&gt; to replace it mainly for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allows access to individual folders and files (even if it means bigger uploads)&lt;/li&gt;
&lt;li&gt;well-known open-source software&lt;/li&gt;
&lt;li&gt;numerous targets (remotes in rclone language) including those that suit me: &lt;a href=&quot;https://rclone.org/s3/#scaleway&quot;&gt;S3 - Scaleway&lt;/a&gt; and &lt;a href=&quot;https://rclone.org/local/&quot;&gt;local filesystem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;handles &lt;a href=&quot;https://rclone.org/crypt/&quot;&gt;encryption of any remote&lt;/a&gt; and of the &lt;a href=&quot;https://rclone.org/docs/#configuration-encryption&quot;&gt;configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the cloud backup I just had to add a new remote to my Scaleway storage named &lt;code&gt;Scaleway&lt;/code&gt;, another one to encrypt the first named &lt;code&gt;ScalewayEncrypted&lt;/code&gt; and encrypted the configuration.&lt;br /&gt;
It is doable in command line but now there is even an &lt;a href=&quot;https://rclone.org/gui/&quot;&gt;experimental gui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the external drive backup, a remote configuration wasn&apos;t necessary and I choose to encrypt the entire drive and not only the backup data.&lt;br /&gt;
I have chosen &lt;a href=&quot;https://veracrypt.fr/en/&quot;&gt;VeraCrypt&lt;/a&gt; for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;well-known open-source software&lt;/li&gt;
&lt;li&gt;audited&lt;/li&gt;
&lt;li&gt;simple to use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like the backup script I have written a powershell script to ease the sync from and to the cloud and external drive, I will detail it bit by bit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;param(
    [Parameter(Mandatory = $True, Position = 0)]
    [string]
    $syncDirection,
    [Parameter(Mandatory = $True, Position = 1)]
    [string]
    $syncType,
    [Parameter(Mandatory = $False, Position = 2)]
    [string]
    $overrideLocalRoot
)

$ErrorActionPreference = &amp;quot;Stop&amp;quot;
. $PSScriptRoot\Toast.ps1

enum SyncType {
    Cloud
    Local
}

enum SyncDirection {
    To
    From
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parameters allows to easily define the sync direction and type, sadly since &lt;code&gt;param&lt;/code&gt; must be at the top it can&apos;t use the enums directly.&lt;br /&gt;
The local root path can be overridden, specially useful for testing a restore and not lose data.&lt;/p&gt;
&lt;p&gt;For the popup notification I use &lt;a href=&quot;https://github.com/Windos/BurntToast&quot;&gt;BurntToast&lt;/a&gt;, here is the &lt;code&gt;Toast.ps1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;enum Icon {
    Info
    Error
}

# Icons from Roselin Christina.S from Noun Project
$icons = @{
    [Icon]::Info = &amp;quot;$PSScriptRoot\info.png&amp;quot;;  # https://thenounproject.com/icon/info-1156901/
    [Icon]::Error = &amp;quot;$PSScriptRoot\error.png&amp;quot; # https://thenounproject.com/icon/error-1156903/
}

function Pop-Toast([string] $title, [string] $message, [Icon] $icon)
{
    New-BurntToastNotification -AppLogo $icons[$icon] -Text $title, $message
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the sync configuration part:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;class SyncConfiguration {
    [ScriptBlock] $getRemote
    [string] $additionalParameters
}

function Get-DriveLetter([string] $driveName) {
    return (Get-CimInstance -ClassName Win32_Volume | ? { $_.Label?.ToLower() -eq $driveName }).DriveLetter
}

function Get-KasullRemote {
    $kasullLetter = Get-DriveLetter &apos;kasull&apos;

    while ($null -eq $kasullLetter) {
        Write-Host &apos;Please insert Kasull, mount it in veracrypt and press a key&apos; -ForegroundColor DarkGreen
        $null = $host.ui.RawUI.ReadKey(&amp;quot;NoEcho,IncludeKeyDown&amp;quot;)
        $kasullLetter = Get-DriveLetter &apos;kasull&apos;
    }

    return &amp;quot;$kasullLetter\Backup\&amp;quot;
}

$syncConfigurations = @{
    [SyncType]::Cloud = [SyncConfiguration]@{ getRemote = { &amp;quot;ScalewayEncrypted:rclone/&amp;quot; }; additionalParameters = &amp;quot;--ask-password=false&amp;quot; };
    [SyncType]::Local = [SyncConfiguration]@{ getRemote = { Get-KasullRemote } }
}

class BackupEntry {
    [string] $name
    [string] $localParentPath
}

function Get-BackupEntries() {
    return @(
        [BackupEntry]@{ name = &apos;Backup&apos;; localParentPath = &amp;quot;D:\&amp;quot; }
        [BackupEntry]@{ name = &apos;Archives&apos;; localParentPath = &amp;quot;D:\&amp;quot; }
        [BackupEntry]@{ name = &apos;Photos&apos;; localParentPath = &amp;quot;M:\&amp;quot; }
    )
}

$rclone = &amp;quot;D:\Softwares\rclone\rclone.exe&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sync configurations are defined for the cloud and local. Since VeraCrypt can mount a drive on any letter, the letter of the external drive is detected with his name.&lt;br /&gt;
To fully automate the sync the rclone password is taken from an environment variable hence the &lt;code&gt;--ask-password=false&lt;/code&gt;.
After that the folders to sync are also defined.&lt;/p&gt;
&lt;p&gt;And the last part of the script which determine the rclone arguments and calls it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;try {
    $syncConfiguration = $syncConfigurations[[SyncType]$syncType]

    $title = &amp;quot;Sync $syncDirection $syncType&amp;quot;

    .$rclone selfupdate

    Pop-Toast $title &apos;Started&apos; Info

    if (![string]::IsNullOrEmpty($overrideLocalRoot) -and !$overrideLocalRoot.EndsWith(&amp;quot;\&amp;quot;)) {
        $overrideLocalRoot += &amp;quot;\&amp;quot;
    }

    foreach ($backupEntry in Get-BackupEntries) {
        $syncSource = &amp;quot;$([string]::IsNullOrEmpty($overrideLocalRoot) ? $backupEntry.localParentPath : $overrideLocalRoot)$($backupEntry.name)&amp;quot;
        $syncDestination = &amp;quot;$($syncConfiguration.getRemote.invoke())$($backupEntry.name)&amp;quot;

        if ([SyncDirection]$syncDirection -ne [SyncDirection]::To) {
            $destTemp = $syncSource
            $syncSource = $syncDestination
            $syncDestination = $destTemp
        }

        Write-Host &amp;quot;Sync $($backupEntry.name)&amp;quot; -ForegroundColor DarkGreen
        .$rclone sync --progress $syncConfiguration.additionalParameters $syncSource $syncDestination
    }

    Pop-Toast $title &apos;Finished&apos; Info
}
catch {
    Pop-Toast $title &apos;Error&apos; Error
    Write-Host &apos;An error occurred:&apos;
    Write-Host $_.ScriptStackTrace
    Write-Host $_
    Read-Host -Prompt &apos;Press enter to close&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script can be invoked like this &lt;code&gt;SyncBackup.ps1 To Cloud&lt;/code&gt; or &lt;code&gt;SyncBackup.ps1 From Local&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And finally I created two tasks to launch the syncs regularly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;# SyncToCloud
$action = New-ScheduledTaskAction -Execute &apos;pwsh&apos; -Argument &apos;-File D:\Backup\Scripts\SyncBackup.ps1 To Cloud&apos;
$trigger = New-ScheduledTaskTrigger -Daily -At 2AM
$description = &amp;quot;Sync data to the cloud&amp;quot;
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -WakeToRun
Register-ScheduledTask -TaskName &amp;quot;SyncToCloud&amp;quot; -Action $action -Trigger $trigger -Description $description -RunLevel Highest -Settings $settings

# SyncToKasull
$action = New-ScheduledTaskAction -Execute &apos;pwsh&apos; -Argument &apos;-File D:\Backup\Scripts\SyncBackup.ps1 To Local&apos;
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 5AM
$description = &amp;quot;Sync data to Kasull&amp;quot;
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -WakeToRun
Register-ScheduledTask -TaskName &amp;quot;SyncToKasull&amp;quot; -Action $action -Trigger $trigger -Description $description -RunLevel Highest -Settings $settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! (for now at least)&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Switching from Hubic to BlobBackup</title>
      <link>https://laedit.net/2021/11/28/switching-from-hubic-to-blobbackup.html</link>
      <pubDate>Sun, 28 Nov 2021 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2021/11/28/switching-from-hubic-to-blobbackup.html</guid>
      <description>&lt;p&gt;It&apos;s important to do backups.&lt;br /&gt;
I had one in place for all my important documents, encrypted locally and synced with Hubic. But since it is becoming out of commission and the last time I tried to do a restore the data was corrupted, I though that it was time to replace it.&lt;/p&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;p&gt;First I needed to rethink my use of backups: I was only doing backup of some important files in a cloud provider.&lt;br /&gt;
My plan was to add my photos, some other documents and my software settings to the backup for ~25GB and the backup should be done in cloud and on a local external hard drive.&lt;/p&gt;
&lt;h3&gt;Settings backup&lt;/h3&gt;
&lt;p&gt;I have a hard drive with the OS and softwares and another drive with the data to avoid losing them if I need to wipe the OS clean.&lt;br /&gt;
The idea was just to copy the settings of certain softwares I use in order to be able to restore them after a fresh install of Windows.&lt;br /&gt;
I have written a quick powershell script (I&apos;m tempted to make it a more robust application):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;#Requires -RunAsAdministrator
$ErrorActionPreference = &amp;quot;Stop&amp;quot;
Add-Type -AssemblyName System.Windows.Forms
$dataFolder = &amp;quot;D:\Backup\Data&amp;quot;

$balloon = New-Object System.Windows.Forms.NotifyIcon
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon((Get-Process -id $pid).Path)

function ToastNotification([string] $message, [System.Windows.Forms.ToolTipIcon] $icon) {
    $balloon.BalloonTipIcon = $icon
    $balloon.BalloonTipText = $message
    $balloon.BalloonTipTitle = $message
    $balloon.Visible = $true
    $balloon.ShowBalloonTip(5000)
}

function CreateBackupDir([string] $dirName) {
    if (-not (Test-Path &amp;quot;$dataFolder\$dirName&amp;quot;)) {
        New-Item -ItemType Directory -Force -Path &amp;quot;$dataFolder\$dirName&amp;quot;
    }
}

function DeleteDir([string] $dirName) {
    if (Test-Path &amp;quot;$dataFolder\$dirName&amp;quot;) {
        Remove-Item -path &amp;quot;$dataFolder\$dirName&amp;quot; -recurse -force
    }
}

try {
    ToastNotification &apos;Backup started&apos; Info

    # Jellyfin
    $jellyfinBackupPath = &amp;quot;$dataFolder\JellyfinServer.7z&amp;quot;
    Write-Host &apos;Backup Jellyfin&apos; -ForegroundColor DarkGreen
    # 1. Stop Jellyfin
    Write-Host &apos;Stopping service&apos; -ForegroundColor DarkGray
    Stop-Service &apos;JellyfinServer&apos;
    # 2. Make a copy of all the Jellyfin data and configuration directories
    Write-Host &apos;Copying data&apos; -ForegroundColor DarkGray
    &amp;amp;&apos;C:\Program Files\7-Zip\7z.exe&apos; a $jellyfinBackupPath &apos;C:\ProgramData\Jellyfin\Server&apos; -mx=0 -aoa
    # 3. Start Jellyfin
    Write-Host &apos;Starting service&apos; -ForegroundColor DarkGray
    Start-Service &apos;JellyfinServer&apos;

    # Firefox dev tabs
    Write-Host &apos;Backup Firefox&apos; -ForegroundColor DarkGreen
    CreateBackupDir &apos;Firefox&apos;
    Copy-Item -Path &amp;quot;$env:APPDATA\Mozilla\Firefox\Profiles\*.dev-edition-default\sessionstore-backups\recovery.jsonlz4&amp;quot; -Destination &amp;quot;$dataFolder\Firefox&amp;quot;
    Copy-Item -Path &amp;quot;$env:APPDATA\Mozilla\Firefox\Profiles\*.dev-edition-default\sessionstore-backups\recovery.baklz4&amp;quot; -Destination &amp;quot;$dataFolder\Firefox&amp;quot;

    # Powershell profile
    Write-Host &apos;Backup Powershell&apos; -ForegroundColor DarkGreen
    if (Test-Path -Path &amp;quot;$env:USERPROFILE\Documents\Powershell&amp;quot;)
    {
        Copy-Item -Path &amp;quot;$env:USERPROFILE\Documents\Powershell\Microsoft.PowerShell_profile.ps1&amp;quot; -Destination &amp;quot;$dataFolder\&amp;quot; –Force
    }
    else
    {
        Copy-Item -Path &amp;quot;$env:USERPROFILE\OneDrive\Documents\Powershell\Microsoft.PowerShell_profile.ps1&amp;quot; -Destination &amp;quot;$dataFolder\&amp;quot; –Force
    }

    # BlobBackup
    Write-Host &apos;Backup BlobBackup&apos; -ForegroundColor DarkGreen
    CreateBackupDir &apos;BlobBackup&apos;
    Copy-Item -Path &amp;quot;$env:USERPROFILE\.blobbackup\*&amp;quot; -Destination &amp;quot;$dataFolder\BlobBackup\&amp;quot;

    # Notepad++
    Write-Host &apos;Backup Notepad++&apos; -ForegroundColor DarkGreen
    CreateBackupDir &apos;Notepad++&apos;
    DeleteDir &apos;notepad++\backup&apos;
    Copy-Item -Path &amp;quot;$env:AppData\Notepad++\config.xml&amp;quot; -Destination &amp;quot;$dataFolder\Notepad++&amp;quot;
    Copy-Item -Path &amp;quot;$env:AppData\Notepad++\session.xml&amp;quot; -Destination &amp;quot;$dataFolder\Notepad++&amp;quot;
    Copy-Item -Path &amp;quot;$env:AppData\Notepad++\backup&amp;quot; -Destination &amp;quot;$dataFolder\Notepad++&amp;quot; -Recurse

    ToastNotification &apos;Backup finished&apos; Info
}
catch {
    ToastNotification &apos;Backup error&apos; Error
    Write-Host &apos;An error occurred:&apos;
    Write-Host $_.ScriptStackTrace
    Write-Host $_
    Read-Host -Prompt &apos;Press enter to close&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Edit: Thanks to &lt;a href=&quot;https://hachyderm.io/@loicwolff&quot;&gt;Loïc Wolff&lt;/a&gt; the administrator check has been simplified with a &lt;code&gt;#requires&lt;/code&gt; and the notifications are now grouped together.&lt;/em&gt;&lt;br /&gt;
It requires to be launched as administrator to be able to stop and restart the &lt;a href=&quot;https://jellyfin.org/&quot;&gt;Jellyfin&lt;/a&gt; service, it copies the settings of various softwares in a specific folder and raise a notification (&lt;a href=&quot;https://mcpmag.com/articles/2017/09/07/creating-a-balloon-tip-notification-using-powershell.aspx&quot;&gt;Thanks to Boe Prox&lt;/a&gt;), alerting me if an error showed.&lt;br /&gt;
&lt;em&gt;Edit 2: Since then I discovered that the notifications do not stay in the notification center of Windows 11 so I switched to &lt;a href=&quot;https://github.com/Windos/BurntToast/&quot;&gt;BurntToast&lt;/a&gt;:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;enum Icon {
    Info
    Error
}

# Icons from Roselin Christina.S from Noun Project
# https://thenounproject.com/icon/error-1156903/
# https://thenounproject.com/icon/info-1156901/
$icons = @{
    [Icon]::Info = &amp;quot;$PSScriptRoot\info.png&amp;quot;;
    [Icon]::Error = &amp;quot;$PSScriptRoot\error.png&amp;quot;
}

function Pop-Toast([string] $title, [string] $message, [Icon] $icon)
{
    New-BurntToastNotification -AppLogo $icons[$icon] -Text $title, $message
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A scheduled task allows me to run it every day:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;$taskAction = New-ScheduledTaskAction -Execute &apos;pwsh&apos; -Argument &apos;-File Backup.ps1&apos;
$taskTrigger = New-ScheduledTaskTrigger -Daily -At 1AM
$taskName = &amp;quot;Backup&amp;quot;
$description = &amp;quot;Backup settings of applications&amp;quot;

Register-ScheduledTask -TaskName $taskName -Action $taskAction -Trigger $taskTrigger -Description $description -RunLevel Highest
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Search for a replacement&lt;/h3&gt;
&lt;p&gt;Next step was to search for a replacement of Hubic. After a quick search I turned to &lt;a href=&quot;https://rclone.org/&quot;&gt;rclone&lt;/a&gt; which is very versatile but seems a little bit complex for my taste.&lt;br /&gt;
Luckily a french blogger I follow published an &lt;a href=&quot;https://korben.info/blobbackup-sauvegarde.html&quot;&gt;article&lt;/a&gt; at that time on a backup software which seems to check all my boxes.&lt;br /&gt;
&lt;a href=&quot;https://github.com/BlobBackup/BlobBackup&quot;&gt;BlobBackup&lt;/a&gt; is simple to use, handles many backup destinations including local directory and S3 compatible storages, is open-source and encrypts data.&lt;/p&gt;
&lt;p&gt;But there are some inconveniences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it is still in beta, although very stable&lt;/li&gt;
&lt;li&gt;it hasn&apos;t an included cloud storage (&lt;a href=&quot;https://www.reddit.com/r/BlobBackup/comments/nrm9yd/bitwardenlike_business_model_ideas/&quot;&gt;for now&lt;/a&gt;) which forces me to search for one&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cloud storage&lt;/h3&gt;
&lt;p&gt;My requirements were an S3 compatible storage preferably located in France or europe, and I found &lt;a href=&quot;https://www.scaleway.com&quot;&gt;Scaleway&lt;/a&gt; which is a french cloud provider and has a free &lt;a href=&quot;https://www.scaleway.com/en/object-storage/&quot;&gt;Object Storage plan&lt;/a&gt; up to 75Go.&lt;/p&gt;
&lt;p&gt;I just had to create an account, fill in my credit card to had access to the creation of an S3 bucket and the last thing to do was to create an API key.&lt;/p&gt;
&lt;h3&gt;BlobBackup Configuration&lt;/h3&gt;
&lt;p&gt;BlobBackup is very easy to configure, you choose a storage location, fill in the backup name and password and set the storage parameters.&lt;br /&gt;
After that you choose the folders to include in the backup, maybe define exclude rules and a schedule and can even specify a retention.&lt;/p&gt;
&lt;p&gt;For my needs, I have configured a backup to an external hard drive scheduled every week on sunday 5AM and a backup to my Scaleway object storage bucket every day at 2AM.&lt;/p&gt;
&lt;h3&gt;Quick feedback&lt;/h3&gt;
&lt;p&gt;I have only used BlobBackup since ~15 days, but here are my quick feedback.&lt;br /&gt;
There are some issues, with &lt;a href=&quot;https://github.com/BlobBackup/BlobBackup/issues/74&quot;&gt;storage parameters modification&lt;/a&gt; for example, due to his young age but it is open-source and &lt;a href=&quot;https://github.com/BlobBackup/BlobBackup/pull/76&quot;&gt;you can help&lt;/a&gt; build it.&lt;br /&gt;
About his performance, I have ~25 Go of data to backup and thanks to his incremental engine for the backup to the external drive it has taken about ~17 minutes the first time and now it is down to 49 secondes. Of course it depends if many files have been modified.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Switching from Chocolatey to Winget</title>
      <link>https://laedit.net/2021/11/24/switching-from-chocolatey-to-winget.html</link>
      <pubDate>Wed, 24 Nov 2021 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2021/11/24/switching-from-chocolatey-to-winget.html</guid>
      <description>&lt;p&gt;Around every 2-3 years I reinstall my computer with a fresh Windows.&lt;br /&gt;
To avoid loosing to much time, I use &lt;a href=&quot;https://boxstarter.org/&quot;&gt;Boxstarter&lt;/a&gt; and &lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt; to automate as much as possible all settings and softwares installations.&lt;br /&gt;
But since I use Chocolatey only on that occasion, I wanted to replace it by &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/package-manager/winget/&quot;&gt;Winget&lt;/a&gt;: is is (almost) native to windows, the v1.0 is out and the number of packages available in the &lt;a href=&quot;https://github.com/microsoft/winget-pkgs&quot;&gt;community repository&lt;/a&gt; is good enough.&lt;/p&gt;
&lt;p&gt;Both are package managers and are invoked from the command line, so the switch was not hard but there was some differences and deficiencies to come by.&lt;/p&gt;
&lt;h3&gt;Installing winget&lt;/h3&gt;
&lt;p&gt;Winget will be integrated with Windows 11 but it is not yet a reality, so to be sure to have the right version installed I use this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;$hasPackageManager = Get-AppPackage -name &apos;Microsoft.DesktopAppInstaller&apos;
if (!$hasPackageManager -or [version]$hasPackageManager.Version -lt [version]&amp;quot;1.10.0.0&amp;quot;) {
    Start-Process ms-appinstaller:?source=https://aka.ms/getwinget
    Read-Host -Prompt &amp;quot;Press enter to continue...&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use the prompt to pause the script since I need winget for the rest.&lt;/p&gt;
&lt;h3&gt;Package arguments&lt;/h3&gt;
&lt;p&gt;Chocolatey allows packages creators to add parameters directly for the package, which allows to install Git, add it to the path and disables shell integration with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;choco install git.install --params &amp;quot;/GitOnlyOnPath /NoShellIntegration&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Winget doesn&apos;t allow package creators to add parameters since it handles directly the installers, but you can override the parameters passed to the installer.&lt;br /&gt;
So to install git the same way than above, you can use the &lt;code&gt;--override&lt;/code&gt; parameter which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;winget install --id Git.Git --override &apos;/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /NOCANCEL /SP- /LOG /COMPONENTS=&amp;quot;assoc,gitlfs&amp;quot; /o:PathOption=Cmd&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since you override all parameters, you have to pass the silent ones and the ones you want to add.&lt;/p&gt;
&lt;p&gt;It can be hard to find the parameters of the installer, for git you can see in &lt;a href=&quot;https://github.com/git-for-windows/git/issues/2912&quot;&gt;this issue&lt;/a&gt; that they have added a &lt;code&gt;/o:&lt;/code&gt; arg which overrides the parameters defined in the &lt;a href=&quot;https://github.com/git-for-windows/build-extra/blob/HEAD/installer/install.iss&quot;&gt;installer definition&lt;/a&gt;, specifically the arguments passed to the &lt;a href=&quot;https://github.com/git-for-windows/build-extra/blob/HEAD/installer/install.iss#L1140&quot;&gt;ReplayChoice function&lt;/a&gt;. For the components, they are defined in the &lt;a href=&quot;https://github.com/git-for-windows/build-extra/blob/HEAD/installer/install.iss#L105&quot;&gt;[Components]&lt;/a&gt; section.&lt;/p&gt;
&lt;p&gt;Here are some other examples:&lt;br /&gt;
&lt;strong&gt;Visual Studio Code&lt;/strong&gt;&lt;br /&gt;
Choco:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;choco install vscode --params &amp;quot;/NoDesktopIcon /NoQuicklaunchIcon&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Winget:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;winget install --id Microsoft.VisualStudioCode --override &apos;/VERYSILENT /SUPPRESSMSGBOXES /MERGETASKS=&amp;quot;!runcode,!desktopicon,!quicklaunchicon&amp;quot;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;!runcode&lt;/code&gt; task allows to not run Visual Studio Code once installed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sumatra PDF&lt;/strong&gt;&lt;br /&gt;
Choco:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;choco install sumatrapdf.install --params &amp;quot;/WithPreview&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Winget:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;winget install --id SumatraPDF.SumatraPDF --override &apos;/install /S -with-preview&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Missing packages&lt;/h3&gt;
&lt;p&gt;Some packages where missing from the &lt;a href=&quot;https://github.com/microsoft/winget-pkgs&quot;&gt;community repository&lt;/a&gt;, but it is quite easy to create a new one with &lt;a href=&quot;https://github.com/microsoft/winget-create&quot;&gt;winget create&lt;/a&gt; or, if you want to automate that, with the &lt;a href=&quot;https://github.com/microsoft/winget-pkgs#using-the-yamlcreateps1&quot;&gt;YamlCreate script&lt;/a&gt;.&lt;br /&gt;
For example I quickly added &lt;a href=&quot;https://github.com/microsoft/winget-pkgs/pull/34590&quot;&gt;Cybersoft.DriversCloud&lt;/a&gt; and &lt;a href=&quot;https://github.com/microsoft/winget-pkgs/pull/34735&quot;&gt;Jellyfin.JellyfinServer&lt;/a&gt;.&lt;br /&gt;
And if you want the packages to keep up to dates with the releases, you can raise a new issue in the &lt;a href=&quot;https://github.com/vedantmgoyal2009/winget-pkgs-automation&quot;&gt;winget-pkgs-automation repository&lt;/a&gt; or better, make a new PR to &lt;a href=&quot;https://github.com/vedantmgoyal2009/winget-pkgs-automation/pull/194&quot;&gt;add the required infos and maybe script necessary to automate the updates&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But Winget is still limited to installers like msi, msix and exe so I had to handle the packages without installers otherwise.&lt;/p&gt;
&lt;p&gt;For standalone exe I chose to download them but not to add them to the path contrary to Chocolatey:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;iwr https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe -out &amp;quot;$env:USERPROFILE\Downloads\OOSU10.exe&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since it concerns only exe that will be used only once, the add to path is not necessary.&lt;br /&gt;
To ease the process I made it a function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;function Download ($url) {
    $fileName =  Split-Path $url -leaf
    $downloadPath = &amp;quot;$env:USERPROFILE\Downloads\$fileName&amp;quot;
    iwr $url -out $downloadPath
    return $downloadPath
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same for zip files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;function UnzipFromWeb ($url) {
    $downloadPath = Download $url
    $targetDir = &amp;quot;$env:USERPROFILE\Downloads\$((Get-ChildItem $downloadPath).BaseName)&amp;quot;
    Expand-Archive $downloadPath -DestinationPath $targetDir -Force
    Remove-Item $downloadPath
    return $targetDir
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is used like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;UnzipFromWeb &apos;https://github.com/microsoft/Terminal/releases/download/1904.29002/ColorTool.zip&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For font it is a little bit more difficult:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;$cascadiaCodeFolder = UnzipFromWeb &apos;https://github.com/ryanoasis/nerd-fonts/releases/latest/download/CascadiaCode.zip&apos;
$fonts = (New-Object -ComObject Shell.Application).Namespace(0x14)
foreach ($file in &amp;quot;$cascadiaCodeFolder\*.ttf&amp;quot;)
{
    $fileName = $file.Name
    dir $file | %{ $fonts.CopyHere($_.fullname) }
}
Remove-Item $cascadiaCodeFolder -Recurse -Force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code is adapted from &lt;a href=&quot;https://blog.simontimms.com/2021/06/11/installing-fonts/&quot;&gt;Simon Timms blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Boxstarter and Chocolatey uninstall&lt;/h3&gt;
&lt;p&gt;Since I will not use them after that, I chose to uninstall both Boxstarter and Chocolatey.&lt;br /&gt;
It is not easy but the code is available on their website: for &lt;a href=&quot;https://boxstarter.org/InstallBoxstarter&quot;&gt;boxstarter&lt;/a&gt; (at the bottom) and for &lt;a href=&quot;https://chocolatey.org/docs/uninstallation&quot;&gt;Chocolatey&lt;/a&gt;.&lt;br /&gt;
Be careful thought, since it modify the path it can cause quite a damage on your computer.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Tips for creating an ASP.NET Core MVC PWA and hosting it on Ikoula Shared Host</title>
      <link>https://laedit.net/2021/04/01/tips-for-creating-an-aspnet-core-mvc-pwa-and-hosting-it-on-ikoula-shared-host.html</link>
      <pubDate>Thu, 01 Apr 2021 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2021/04/01/tips-for-creating-an-aspnet-core-mvc-pwa-and-hosting-it-on-ikoula-shared-host.html</guid>
      <description>&lt;p class=&quot;info&quot;&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This is not a precise how-to but merely some tips.&lt;/p&gt;
&lt;h3&gt;Creating an ASP.NET Core MVC PWA&lt;/h3&gt;
&lt;p&gt;I recently created a personal app and I wanted to try to make a PWA from an ASP.NET Core MVC app.&lt;/p&gt;
&lt;p&gt;So once the app done I wanted to add the PWA specific parts which consist of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a manifest which describe the application to browser, in order for them to propose the installation for example&lt;/li&gt;
&lt;li&gt;a service worker which is generally used to cache some or all parts of the application in order to allow an offline use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is a convenient Nuget package &lt;a href=&quot;https://www.nuget.org/packages/WebEssentials.AspNetCore.PWA/&quot;&gt;WebEssentials.AspNetCore.PWA&lt;/a&gt; which appears to do the job well, but I wanted to things myself since it is my first PWA.&lt;/p&gt;
&lt;h4&gt;Manifest&lt;/h4&gt;
&lt;p&gt;So I added a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Manifest&quot;&gt;&lt;code&gt;manifest.webmanifest&lt;/code&gt;&lt;/a&gt; in the &lt;code&gt;wwwroot&lt;/code&gt; folder, but the uri was returning a 404.
I appears that the &lt;code&gt;UseStaticFiles&lt;/code&gt; method only returns files for known content type.&lt;br /&gt;
So I had to add the right content type with the following code in the &lt;code&gt;Startup.Configure&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var provider = new FileExtensionContentTypeProvider();
provider.Mappings[&amp;quot;.webmanifest&amp;quot;] = &amp;quot;application/manifest+json&amp;quot;;
app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Service worker&lt;/h4&gt;
&lt;p&gt;For the service worker, I added his registration through a dedicated script in order to avoid issue with the csp: default-src &apos;self&apos; policy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (&apos;serviceWorker&apos; in navigator) {
    document.addEventListener(&apos;DOMContentLoaded&apos;, () =&amp;gt; {
        try {
            navigator.serviceWorker.register(&apos;/serviceworker.js&apos;, { scope: &apos;/&apos; });
        } catch (e) {
            console.error(&apos;Error during service worker registration&apos;, e);
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is registered only if the browser supports it and after the dom has loaded to avoid any blocking during the page load.&lt;br /&gt;
The log is minimal here, you have to adapt it to your log system.&lt;/p&gt;
&lt;p&gt;After that I created the service worker script but was confronted with another issue: I use typescript and by default it doesn&apos;t include the webworkers types so I was having errors about unknown types like &lt;code&gt;ExtendableEvent&lt;/code&gt; and &lt;code&gt;FetchEvent&lt;/code&gt;.&lt;br /&gt;
So I found the right &lt;a href=&quot;https://stackoverflow.com/questions/56356655/structuring-a-typescript-project-with-workers&quot;&gt;StackOverflow answer&lt;/a&gt;, reorganized my scripts and used a multi-tsconfig configuration to have all base compiler options common and specialized &lt;code&gt;lib&lt;/code&gt; by folder : &lt;code&gt;DOM&lt;/code&gt; for the client-side scripts and &lt;code&gt;WebWorker&lt;/code&gt; for the service worker.&lt;/p&gt;
&lt;p&gt;But I still had errors about the export part:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;declare var self: ServiceWorkerGlobalScope;
export {};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It was because for this app I only used scripts and not client-side app, so I had the typescript compiler &lt;code&gt;module&lt;/code&gt; option set to &lt;code&gt;none&lt;/code&gt;.&lt;br /&gt;
So once again after having found the right solution (this time on &lt;a href=&quot;https://github.com/microsoft/TypeScript/issues/11781#issuecomment-785350836&quot;&gt;github&lt;/a&gt;) I used the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const sw: ServiceWorkerGlobalScope &amp;amp; typeof globalThis = self as any
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and replaced all &lt;code&gt;self&lt;/code&gt; by &lt;code&gt;sw&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And after that all was well!&lt;/p&gt;
&lt;h3&gt;Hosting on a Ikoula shared host&lt;/h3&gt;
&lt;h4&gt;Web deploy&lt;/h4&gt;
&lt;p&gt;I have a shared host at Ikoula and they use Plesk for that.&lt;br /&gt;
I recently discovered that Plesk have an option to activate the web deploy publishing under the hosting settings.&lt;br /&gt;
Once activated you have a link on your site&apos;s dashboard which allows you to download the web deploy publishing settings.&lt;br /&gt;
The format is not the same that the &amp;quot;Import Profile&amp;quot; option expect in Visual Studio 2019, but you can create a new &amp;quot;Web Server (IIS) / Web Deploy&amp;quot; publish profile and copy the values from the file.&lt;/p&gt;
&lt;p&gt;If you have an issue on certificate check, that can be resolved through the &amp;quot;Validate Connection&amp;quot; on the Connection page.&lt;/p&gt;
&lt;h4&gt;SQLite error&lt;/h4&gt;
&lt;p&gt;I use SQLite with Entity Framework for business and identity storage and after the app deployment I got this error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DllNotFoundException: Unable to load DLL &apos;e_sqlite3&apos; or one of its dependencies: Access denied. (0x80070005 (E_ACCESSDENIED))&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&apos;t know how that works, but it appears that the SQLite Entity Framework package need the write permission to the folder where the dll are.&lt;br /&gt;
So just add it to the application pool group user and that&apos;s done!&lt;/p&gt;
&lt;h4&gt;Secrets&lt;/h4&gt;
&lt;p&gt;If you use &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets&quot;&gt;secrets&lt;/a&gt;, it is recommended to use environment variables.&lt;br /&gt;
But if you can&apos;t (at least I didn&apos;t find a way to set one on Plesk), how do you do?&lt;/p&gt;
&lt;p&gt;Well I tried to copy the content of the &lt;code&gt;secrets.json&lt;/code&gt; in the &lt;code&gt;appsettings.json&lt;/code&gt; after the deployment but it is not practical since I use webdeploy and it need to restart the website (a touch of the &lt;code&gt;web.config&lt;/code&gt; suffice).&lt;/p&gt;
&lt;p&gt;So finally I just used my own &lt;code&gt;secrets.json&lt;/code&gt; file, added it to the app configuration and ignored it through the &lt;code&gt;.gitignore&lt;/code&gt; to avoid leaking secrets.&lt;br /&gt;
It is not the best but at least that do the trick.&lt;/p&gt;
&lt;p&gt;So here my few tips, don&apos;t hesitate to guide me to better ways!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>My journey with NDepend</title>
      <link>https://laedit.net/2017/05/16/my-journey-with-ndepend.html</link>
      <pubDate>Tue, 16 May 2017 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2017/05/16/my-journey-with-ndepend.html</guid>
      <description>&lt;p class=&quot;info&quot;&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I was offered a 1 year NDepend pro license by &lt;a href=&quot;https://blog.ndepend.com/author/psmacchia/&quot;&gt;Patrick Smacchia&lt;/a&gt; (NDepend creator) so I could use it and share my experience if I wanted to.&lt;br /&gt;
Since I was in the middle of the refactoring of a little personal WPF project, I though that this was the perfect time to test NDepend beyond the testing phase.&lt;/p&gt;
&lt;p&gt;The project is a small WPF application which used only code behind, so I decided to refactor it to switch to MVVM and to change the design from &lt;a href=&quot;https://github.com/ratishphilip/wpfspark&quot;&gt;WPFSpark&lt;/a&gt; to &lt;a href=&quot;http://mahapps.com/&quot;&gt;MahApps&lt;/a&gt;:&lt;/p&gt;
&lt;img src=&quot;/resources/my-journey-with-ndepend/QIASI_old.png&quot; title=&quot;before&quot; alt=&quot;before&quot; style=&quot;width: 350px;vertical-align:middle;&quot;/&gt;
&lt;span style=&quot;font-size:50px;&quot;&gt;➞&lt;/span&gt;
&lt;img src=&quot;/resources/my-journey-with-ndepend/QIASI_new.png&quot; title=&quot;after&quot; alt=&quot;before&quot; style=&quot;width: 350px;vertical-align:middle;&quot;/&gt;
&lt;p&gt;I started using NDepend during the refactoring so it passed some time before all was good, but here is my journey with NDepend.&lt;/p&gt;
&lt;h3&gt;What is NDepend&lt;/h3&gt;
&lt;p&gt;I am not good at presenting things so you&apos;d better go to their &lt;a href=&quot;http://www.ndepend.com/&quot;&gt;site&lt;/a&gt;, but basically it is a static code analyzer which based on your assemblies and code coverage will bring you a ton a interesting information and metrics about your code.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;The installation is ultra easy: you only have to unzip a folder and launch Visual NDepend to validate the license.&lt;br /&gt;
Then you can install the Visual Studio extension or another integration.&lt;/p&gt;
&lt;p&gt;An installer and / or a Chocolatey package would be preferable but it does the job.&lt;/p&gt;
&lt;h3&gt;Integration&lt;/h3&gt;
&lt;p&gt;NDepend have an extensive list of integrations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual NDepend&lt;/li&gt;
&lt;li&gt;NDepend Console&lt;/li&gt;
&lt;li&gt;Visual Studio&lt;/li&gt;
&lt;li&gt;TFS / VSTS&lt;/li&gt;
&lt;li&gt;SonarQube&lt;/li&gt;
&lt;li&gt;CruiseControl.NET&lt;/li&gt;
&lt;li&gt;FinalBuilder&lt;/li&gt;
&lt;li&gt;TeamCity&lt;/li&gt;
&lt;li&gt;Jenkins&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I only used Visual NDepend, Visual Studio and the VSTS extension.&lt;/p&gt;
&lt;h4&gt;Visual NDepend&lt;/h4&gt;
&lt;p&gt;It is the standalone version of NDepend and propose all the functionalities of NDepend as wall as all the installation of Visual Studio extensions, the links to the docs, options, UserVoice and release notes:
&lt;img src=&quot;/resources/my-journey-with-ndepend/VisualNDepend_start.png&quot; alt=&quot;Visual NDepend start page&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It also take care of the updates, which are notified as windows notifications.&lt;/p&gt;
&lt;h4&gt;Visual Studio&lt;/h4&gt;
&lt;p&gt;The integration add a new NDepend menu which allow access to all windows / functionalities of NDepend.&lt;br /&gt;
There is also a quick access through a round icon in the bottom right:
&lt;img src=&quot;/resources/my-journey-with-ndepend/NDepend_quick_access.png&quot; alt=&quot;NDepend Visual Studio quick access&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To create a new NDepend project attached to the current solution it is as simple as click on NDepend Menu / Attach new NDepend project to solution.&lt;br /&gt;
Within a couple of seconds my solution of 9 projects (+1 for the WIX installer) have been analyzed and boy, I had work to do!
&lt;img src=&quot;/resources/my-journey-with-ndepend/QIASI_NDepend_dashboard.png&quot; alt=&quot;NDepend dashboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The integration with Visual Studio is beautiful but could be improved with roslyn integration: having the errors right on the code. It is a &lt;a href=&quot;https://ndepend.uservoice.com/forums/226344-ndepend-user-voice/suggestions/8973031-provide-auto-code-fixes-for-simple-issues&quot;&gt;topic on NDepend&apos;s UserVoice&lt;/a&gt;.&lt;br /&gt;
&lt;em&gt;&lt;strong&gt;Edit&lt;/strong&gt;: it seems that it will be the next big thing of NDepend and has been announced during the &lt;a href=&quot;https://channel9.msdn.com/Events/Build/2017/B8019&quot;&gt;Build event&lt;/a&gt; (from 29:00)&lt;/em&gt;&lt;br /&gt;
Some windows are also oddly placed (the info tooltip appear too low and a part is invisible because it is below the screen).&lt;/p&gt;
&lt;h4&gt;VSTS&lt;/h4&gt;
&lt;p&gt;The extension is available on the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ndepend.ndependextension&quot;&gt;marketplace&lt;/a&gt; and has a free 30 days trial.&lt;/p&gt;
&lt;p&gt;It is also easy to &lt;a href=&quot;http://www.ndepend.com/docs/vsts-integration-ndepend#HowToConfigureHub&quot;&gt;configure&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have got some issues to make it work on my VSTS account but the support was responsive and a fix was issued within a day.&lt;/p&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;p&gt;Once the NDepend project properly configured, including the code coverage which needs some extra steps, you can run an analysis if one haven&apos;t been launched automatically after a project/solution build.&lt;/p&gt;
&lt;p&gt;And now if your project is like mine, you are facing a long list of rules violated, the global status of your debt, a ton of information on your code (types, comments, coverage) and multiple ways to visualize it (&lt;a href=&quot;http://www.ndepend.com/docs/visual-studio-dependency-graph&quot;&gt;dependency graph&lt;/a&gt;, &lt;a href=&quot;http://www.ndepend.com/docs/dependency-structure-matrix-dsm&quot;&gt;dependency matrix&lt;/a&gt;, &lt;a href=&quot;http://www.ndepend.com/docs/treemap-visualization-of-code-metrics&quot;&gt;metrics&lt;/a&gt;, &lt;a href=&quot;http://www.ndepend.com/docs/trend-monitoring&quot;&gt;trends&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;So if you want to fix the debt, you just have to browse the rules and either choose to fix the code, or to adapt the rules to your project. You can deactivate some, or add some exceptions.&lt;/p&gt;
&lt;p&gt;The good news is that all rules are stored in the &lt;code&gt;.ndproj&lt;/code&gt; file, so every rule modification will be applicable for everyone running the project.&lt;br /&gt;
And NDepend detect the modification of the &lt;code&gt;.ndproj&lt;/code&gt; outside of the application (Visual NDepend or Visual Studio) and propose to reload it automatically. That is excellent but can lead to some freeze of Visual Studio.&lt;/p&gt;
&lt;p&gt;One thing I loved is that unlike other static analyzer like Roslyn&apos;s analyzers or SonarQube, which I have used and propose only rules related directly to code and best practices, NDepend also provide some architectural level rules (Namespace dependency, project organization, assembly cohesion and such). That forced me to take some steps back and see the &amp;quot;big picture&amp;quot; of my project, which I didn&apos;t do in a while.&lt;br /&gt;
That have allowed me to realized that most projects on the solution doesn&apos;t belong here but only on another. So I have reorganized entirely my solution and focus on the only project that matter.&lt;/p&gt;
&lt;h3&gt;rules&lt;/h3&gt;
&lt;p&gt;I will not detail all the rules I have fixed on my project, there is too many and not all have a general interest. But there are some that I think are interesting or can be useful for every WPF / XAML project:&lt;/p&gt;
&lt;h4&gt;Avoid namespaces mutually dependent&lt;/h4&gt;
&lt;p&gt;A little refactoring of the designated classes have been sufficient.&lt;br /&gt;
One was referencing types that it not needed to know to work, they were just passed to another class which need only object.&lt;br /&gt;
The second was using a static property of the App class (in root namespace) to get the log file path, so I moved all log related code to a specialized class.&lt;/p&gt;
&lt;h4&gt;Methods should be declared static if possible&lt;/h4&gt;
&lt;p&gt;Cannot be applied to event handler bounded to a XAML window like &lt;code&gt;Loaded&lt;/code&gt;. So the rule has been modified with an exception.&lt;/p&gt;
&lt;h4&gt;Avoid namespaces with few types&lt;/h4&gt;
&lt;p&gt;My project is small so it is normal that this rule raise a warning, but why is there the &lt;code&gt;XamlGeneratedNamespace&lt;/code&gt; in it?&lt;br /&gt;
As stated in his name, it is generated so I have no control over it. I think that this rule must ignore namespace which contains only generated types, so I modified the &lt;code&gt;Discard generated Namespaces from JustMyCode&lt;/code&gt; which target only the &lt;strong&gt;My&lt;/strong&gt; namespace of VB.NET to also target namespaces which contains only types with &lt;code&gt;GeneratedCode&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;notmycode

// First gather assemblies written with VB.NET
let vbnetAssemblies = Application.Assemblies.Where(
   a =&amp;gt; a.SourceDecls.Any(decl =&amp;gt; decl.SourceFile.FileNameExtension.ToLower() == &amp;quot;.vb&amp;quot;))

// Then find the My namespace and its child namespaces.
let vbnetMyNamespaces = vbnetAssemblies.ChildNamespaces().Where(
   n =&amp;gt; n.SimpleName == &amp;quot;My&amp;quot; ||
   n.ParentNamespaces.Any(nParent =&amp;gt; nParent.SimpleName == &amp;quot;My&amp;quot;))

let generatedNamespaces = Application.Assemblies.ChildNamespaces().Where(
    n =&amp;gt; n.ChildTypes.All(t =&amp;gt; t.HasAttribute (&amp;quot;System.CodeDom.Compiler.GeneratedCodeAttribute&amp;quot;)))

from n in vbnetMyNamespaces.Concat(generatedNamespaces)
select n
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Instance fields naming convention / Fields should be declared as private&lt;/h4&gt;
&lt;p&gt;I had to specify names for some of my XAML elements and I choose the PascalCase naming convention, so these two rules was failing because the name wasn&apos;t corresponding to those of a field and because these XAML fields are generated internal be default.&lt;br /&gt;
It is possible to change their modifier through the &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/aa970905(v=vs.110).aspx&quot;&gt;&lt;code&gt;x:FieldModifier=&amp;quot;private&amp;quot;&lt;/code&gt; attribute&lt;/a&gt; but I considered that since it was part generated (everything but the name) it should not be considered as my code and modified the &lt;code&gt;Discard generated Fields from JustMyCode&lt;/code&gt; rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;notmycode
from f in Application.Fields where
  f.HasAttribute (&amp;quot;System.CodeDom.Compiler.GeneratedCodeAttribute&amp;quot;.AllowNoMatch()) ||

  // Eliminate &amp;quot;components&amp;quot; generated in Windows Form Control context
  f.Name == &amp;quot;components&amp;quot; &amp;amp;&amp;amp; f.ParentType.DeriveFrom(&amp;quot;System.Windows.Forms.Control&amp;quot;.AllowNoMatch()) ||
  // Eliminate XAML generated fields
  f.ParentType.Implement(&amp;quot;System.Windows.Markup.IComponentConnector&amp;quot;)
select f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;IComponentConnector&lt;/code&gt; is XAML specific (WPF with this namespace, for UWP it is in &lt;code&gt;Window.UI.Xaml.Markup&lt;/code&gt;) and is automatically implemented for every &lt;code&gt;Window&lt;/code&gt;, &lt;code&gt;Page&lt;/code&gt; and &lt;code&gt;UserControl&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Avoid public methods not publicly visible / Methods that could have a lower visibility&lt;/h4&gt;
&lt;p&gt;All generated methods from properties of my ViewModels are marked but they need to be public for the XAML binding.&lt;/p&gt;
&lt;p&gt;Rule modification to exclude all properties from classes which derive from my ViewModelBase:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;&amp;amp;&amp;amp; !((m.IsPropertyGetter || m.IsPropertySetter) &amp;amp;&amp;amp; m.ParentType.DeriveFrom(&amp;quot;QIASI.Client.ViewModels.ViewModelBase&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Potentially Dead Methods&lt;/h4&gt;
&lt;p&gt;Since XAML is not analyzed by NDepend, this rule list all properties used in XAML binding only. For now I haven&apos;t find a satisfying way to exclude them without avoiding the risk of false negative in the future.&lt;br /&gt;
I looked at &lt;a href=&quot;http://www.ndepend.com/api/webframe.html?NDepend.API_gettingstarted.html&quot;&gt;&lt;code&gt;NDepend.API&lt;/code&gt;&lt;/a&gt; but it doesn&apos;t seems like it is possible to add some information, only to manipulate information provided by NDepend analyzer.&lt;/p&gt;
&lt;h4&gt;Avoid the Singleton pattern&lt;/h4&gt;
&lt;p&gt;I also have modified this rule since it target only types with one static field of its parent type, but I wanted to also track the types which use their interface for the static field type.&lt;br /&gt;
Here is the modified rule part:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;let staticFieldInstances = t.StaticFields.WithFieldTypeIn(t.InterfacesImplemented.Concat(t))
where staticFieldInstances.Count() == 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now my project is (almost) all green!
&lt;img src=&quot;/resources/my-journey-with-ndepend/QIASI_NDepend_dashboard_after.png&quot; alt=&quot;NDepend dashboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I loved use NDepend and will continue to use it.&lt;br /&gt;
I particularly appreciate the flexibility permitted for the rules modification.&lt;br /&gt;
It has some flaws, but the gain and possibilities are totally worth it.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Commit to GitHub with Octokit.net</title>
      <link>https://laedit.net/2016/11/12/GitHub-commit-with-Octokit-net.html</link>
      <pubDate>Sat, 12 Nov 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/11/12/GitHub-commit-with-Octokit-net.html</guid>
      <description>&lt;h4&gt;GitHub api &amp;amp; Octokit.net&lt;/h4&gt;
&lt;p&gt;GitHub has an &lt;a href=&quot;https://developer.github.com/&quot;&gt;API&lt;/a&gt; which amongts many features, can handles commits directly.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/octokit/octokit.net&quot;&gt;Octokit.net&lt;/a&gt; is the .net declinaison of &lt;a href=&quot;https://octokit.github.io/&quot;&gt;octokit&lt;/a&gt;, the official GitHub API client.&lt;br /&gt;
You can add it to a project through &lt;a href=&quot;https://www.nuget.org/packages/octokit&quot;&gt;Nuget&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And to use it you just have to instanciate a &lt;code&gt;GitHubClient&lt;/code&gt; with a &lt;code&gt;ProductHeaderValue&lt;/code&gt; describing your application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var github = new GitHubClient(new ProductHeaderValue(&amp;quot;GithubCommitTest&amp;quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that you already have access to many operations, like accessing the data of a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var user = await github.User.Get(&amp;quot;laedit&amp;quot;);
Console.WriteLine($&amp;quot;laedit have {user.PublicRepos} public repos&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But in order to commit you have to authenticate yourself, otherwise you can have a &amp;quot;not found&amp;quot; error.&lt;/p&gt;
&lt;h4&gt;Authentication&lt;/h4&gt;
&lt;p&gt;There are two ways of doing it, either with login/password or personal access token.&lt;br /&gt;
I strongly recommand the personal access token since it has a limited scope and can be revoked easily at any time.&lt;/p&gt;
&lt;p&gt;For that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;go to your GitHub&apos;s &lt;a href=&quot;https://github.com/settings/tokens&quot;&gt;settings/tokens page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;clic on &amp;quot;Generate new token&amp;quot;&lt;/li&gt;
&lt;li&gt;check at least the &amp;quot;public_repo&amp;quot; scope since it is needed to commit on public repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once generated, you can use it in the GitHub client:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;github.Credentials = new Credentials(&amp;quot;personal_access_token&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;warning&quot;&gt;The code above is only an example, avoid to store your token directly in source code or in a Version Control System.&lt;/p&gt;
&lt;h4&gt;One file / one line commit&lt;/h4&gt;
&lt;p&gt;The API allows to dome some one-line commits for operations on single file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// github variables
var owner = &amp;quot;laedit&amp;quot;;
var repo = &amp;quot;CommitTest&amp;quot;;
var branch = &amp;quot;master&amp;quot;;

// create file
var createChangeSet = await github.Repository.Content.CreateFile(
                                owner,
                                repo,
                                &amp;quot;path/file.txt&amp;quot;,
                                new CreateFileRequest(&amp;quot;File creation&amp;quot;,
                                                      &amp;quot;Hello World!&amp;quot;,
                                                      branch));

// update file
var updateChangeSet = await github.Repository.Content.UpdateFile(
                                owner,
                                repo,
                                &amp;quot;path/file.txt&amp;quot;,
                                new UpdateFileRequest(&amp;quot;File update&amp;quot;,
                                                      &amp;quot;Hello Universe!&amp;quot;,
                                                      createChangeSet.Content.Sha,
                                                      branch));

// delete file
await github.Repository.Content.DeleteFile(
                                owner,
                                repo,
                                &amp;quot;path/file.txt&amp;quot;,
                                new DeleteFileRequest(&amp;quot;File deletion&amp;quot;,
                                                      updateChangeSet.Content.Sha,
                                                      branch));
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;warning&quot;&gt;All content is automatically converted to base64, preventing to commit any file other than text, like an image.&lt;br /&gt;
This limitation will be removed with PR &lt;a href=&quot;https://github.com/octokit/octokit.net/pull/1488&quot;&gt;#1488&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Full commit&lt;/h4&gt;
&lt;p&gt;But it is also possible to acces the whole &lt;a href=&quot;https://developer.github.com/v3/git/&quot;&gt;Git Data&lt;/a&gt; and create a more complex commit step by step.
So you have a precise control on the git database but it require more API calls.&lt;/p&gt;
&lt;p&gt;For example if you want to add a new commit on top of the last commit if the master branch:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the SHA of the latest commit of the master branch&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var headMasterRef = &amp;quot;heads/master&amp;quot;;
// Get reference of master branch
var masterReference = await github.Git.Reference.Get(owner, repo, headMasterRef);
// Get the laster commit of this branch
var latestCommit = await github.Git.Commit.Get(owner, repo, masterReference.Object.Sha);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Create the blob(s) corresponding to your file(s)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// For image, get image content and convert it to base64
var imgBase64 = Convert.ToBase64String(File.ReadAllBytes(&amp;quot;MyImage.jpg&amp;quot;));
// Create image blob
var imgBlob = new NewBlob { Encoding = EncodingType.Base64, Content = (imgBase64) };
var imgBlobRef = await github.Git.Blob.Create(owner, repo, imgBlob);
// Create text blob
var textBlob = new NewBlob { Encoding = EncodingType.Utf8, Content = &amp;quot;Hellow World!&amp;quot; };
var textBlobRef = await github.Git.Blob.Create(owner, repo, textBlob);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Create a new tree with:
&lt;ul&gt;
&lt;li&gt;the SHA of the tree of the latest commit as base&lt;/li&gt;
&lt;li&gt;items based on blob(s) or entirelly new&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// Create new Tree
var nt = new NewTree { BaseTree = latestCommit.Tree.Sha };
// Add items based on blobs
nt.Tree.Add(new NewTreeItem { Path = &amp;quot;MyImage.jpg&amp;quot;, Mode = &amp;quot;100644&amp;quot;, Type = TreeType.Blob, Sha = imgBlobRef.Sha });
nt.Tree.Add(new NewTreeItem { Path = &amp;quot;HelloW.txt&amp;quot;, Mode = &amp;quot;100644&amp;quot;, Type = TreeType.Blob, Sha = textBlobRef.Sha });

// Other way to add a text file directly
// less API call but the content is automatically converted to base64 so only text can be used
var newTreeItem = new NewTreeItem { Mode = &amp;quot;100644&amp;quot;, Type = TreeType.Blob, Content = &amp;quot;Hello Universe!&amp;quot;, Path = &amp;quot;HelloU.txt&amp;quot; };
nt.Tree.Add(newTreeItem);

var newTree = await github.Git.Tree.Create(owner, repo, nt);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Create the commit with the SHAs of the tree and the reference of master branch&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// Create Commit
var newCommit = new NewCommit(&amp;quot;Commit test with several files&amp;quot;, newTree.Sha, masterReference.Object.Sha);
var commit = await github.Git.Commit.Create(owner, repo, newCommit);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Update the reference of master branch with the SHA of the commit&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var headMasterRef = &amp;quot;heads/master&amp;quot;;
// Update HEAD with the commit
await github.Git.Reference.Update(owner, repo, headMasterRef, new ReferenceUpdate(commit.Sha));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once understood it is not quite complex and it allows to learn how Git works with commit creation.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Incremental FTP deploy of a Pretzel site with Creep on AppVeyor</title>
      <link>https://laedit.net/2016/10/30/Incremental-ftp-deploy-with-creep.html</link>
      <pubDate>Sun, 30 Oct 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/10/30/Incremental-ftp-deploy-with-creep.html</guid>
      <description>&lt;p&gt;In a &lt;a href=&quot;/2016/06/14/integrate-pretzel-with-appveyor.html&quot;&gt;previous post&lt;/a&gt;, I have described how &lt;a href=&quot;https://github.com/code52/pretzel&quot;&gt;Pretzel&lt;/a&gt; could be integrated with &lt;a href=&quot;https://www.appveyor.com/&quot;&gt;AppVeyor&lt;/a&gt; in order to generate and deploy a website.&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;deploy&lt;/em&gt; part, I was clearing up all the contents on the FTP through a powershell script and then upload everything at every commit.&lt;br /&gt;
It can be fine at start, but as the site grows it can be very time consuming and prone to errors.&lt;/p&gt;
&lt;p&gt;In order to fix that, I was searching for an incremental deploy tool which handle FTP (since I am limited with that by my hosting) and I came across &lt;a href=&quot;https://github.com/r3c/creep&quot;&gt;creep&lt;/a&gt;:&lt;br /&gt;
it is written in python, can deploy either from a git revision or files hashes and to local file system, FTP or SSH.&lt;/p&gt;
&lt;p&gt;In my case I works with a generated site, which is not versioned in git so I will deploy the content of a folder by using files hashes for comparison, to a distant FTP.&lt;br /&gt;
The site must be deployed only if the generation went well, and for security I will store the FTP user/password in AppVeyor secure environment variable.&lt;/p&gt;
&lt;p&gt;So, first step, add the variables in &lt;code&gt;appveyor.yml&lt;/code&gt;, install the necessary softwares and call the powershell script which will be generating and deploying the site:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;environment:
  ftp_user:
    secure: replace_with_you_appveyor_encrypted_ftp_user
  ftp_password:
    secure: replace_with_you_appveyor_encrypted_ftp_password

install:
  - choco install pretzel -y
  - ps: $env:Path += &amp;quot;;C:\\Python35;C:\\Python35\\Scripts&amp;quot;
  - pip install creep

cache:
  - &apos;%LOCALAPPDATA%\pip\Cache -&amp;gt; appveyor.yml&apos;

build_script:
- ps: .\BakeAndDeploy.ps1

test: off

artifacts:
- path: src/_site
  name: compiled_site
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;PATH&lt;/code&gt; is needed to call &lt;code&gt;pip&lt;/code&gt;.&lt;br /&gt;
The tests are off in order to not waiste time on it.&lt;br /&gt;
The artifact part is needed only if you want to keep a backup of the generated site.&lt;/p&gt;
&lt;p&gt;And the second part, the powershell script &lt;code&gt;BakeAndDeploy.ps1&lt;/code&gt; itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;C:\tools\Pretzel\pretzel bake src

if ($lastExitCode -ne 0)
{
    exit -1
}
else
{
    Write-Host &amp;quot;Starting deploy&amp;quot;
    $envConf = &apos;{{&amp;quot;&amp;quot;default&amp;quot;&amp;quot;: {{&amp;quot;&amp;quot;connection&amp;quot;&amp;quot;: &amp;quot;&amp;quot;ftp://{0}:{1}@laedit.net&amp;quot;&amp;quot;}}}}&apos; -f $env:ftp_user, $env:ftp_password
    creep -e $envConf -d &apos;{&amp;quot;&amp;quot;source&amp;quot;&amp;quot;: &amp;quot;&amp;quot;hash&amp;quot;&amp;quot;}&apos; -b src/_site -y

    if ($lastExitCode -ne 0)
    {
        exit -1
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script starts by calling &lt;code&gt;pretzel bake&lt;/code&gt; on the site&apos;s source, and if the generation went ok it call creep.&lt;br /&gt;
That part is quite confuse because it is a JSON string in a powershell script, with mandatory double quotes for each property. You can also use two files (&lt;code&gt;.creep.env&lt;/code&gt; and &lt;code&gt;.creep.def&lt;/code&gt;) but since I was getting the ftp user/password from environment variable I did not want to have to write two files every time. And sadly creep doesn&apos;t support clean command line parameters for that &lt;a href=&quot;https://github.com/r3c/creep/issues/4&quot;&gt;right now&lt;/a&gt;.&lt;br /&gt;
&lt;code&gt;$envConf&lt;/code&gt; is the creep environment configuration, which in this case define a unique connection to my FTP server, with the user/password from environment variables. It is passed through the &lt;code&gt;-e&lt;/code&gt; switch.&lt;br /&gt;
The creep definition configuration, with the &lt;code&gt;-d&lt;/code&gt; switch state that even if the current directory is in a git repository, creep must use the files hashes as comparison method.&lt;br /&gt;
The &lt;code&gt;-b&lt;/code&gt; switch defines the base directory and &lt;code&gt;-y&lt;/code&gt; always answer &apos;yes&apos; to prompts, allowing a quiet execution.&lt;/p&gt;
&lt;p&gt;And now, at each commit only the new or modified files will be deployed and not the entire site.&lt;/p&gt;
&lt;p&gt;There is only one remaining minor issues: it is displayed as an &lt;a href=&quot;https://ci.appveyor.com/project/laedit/laedit-net/build/1.0.45#L162&quot;&gt;error in AppVeyor&lt;/a&gt; even if it works. It probably come from the way python writes in console.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>NVika 1.0 is out</title>
      <link>https://laedit.net/2016/10/01/nvika-1-0-is-out.html</link>
      <pubDate>Sat, 01 Oct 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/10/01/nvika-1-0-is-out.html</guid>
      <description>&lt;p&gt;&lt;img src=&quot;/resources/nvika-1-0-is-out/nvika-icon.png&quot; alt=&quot;nvika icon&quot; /&gt; After a while, &lt;a href=&quot;https://github.com/laedit/vika&quot;&gt;NVika&lt;/a&gt; is finally out of beta.&lt;/p&gt;
&lt;h4&gt;What does it do&lt;/h4&gt;
&lt;p&gt;Parse analytics reports and show issues on console or send them to the current build server.&lt;/p&gt;
&lt;p&gt;In action:
&lt;img data-gifffer=&quot;/resources/nvika-1-0-is-out/NVika_cmd.gif&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On &lt;a href=&quot;https://www.appveyor.com/&quot;&gt;AppVeyor&lt;/a&gt;:
&lt;a href=&quot;/resources/nvika-1-0-is-out/nvika-appveyor.png&quot;&gt;&lt;img src=&quot;/resources/nvika-1-0-is-out/nvika-appveyor.png&quot; alt=&quot;nvika on AppVeyor&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey, but isn&apos;t that something &lt;a href=&quot;http://www.sonarqube.org&quot;&gt;SonarQube&lt;/a&gt; kinda do?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, and you can even say that SonarQube do much more.&lt;/p&gt;
&lt;p&gt;But NVika has some advantages, since it isn&apos;t doing the analysis part it is smaller and simpler to use, you only need a command line.&lt;/p&gt;
&lt;p&gt;And since it isn&apos;t implying a web server (at least for now) it can be used in pull request to enforce the quality standard of your project.&lt;/p&gt;
&lt;h4&gt;Links&lt;/h4&gt;
&lt;p&gt;NVika is available on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/laedit/vika/releases&quot;&gt;GitHub&lt;/a&gt;: source and binaries&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chocolatey.org/packages/nvika&quot;&gt;Chocolatey&lt;/a&gt;: command line tool&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/nvika.msbuild&quot;&gt;Nuget&lt;/a&gt;: MSBuild target&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Future&lt;/h4&gt;
&lt;p&gt;At first I was thinking about a website, to put the consolidated reports on.  It would have been accessible to anyone, with repo&apos;s settings only accessible by his owner on GitHub, ala &lt;a href=&quot;https://codecov.io/&quot;&gt;CodeCov&lt;/a&gt; or other tool integrated to GitHub.
But I don&apos;t think it is a good idea, since SonarQube already do that and pretty well.&lt;/p&gt;
&lt;p&gt;Another idea is to add some integration to other tools: GitHub on pull requests, Slack, Gitter, others?&lt;br /&gt;
But since all of them need a user authorization it can&apos;t be done only with a command line tool.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What do you think?&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>integrate sonarqube in a .net project with appveyor</title>
      <link>https://laedit.net/2016/09/29/integrate-sonarqube-in-a-dotnet-project-with-appveyor.html</link>
      <pubDate>Thu, 29 Sep 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/09/29/integrate-sonarqube-in-a-dotnet-project-with-appveyor.html</guid>
      <description>&lt;h3&gt;What is SonarQube&lt;/h3&gt;
&lt;p&gt;From their &lt;a href=&quot;https://www.sonarqube.org/&quot;&gt;site&lt;/a&gt;:&lt;br /&gt;
SonarQube is an open platform to manage code quality. As such, it covers the 7 axes of code quality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Architecture &amp;amp; design&lt;/li&gt;
&lt;li&gt;Duplications&lt;/li&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;Complexity&lt;/li&gt;
&lt;li&gt;Potential bugs&lt;/li&gt;
&lt;li&gt;Coding rules&lt;/li&gt;
&lt;li&gt;Comments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So basically with this you can be pretty sure that your code is good.
And since it is open source, you can download it and install a copy on your own server.
Or you can use the &lt;a href=&quot;https://sonarqube.com&quot;&gt;instance&lt;/a&gt; dedicated to open source projects.&lt;/p&gt;
&lt;h3&gt;Add scan to build&lt;/h3&gt;
&lt;p&gt;SonarQube works with MSBuild for .net projects through the &lt;a href=&quot;https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+MSBuild&quot;&gt;Scanner for MSBuild&lt;/a&gt;, which is available on &lt;a href=&quot;https://chocolatey.org/packages/msbuild-sonarqube-runner&quot;&gt;Chocolatey&lt;/a&gt;. There is also an unofficial plugin for &lt;a href=&quot;https://github.com/jmecsoftware/sonar-fsharp-plugin&quot;&gt;F#&lt;/a&gt; but it isn&apos;t available on the public instance of sonarqube.&lt;/p&gt;
&lt;p&gt;All following code examples will be in &lt;em&gt;classic&lt;/em&gt; command line and in &lt;a href=&quot;http://fsharp.github.io/FAKE/&quot;&gt;FAKE&lt;/a&gt;, which is a build automation system I use in my projects, like for &lt;a href=&quot;https://github.com/laedit/vika/blob/master/build/build.fsx&quot;&gt;NVika&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Installation&lt;/h4&gt;
&lt;p&gt;Two ways to install it, either download it from the &lt;a href=&quot;https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner+for+MSBuild&quot;&gt;SonarQube Scanner for MSBuild page&lt;/a&gt; or through Chocolatey:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;choco install &amp;quot;msbuild-sonarqube-runner&amp;quot; -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or with FAKE, which have a lot of helpers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-fsharp&quot;&gt;&amp;quot;msbuild-sonarqube-runner&amp;quot; |&amp;gt; Choco.Install id
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Scan&lt;/h4&gt;
&lt;p&gt;The scan must be started before the build then ended after the build:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Begin:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;MSBuild.SonarQube.Runner.exe begin /k:&amp;quot;laedit:Vika&amp;quot; /n:&amp;quot;Vika&amp;quot; /v:&amp;quot;0.1.4&amp;quot; /d:sonar.host.url=https://sonarqube.com /d:sonar.login=[SonarToken]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or with FAKE:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-fsharp&quot;&gt;SonarQube Begin (fun p -&amp;gt;
        {p with
             ToolsPath = &amp;quot;MSBuild.SonarQube.Runner.exe&amp;quot;
             Key = &amp;quot;laedit:Vika&amp;quot;
             Name = &amp;quot;Vika&amp;quot;
             Version = version
             Settings = [ &amp;quot;sonar.host.url=https://sonarqube.com&amp;quot;; &amp;quot;sonar.login=&amp;quot; + environVar &amp;quot;SonarQube_Token&amp;quot; ] })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mandatory parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/k&lt;/code&gt; | &lt;code&gt;Key&lt;/code&gt;: key of the project; Must be unique; Allowed characters are: letters, numbers, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;:&lt;/code&gt;, with at least one non-digit.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/n&lt;/code&gt; | &lt;code&gt;Name&lt;/code&gt;: name of the project; Displayed on the web interface.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v&lt;/code&gt; | &lt;code&gt;Version&lt;/code&gt;: version of the project.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/d:sonar.host.url&lt;/code&gt;: SonarQube server url; default: &lt;code&gt;http://localhost:9000&lt;/code&gt;; must be set to &lt;code&gt;https://sonarqube.com&lt;/code&gt; to use the SonarQube public instance.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/d:sonar.login&lt;/code&gt;: your login or &lt;a href=&quot;https://docs.sonarqube.org/display/SONAR/User+Token&quot;&gt;authentication token&lt;/a&gt;. If login is used, you must use the &lt;code&gt;sonar.password&lt;/code&gt; with your password as well but this is highly unsecure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;SonarQube_Token&lt;/code&gt; is an &lt;a href=&quot;https://www.appveyor.com/&quot;&gt;AppVeyor&lt;/a&gt; &lt;a href=&quot;https://www.appveyor.com/docs/build-configuration/#secure-variables&quot;&gt;secure environment variable&lt;/a&gt; wich contains the SonarQube token. While not mandatory it is recommended to generate one by project scanned.&lt;/p&gt;
&lt;p&gt;There is also a bunch of other &lt;a href=&quot;https://docs.sonarqube.org/display/SONAR/Analysis+Parameters&quot;&gt;parameters available&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build&lt;/strong&gt; like usual, with msbuild for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;&amp;quot;C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe&amp;quot; /t:Rebuild
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or an helper:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-fsharp&quot;&gt;Target &amp;quot;BuildApp&amp;quot; (fun _ -&amp;gt;
    !! &amp;quot;src/NVika/NVika.csproj&amp;quot;
      |&amp;gt; MSBuildRelease buildResultDir &amp;quot;Build&amp;quot;
      |&amp;gt; Log &amp;quot;AppBuild-Output: &amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the &lt;strong&gt;end&lt;/strong&gt; part:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;MSBuild.SonarQube.Runner.exe end /d:sonar.login=[SonarToken]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or with FAKE:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-fsharp&quot;&gt;SonarQube End (fun p -&amp;gt;
        {p with
             ToolsPath = &amp;quot;MSBuild.SonarQube.Runner.exe&amp;quot;
             Settings = [ &amp;quot;sonar.login=&amp;quot; + environVar &amp;quot;SonarQube_Token&amp;quot; ]
        })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since all security related parameters aren&apos;t write to the disk, you have to pass them again to the end part. Meaning that if you have used &lt;code&gt;login&lt;/code&gt; + &lt;code&gt;password&lt;/code&gt; in begin you have to pass them both again in end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; there is no need to create a project in the web interface, it is automatically created on the first analysis.&lt;/p&gt;
&lt;h4&gt;Frequency&lt;/h4&gt;
&lt;p&gt;It&apos;s up to you to determine the frequency of the SonarQube scans, but int order to avoid abuse of the SonarQube public instance and because a scan is not needed for every local build I choose to start one only on AppVeyor and for the original repository (not forks) because I use AppVeyor&apos;s &lt;a href=&quot;https://www.appveyor.com/docs/build-configuration/#secure-variables&quot;&gt;secure environment variables&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-fsharp&quot;&gt;// check if the build is on AppVeyor and for the original repository
let isOriginalRepo = environVar &amp;quot;APPVEYOR_REPO_NAME&amp;quot; = &amp;quot;laedit/vika&amp;quot;
let isAppVeyorBuild = buildServer = AppVeyor

// build dependencies: SonarQube scan will be launched only if the condition is true
&amp;quot;Clean&amp;quot;
  ==&amp;gt; &amp;quot;RestorePackages&amp;quot;
  =?&amp;gt; (&amp;quot;BeginSonarQube&amp;quot;, isAppVeyorBuild &amp;amp;&amp;amp; isOriginalRepo)
  ==&amp;gt; &amp;quot;BuildApp&amp;quot;
  =?&amp;gt; (&amp;quot;EndSonarQube&amp;quot;, isAppVeyorBuild &amp;amp;&amp;amp; isOriginalRepo)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Additional settings&lt;/h4&gt;
&lt;p&gt;You might want to subscribe to &lt;a href=&quot;https://docs.sonarqube.org/display/SONAR/Notifications+-+Administration&quot;&gt;notifications&lt;/a&gt; in order to be aware of each new issues or changes of the quality gate status.&lt;/p&gt;
&lt;p&gt;But if you use the &lt;a href=&quot;https://sonarqube.com&quot;&gt;public instance&lt;/a&gt;, subscribe only for specific projects if you want to avoid getting spammed with notifications of all projects.&lt;/p&gt;
&lt;p&gt;Also, even if SonarQube doesn&apos;t propose built-in badges, &lt;a href=&quot;http://shields.io/&quot;&gt;shields&lt;/a&gt; do, so you can add one to your project&apos;s ReadMe.&lt;/p&gt;
&lt;h3&gt;Use SonarQube in Pull Request builds&lt;/h3&gt;
&lt;p&gt;SonarQube have a &lt;a href=&quot;https://docs.sonarqube.org/display/PLUG/GitHub+Plugin&quot;&gt;GitHub&lt;/a&gt;, which can handle the pull request build without pushing the results to SonarQube.&lt;/p&gt;
&lt;p&gt;You just have to add several parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sonar.analysis.mode=preview&lt;/code&gt;: avoid to send the results to the SonarQube instance&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sonar.github.pullRequest=&amp;quot; + environVar &amp;quot;APPVEYOR_PULL_REQUEST_NUMBER&amp;quot;&lt;/code&gt;: pull request number, here from AppVeyor&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sonar.github.repository=laedit/vika&lt;/code&gt;: identification of the repository with format &amp;lt;organisation/repo&amp;gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sonar.github.oauth=&amp;quot; + environVar &amp;quot;Sonar_PR_Token&amp;quot;&lt;/code&gt;: GitHub personal access token, with the scopes &lt;code&gt;public_repo&lt;/code&gt; (or &lt;code&gt;repo&lt;/code&gt; for private repositories) and &lt;code&gt;repo:status&lt;/code&gt; in order to update the PR status&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this example, &lt;code&gt;Sonar_PR_Token&lt;/code&gt; is the GitHub token embedded as AppVeyor &lt;a href=&quot;https://www.appveyor.com/docs/build-configuration/#secure-variables&quot;&gt;secure environment variable&lt;/a&gt;. They are not accessible from PRs, unless you check the &amp;quot;Enable secure variables in Pull Requests from the same repository only&amp;quot; box in General tab of your AppVeyor&apos;s repo settings.&lt;/p&gt;
&lt;p class=&quot;warning&quot;&gt;Even if only PRs from the same repository will have access to the content of the secure environment variable, they can still be visible in the logs of your AppVeyor builds, so be careful.&lt;/p&gt;
&lt;p&gt;But if you still want to implement it, be sure to add these parameters only on PRs, for example with FAKE:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-fsharp&quot;&gt;let isPR = environVar &amp;quot;APPVEYOR_PULL_REQUEST_NUMBER&amp;quot; |&amp;gt; isNull |&amp;gt; not

Target &amp;quot;BeginSonarQube&amp;quot; (fun _ -&amp;gt;
    &amp;quot;msbuild-sonarqube-runner&amp;quot; |&amp;gt; Choco.Install id

let sonarSettings = match isPR with
                        | false -&amp;gt; [ &amp;quot;sonar.host.url=https://sonarqube.com&amp;quot;; &amp;quot;sonar.login=&amp;quot; + environVar &amp;quot;SonarQube_Token&amp;quot; ]
                        | true -&amp;gt; [
                                    &amp;quot;sonar.host.url=https://sonarqube.com&amp;quot;;
                                    &amp;quot;sonar.login=&amp;quot; + environVar &amp;quot;SonarQube_Token&amp;quot;;
                                    &amp;quot;sonar.analysis.mode=preview&amp;quot;;
                                    &amp;quot;sonar.github.pullRequest=&amp;quot; + environVar &amp;quot;APPVEYOR_PULL_REQUEST_NUMBER&amp;quot;;
                                    &amp;quot;sonar.github.repository=laedit/vika&amp;quot;;
                                    &amp;quot;sonar.github.oauth=&amp;quot; + environVar &amp;quot;Sonar_PR_Token&amp;quot;
                                  ]

    SonarQube Begin (fun p -&amp;gt;
        {p with
             ToolsPath = &amp;quot;MSBuild.SonarQube.Runner.exe&amp;quot;
             Key = &amp;quot;laedit:Vika&amp;quot;
             Name = &amp;quot;Vika&amp;quot;
             Version = version
             Settings = sonarSettings })
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SonarLint&lt;/h3&gt;
&lt;p&gt;If you want to find issues before committing, you can use &lt;a href=&quot;https://www.sonarlint.org/&quot;&gt;SonarLint&lt;/a&gt;, either in your favorite IDE or through &lt;a href=&quot;https://www.sonarlint.org/commandline/&quot;&gt;command line&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>migrating the old reader bookmark from addon sdk to webextensions</title>
      <link>https://laedit.net/2016/06/27/migrating-the-old-reader-bookmark-from-addon-sdk-to-webextensions.html</link>
      <pubDate>Mon, 27 Jun 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/06/27/migrating-the-old-reader-bookmark-from-addon-sdk-to-webextensions.html</guid>
      <description>&lt;p class=&quot;tldr&quot;&gt;&lt;a href=&quot;https://github.com/laedit/the-old-reader-bookmark/compare/7ae5b664a477db4a65aab0ceb63698496c103583...ccf3eac7a37ef260e44cda7d3847cfe9bc55faf3&quot;&gt;Here&lt;/a&gt; is the comparison of the commits pushed for the addon&apos;s migration.&lt;/p&gt;
&lt;h3&gt;The Addon&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/the-old-reader-bookmark/&quot;&gt;The Old Reader - Bookmark&lt;/a&gt; is an addon wich add a button to easily add a page or a selection of the page to &lt;a href=&quot;https://theoldreader.com&quot;&gt;The Old Reader&lt;/a&gt; bookmarks.&lt;br /&gt;
It is done with &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/SDK&quot;&gt;Firefox Addon SDK&lt;/a&gt;, both high-level and low-level APIs.&lt;/p&gt;
&lt;h3&gt;Preparation&lt;/h3&gt;
&lt;p&gt;First thing to do: read some &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions&quot;&gt;docs&lt;/a&gt; on &lt;a href=&quot;https://wiki.mozilla.org/WebExtensions&quot;&gt;WebExtensions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And a good thing to have is &lt;a href=&quot;https://blog.mozilla.org/addons/2016/04/14/developing-extensions-with-web-ext-1-0/&quot;&gt;&lt;code&gt;web-ext&lt;/code&gt;&lt;/a&gt; (available on &lt;a href=&quot;https://github.com/mozilla/web-ext&quot;&gt;GitHub&lt;/a&gt;), which is a command line tool aiming to help running and debugging WebExtensions.&lt;br /&gt;
It is available through &lt;code&gt;npm&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --global web-ext
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to ease the process, I have created a small cmd file which will update &lt;code&gt;web-ext&lt;/code&gt; if necessary and run it :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;@echo off
:: update web-ext
call npm update -g web-ext

echo Exit Code is %errorlevel%
if &amp;quot;%ERRORLEVEL%&amp;quot; == &amp;quot;1&amp;quot; exit /B 1

:: run web-ext
if [%1]==[] (
    web-ext -s &amp;quot;src&amp;quot; run --firefox-binary &amp;quot;C:\Program Files\Firefox Developer Edition\firefox.exe&amp;quot;
)

if [%1]==[current] (
    web-ext -s &amp;quot;src&amp;quot; run --firefox-binary &amp;quot;C:\Program Files\Mozilla Firefox\firefox.exe&amp;quot;
)

if [%1]==[beta] (
    web-ext -s &amp;quot;src&amp;quot; run --firefox-binary &amp;quot;C:\Program Files (x86)\Mozilla Firefox Beta\firefox.exe&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;web-ext&lt;/code&gt; is run against the &lt;code&gt;src&lt;/code&gt; folder which contains the WebExtension source and launch Firefox Developer Edition by default but can launch the current or beta version with the right argument.&lt;/p&gt;
&lt;h3&gt;Migration&lt;/h3&gt;
&lt;p&gt;For the detail, lets begin with the folder tree before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;|- data
|  |- oldreadericon-16.png
|  |- oldreadericon-32.png
|  +- oldreadericon-64.png
|
|- lib
|  +- main.js
|
|- locale
|  |- en-US.properties
|  +- fr-FR.properties
|
|- icon.png
|- icon64.png
+- package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;|- _locales
|  |- en
|  |  +- messages.json (moved and migrated from en-US.properties)
|  +- fr
|     +- messages.json (moved and migrated from fr-FR.properties)
|
|- content_scripts
|  |- getSelection.js
|  +- postNewBookmark.js
|
|- icons (renamed from data)
|  |- oldreadericon-16.png
|  |- oldreadericon-32.png
|  |- oldreadericon-48.png (moved from icon.png)
|  +- oldreadericon-64.png
|
|- background-script.cs (moved and migrated from main.js)
+- manifest.json (migrated from package.json)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing complicated, just some files moved except for three parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;localization&lt;/li&gt;
&lt;li&gt;manifest&lt;/li&gt;
&lt;li&gt;logic&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;localization&lt;/h4&gt;
&lt;p&gt;That was the easiest part:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rename folder from &lt;code&gt;locale&lt;/code&gt; to &lt;code&gt;_locales&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a subfolder fo each language (instead of the culture previously used)&lt;/li&gt;
&lt;li&gt;migrate each file from &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/l10n&quot;&gt;properties&lt;/a&gt; to &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Internationalization&quot;&gt;the new format in json&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each entry must have a key which will be used in the extension and a &lt;code&gt;message&lt;/code&gt; property which contains the translation. The &lt;code&gt;description&lt;/code&gt; property isn&apos;t mandatory but could be useful.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;theOldReaderSelfBookmarkMessage&amp;quot;: {
    &amp;quot;message&amp;quot;: &amp;quot;If you were to bookmark The Old Reader with The Old Reader then the universe will fold in on itself and become a very large black hole.&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;Message when the user want to bookmark the old reader itself&amp;quot;
  },
  &amp;quot;addonDescription&amp;quot;: {
    &amp;quot;message&amp;quot;: &amp;quot;Bookmark the current page or selection in The Old Reader (premium membership needed)&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;addon description&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;manifest&lt;/h4&gt;
&lt;p&gt;The manifest stay in json but the format change to be &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json&quot;&gt;close that the one used by Chrome&lt;/a&gt;.&lt;br /&gt;
As you can see by comparing the old &lt;a href=&quot;https://github.com/laedit/the-old-reader-bookmark/blob/7ae5b664a477db4a65aab0ceb63698496c103583/src/package.json&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt; with the new &lt;a href=&quot;https://github.com/laedit/the-old-reader-bookmark/blob/master/src/manifest.json&quot;&gt;&lt;code&gt;manifest.json&lt;/code&gt;&lt;/a&gt;, some properties are quite the same:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the title is now the name&lt;/li&gt;
&lt;li&gt;the version doesn&apos;t move&lt;/li&gt;
&lt;li&gt;the description can use a translated message, you have to use the following pattern: &lt;code&gt;__MSG_messageKey__&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some are new:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the key &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/manifest_version&quot;&gt;&lt;code&gt;manifest_version&lt;/code&gt;&lt;/a&gt; is mandatory, with the value &lt;code&gt;2&lt;/code&gt; for now&lt;/li&gt;
&lt;li&gt;the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/default_locale&quot;&gt;&lt;code&gt;default_locale&lt;/code&gt;&lt;/a&gt; key is also mandatory if you have a &lt;code&gt;_locales&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;all icons are now defined in the manifest under the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/icons&quot;&gt;&lt;code&gt;icons&lt;/code&gt;&lt;/a&gt; key&lt;/li&gt;
&lt;li&gt;the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/applications&quot;&gt;&lt;code&gt;applications&lt;/code&gt;&lt;/a&gt; key with the &lt;code&gt;gecko&lt;/code&gt; subkey is specific to firefox, it is used in my addon to define:
&lt;ul&gt;
&lt;li&gt;the minimal version of firefox supported&lt;/li&gt;
&lt;li&gt;the addon id - you can see that I have suffixed it with &lt;code&gt;@jetpack&lt;/code&gt;: the id must now contains a &lt;code&gt;@&lt;/code&gt;, but the &lt;code&gt;jetpack&lt;/code&gt; part could be replaced by anything at your like&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/permissions&quot;&gt;&lt;code&gt;persmissions&lt;/code&gt;&lt;/a&gt; allow to define the permissions needed by the addon. In my case I need the &lt;code&gt;activeTab&lt;/code&gt; to get the url of the current tab, and &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt; to execute my addon on any website.&lt;/li&gt;
&lt;li&gt;the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/browser_action&quot;&gt;&lt;code&gt;browser_action&lt;/code&gt;&lt;/a&gt; defines a button on the browser&apos;s toolbar, with an icon and a title&lt;/li&gt;
&lt;li&gt;the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/background&quot;&gt;&lt;code&gt;background&lt;/code&gt;&lt;/a&gt; defines the background scripts and page which are loaded at the launch of the extension, generally they contains the logic of the extension&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And some have disappear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I haven&apos;t find where to put the license information&lt;/li&gt;
&lt;li&gt;same for author&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;logic&lt;/h4&gt;
&lt;p&gt;The logic of the extension take place in the background script, but since WebExtensions support &lt;a href=&quot;https://wiki.mozilla.org/Electrolysis&quot;&gt;e10s&lt;/a&gt; all interactions with the IHM or current page must be injected through a &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts&quot;&gt;content script&lt;/a&gt;, even an &lt;code&gt;alert(...)&lt;/code&gt; for showing a small information.&lt;/p&gt;
&lt;p&gt;So, there is 4 parts in my &lt;a href=&quot;https://github.com/laedit/the-old-reader-bookmark/blob/7ae5b664a477db4a65aab0ceb63698496c103583/src/lib/main.js&quot;&gt;old logic script&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;toolbar button declaration, wich have been moved to the manifest.json&lt;/li&gt;
&lt;li&gt;show an alert if the current tab is on theoldreader.com&lt;/li&gt;
&lt;li&gt;get the selection of the current tab if any&lt;/li&gt;
&lt;li&gt;post the selection and the url of the current tab to the old reader in a new tab&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the alert I had to use the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/executeScript&quot;&gt;&lt;code&gt;tabs.executeScript&lt;/code&gt;&lt;/a&gt; method which allow to inject a code or a script in a tab:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if(/^http(?:s?)\:\/\/theoldreader\.com/i.test(tab.url))
{
    browser.tabs.executeScript({ code: &amp;quot;alert(&apos;&amp;quot; + chrome.i18n.getMessage(&amp;quot;theOldReaderSelfBookmarkMessage&amp;quot;) + &amp;quot;&apos;);&amp;quot; });
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also see the use of &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/i18n/getMessage&quot;&gt;&lt;code&gt;i18n.getMessage&lt;/code&gt;&lt;/a&gt; to get a translated message from the locales.&lt;/p&gt;
&lt;p&gt;After that, I inject the content of &lt;a href=&quot;https://github.com/laedit/the-old-reader-bookmark/blob/ccf3eac7a37ef260e44cda7d3847cfe9bc55faf3/src/content_scripts/getSelection.js&quot;&gt;&lt;code&gt;getSelection.js&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;browser.tabs.executeScript({ file: &amp;quot;content_scripts/getSelection.js&amp;quot; }, postToOldReadBookmarks);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which get the selection of the current tab and return it. The second parameter is a callback method which handle the return of the script execution, in my extension it is the 4th part which post the data to the old reader in a new tab.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function postToOldReadBookmarks(selections) {
    browser.tabs.create({ index: currentTabIndex + 1, url: &amp;quot;https://theoldreader.com/bookmarks/bookmark&amp;quot; }, function (tab) {
        browser.tabs.executeScript(tab.id, { file: &amp;quot;content_scripts/postNewBookmark.js&amp;quot; }, function () {
            chrome.tabs.sendMessage(tab.id, {url: currentTabUrl, html: selections});
        });
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the Addon SDK it was possible to post directly the data to a new tab but with WebExtensions it is not (yet?) possible, instead I use a &lt;a href=&quot;https://github.com/laedit/the-old-reader-bookmark/blob/ccf3eac7a37ef260e44cda7d3847cfe9bc55faf3/src/content_scripts/postNewBookmark.js&quot;&gt;form created in a content script&lt;/a&gt;.&lt;br /&gt;
Due to some limitations and &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1272890&quot;&gt;bugs&lt;/a&gt;, I must inject the script to a &apos;real&apos; page, not a &lt;code&gt;about:blank&lt;/code&gt; page or one included in my extension.&lt;br /&gt;
After that I use the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/sendMessage&quot;&gt;&lt;code&gt;tabs.sendMessage&lt;/code&gt;&lt;/a&gt; to pass the data to the script, which will inject it as hidden input value before submitting the form.&lt;/p&gt;
&lt;p&gt;And all works!&lt;/p&gt;
&lt;h3&gt;conclusion&lt;/h3&gt;
&lt;p&gt;Right now WebExtensions are still in development and even if Firefox 48 have brought the first stable release I prefer to wait until at least v49 (which fix some issues I have encontered) is out before publishing the update of my addon.&lt;br /&gt;
Nevertheless WebExtensions are very promising and the shared APIs with chrome, opera and even edge is a big asset, I will have to test my extension on those!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>integrate pretzel with appveyor</title>
      <link>https://laedit.net/2016/06/14/integrate-pretzel-with-appveyor.html</link>
      <pubDate>Tue, 14 Jun 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/06/14/integrate-pretzel-with-appveyor.html</guid>
      <description>&lt;p&gt;&lt;a href=&quot;https://github.com/code52/pretzel&quot;&gt;Pretzel&lt;/a&gt; is a static web site generator, much like &lt;a href=&quot;http://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; but in .net.&lt;br /&gt;
So while Jekyll users can use TravisCI or directly GitHub pages to generate their website we don&apos;t have this possibility with Pretzel.&lt;/p&gt;
&lt;p&gt;Luckily, &lt;a href=&quot;https://appveyor.com&quot;&gt;Appveyor&lt;/a&gt; provides the same features than TravisCI but in a Windows environment. And it is free, so you just have to create an account or login with your GitHub account.&lt;/p&gt;
&lt;p&gt;Like travis, it is based on a yaml configuration file, named &lt;code&gt;appveyor.yml&lt;/code&gt;, like the &lt;a href=&quot;https://github.com/laedit/laedit.net/blob/master/appveyor.yml&quot;&gt;one&lt;/a&gt; for my website.&lt;/p&gt;
&lt;h4&gt;Preparation&lt;/h4&gt;
&lt;p&gt;First, we need to install pretzel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;install:
  - choco install pretzel -y

cache:
  - C:\tools\Pretzel -&amp;gt; appveyor.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;cache&lt;/code&gt; instruction indicates appveyor to store in &lt;a href=&quot;https://www.appveyor.com/docs/build-cache&quot;&gt;cache&lt;/a&gt; the content of the &lt;code&gt;C:\tools\Pretzel&lt;/code&gt; folder until the appveyor.yml is modified.&lt;/p&gt;
&lt;p class=&quot;warning&quot;&gt;The installation folder of pretzel will soon be modified to comply to &lt;a href=&quot;https://chocolatey.org&quot;&gt;Chocolatey&lt;/a&gt; rules.&lt;/p&gt;
&lt;h4&gt;Build&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;build_script&lt;/code&gt; is simple: just run the &lt;code&gt;bake&lt;/code&gt; command of pretzel on the site source.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;build_script:
- C:\tools\Pretzel\pretzel bake src

artifacts:
- path: src/_site
  name: compiled_site
&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;warning&quot;&gt;Since the pretzel folder is in the appveyor cache, we cannot use the pretzel exe from the PATH, we must use the full path.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;artifacts&lt;/code&gt; instruction defines the files or folder you want to save after a build. You can access and download these files directly on the appveyor website.&lt;/p&gt;
&lt;h4&gt;Test&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;test: off
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I think this line is self-explanatory: no test at all. For now at least, I plan to add a link checker soon. And maybe some sort of integration tests, we&apos;ll see.&lt;/p&gt;
&lt;h4&gt;Deploy&lt;/h4&gt;
&lt;p class=&quot;info&quot;&gt;Since this post I have set an &lt;a href=&quot;/2016/10/30/Incremental-ftp-deploy-with-creep.html&quot;&gt;incremental FTP deploy&lt;/a&gt; with &lt;a href=&quot;https://github.com/r3c/creep/&quot;&gt;creep&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, now the goal is to deploy the artifact in a FTP server.&lt;br /&gt;
You can also deploy it to GitHub, Azure and other, appveyor support quite a few &lt;a href=&quot;http://www.appveyor.com/docs/deployment&quot;&gt;deployment supports&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;before_deploy:
- ps: .\Clear-FtpDirectory.ps1

deploy:
- provider: FTP
  host: laedit.net
  protocol: ftp
  username: zlaeditn12713ne
  password:
    secure: eK/zCvZEGU6BcRfo1CoYnlrLD7SoyaUyOb3aIq8CkmQ=
  folder: /httpdocs/laedit
  application: src\compiled_site.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;before_deploy&lt;/code&gt; instruction run a powershell (indicated by the &lt;code&gt;ps:&lt;/code&gt; prefix) which cleans the destination folder.&lt;br /&gt;
I use powershell but you can use a &lt;a href=&quot;http://fsharp.github.io/FAKE/&quot;&gt;Fake&lt;/a&gt; or your favorite build helper if you prefer.&lt;/p&gt;
&lt;p&gt;Then the &lt;code&gt;deploy&lt;/code&gt; instruction list all the deploy informations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;provider type&lt;/li&gt;
&lt;li&gt;FTP host&lt;/li&gt;
&lt;li&gt;protocol used&lt;/li&gt;
&lt;li&gt;username used&lt;/li&gt;
&lt;li&gt;password encrypted by appveyor and only decrypted during build (and not accessible during PR build +1 for security)&lt;/li&gt;
&lt;li&gt;destination folder&lt;/li&gt;
&lt;li&gt;the artifact to deploy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And since there is no constraints to the deploy, it is executed at each commit.&lt;/p&gt;
&lt;h4&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;You now know how to use pretzel and appveyor to build and publish your shiny static website&lt;br /&gt;
You are just a commit away to your next post.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>hi</title>
      <link>https://laedit.net/2016/05/27/hi.html</link>
      <pubDate>Fri, 27 May 2016 00:00:00 +0000</pubDate>
      <author>Jérémie Bertrand</author>
      <guid isPermaLink="true">https://laedit.net/2016/05/27/hi.html</guid>
      <description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;This is my personal blog.&lt;br /&gt;
I wanted to make a multilingual blog with &lt;a href=&quot;http://code52.org/pretzel/&quot;&gt;Pretzel&lt;/a&gt; but right now the project need some modification to to that.&lt;br /&gt;
So basically this is still a WIP for now...&lt;br /&gt;
Meanwhile I will be discovering the limits and possibilities of Pretzel, and integrate it with &lt;a href=&quot;http://www.appveyor.com&quot;&gt;AppVeyor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is no comments for now but you can contact me through &lt;a href=&quot;https://piaille.fr/@laedit&quot;&gt;mastodon&lt;/a&gt; or &lt;a href=&quot;javascript:window.location.href = &apos;mailto:&apos; + [&apos;contact&apos;,&apos;laedit.net&apos;].join(&apos;@&apos;)&quot; title=&quot;Mail&quot;&gt;mail&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;What is Pretzel&lt;/h4&gt;
&lt;p&gt;Pretzel is a static website generator, like &lt;a href=&quot;http://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; but in .net.&lt;/p&gt;
&lt;h4&gt;What is AppVeyor&lt;/h4&gt;
&lt;p&gt;AppVeyor is a Continuous Integration and Deployment service backed by Windows Azure, like &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis&lt;/a&gt; but for Windows environments.&lt;/p&gt;</description>
    </item>
    

  </channel>
</rss>