Automating VSCode Extension Publishing and Changelog Generation with Gitlab-CI
Tags: gitlab, vscode
Published: 2019-08-26 05:26:28

When I build my first [real VSCode Extension](https://marketplace.visualstudio.com/items?itemName=Jhecht.jhecht-moji) I released it by hand every time. This also meant that I was making up the CHANGELOG every time as well. I had roughly looked into what I would need to do in order to make the changelog / marketplace publishing automated. I never got around to doing this, since it looked to involve more time than I had, and since it was "only one extension, I'll likely not update it _that_ often."

 After creating [Git Angular](https://marketplace.visualstudio.com/items?itemName=Jhecht.git-angular) I decided I would use it as a test bed for two things: fiddling around with [Standard Version](https://github.com/conventional-changelog/standard-version) and finally cementing a way to publish extensions from Gitlab CI. 

# NOTES:

Standard Version uses the Angular-style commits to create the changelog. This means that if you are coming from a different commit style, such as Emoji Commit, or just "whatever information seems relevant in the commit", then that part is going to be difficult. There are other presets, but I've never used them so I don't know much about them or how they are. I'm looking into creating one for the Git Emoji commit style, but don't hold your breath on when that will be available.

Also, if you are like me and you look for an "elegant"-_ish_ solution, this won't be it. This is what I came to terms that I would have to do in order to get it (more or less) automated, and I figure it is worth documenting the process for some other soul to see later on. 

# Tl;DR:

1. Make sure you have a Personal Access Token (PAT) from both Gitlab (created under User Settings > Access Tokens) and Azure Devops (instructions linked below for this step). Your Gitlab PAT should have write repository access. I would recommend maybe making these expire every month or so _just in case_ it somehow gets exposed. Sure, you have to remember to remake the token in a month, but the alternative is someone hijacking everything.
2. Add those two tokens into the Settings > CI/CD > Variables. I named the Azure Token PAT and the Gitlab PAT to GL_PAT. You can name them whatever, but remember what you name them later on.
  * Make sure that the "publisher" value in your package.json is not taken, otherwise your deploys will have issues.
3. Copy the [.gitlab-ci.yml](#gitlabciyml) file from the section below, editing the repository URL.

# First Thing: make an extension.

I obviously have Git Angular that I can use, but if you have yet to make an extension then you should use [the guide](https://code.visualstudio.com/api/get-started/your-first-extension). Basically you are going to want to install `yo` and `generator-code`. You then run `yo code` and follow the prompt. If you don't exactly know what sort of voodoo you want this extension to be, that's fine. Just make sure you are following the commit pattern mentioned by the Standard Version page I linked above. It makes most of your commits look something like `feat(settings): added in settings page` or `fix(submodule): fixed logic error in submodule that would cause errors`.

# Second Thing: Prep a Gitlab Repo

Go over to your Gitlab and make a new blank repo for it. Go into your extension directory and make sure you run 

```
# only run this command if you didn't already initiate the folder as a git repo
git init 
# Change "YOUR-USERNAME" and "YOUR-REPO" to their respective values
git remote add https://gitlab.com/YOUR-USERNAME/YOUR-REPO.git origin
```
For my Git Angular extension, this would be 

```
git remote add https://gitlab.com/jhechtf/git-angular.git origin
```


## VSCode Marketplace Access Token

The instructions for creating a PAT that you can use to publish to the VSCode Marketplace is a bit more involved than I would care to cover in-depth here. Please check [this guide](https://code.visualstudio.com/api/working-with-extensions/publishing-extension). Make sure you copy/paste that **TEMPORARILY** somewhere on your computer. I recommend you only use this token for a single VSCode extension so that if, on the off change, this token gets exposed you can view activity from your account and revoke only the necessary token. 

## Gitlab Personal Access Token

You can make a new Personal Access token by logging into Gitlab, and then clicking on your profile picture on the right side. From the dropdown menu click "Settings." 

<img src="https://thepracticaldev.s3.amazonaws.com/i/56gkunb4m4wos84cx126.PNG">

Make sure to name your Personal Access Token something you can distinguish quickly, and I would recommend setting an expiration date within about a month.  **You need to make sure your PAT has write access to your repository, or this will not work.**

<img src="https://thepracticaldev.s3.amazonaws.com/i/08ng5i1wrp53quzwtfsu.PNG">

# .gitlab-ci.yml

If you are unsure the structure of the `.gitlab-ci.yml` file, please [check here](https://docs.gitlab.com/ee/ci/yaml/) for a rough version. I've commented my gitlab-ci file below, but let's talk about what needs to happen exactly in order for us to be able to push the files. The basics of it are

1. make sure git is configured since `standard-version` auto commits its changes to `package.json` and `CHANGELOG.md` Without the two `git configure` lines the job will fail with an error since git won't let an unknown person make a commit.
2. I use yarn, but the equivalent npm command would `npm run release`. This runs `standard-version` from my `package.json`
3. Gitlab CI checks out a commit, not a branch. Therefore the changes committed in the release script will not be saved. We must therefore merge those changes back into the master branch.
4. Merge the new commit into the master branch, and using our PAT push the code up. 

The very basic Gitlab CI file that I made looks like this:
```yaml
# Smaller image, faster to download than the regular node:12 image.
image: node:12-alpine
# Stages list. I will eventually have more stages, but this was a proof-of-concept more than anything
stages:
  - test
  - deploy
# Before any job, add the vsce globally, and install our node_modules.
before_script:
  - yarn global add vsce
  - yarn

Deploy:
  script:
    # update our distro and add git.
    - apk update && apk add git
    # configure our newly-installed git to use our user name and email
    - git config user.email $GITLAB_USER_EMAIL
    - git config user.name $GITLAB_USER_NAME
    # I added a release task which simply runs the standard-version library.
    - yarn release
    # Gitlab checks out the commit, not the branch, putting any committed changes into a detatched-head scenario.
    # this gets the commit SHA and stores it in the $CUR_HEAD variable.
    - CUR_HEAD=$(git rev-parse HEAD)
    # checkout master and merge in our detatched head
    - git checkout master && git merge $CUR_HEAD
    # GITLAB_USER_LOGIN is your gitlab user name, and GL_PAT will be the Gitlab Personal Access Token you created earlier. We are pushing our current HEAD to the master branch
    # on the quoted repo URL
    - git push --follow-tags "https://${GITLAB_USER_LOGIN}:${GL_PAT}@gitlab.com/jhechtf/git-angular.git" HEAD:master
    # finally, publish our extension by passing in our $PAT token.
    - vsce publish -p $PAT

  only:
    - master
  when: manual
```

# Other considerations

I have modified my repository so that only I may push to the master branch (developers can merge branches, but only I may push to it directly).

the `when: manual` makes it so that I must go in and start the Deploy job manually, so that things like the release tags aren't run. I am likely to keep this, as one push could theoretically spawn a million different patch pushes (which I don't want).

# Closing Notes

I am open to hearing about how other people have setup their own Extension publishing pipeline, so if you have a setup please let me know in the comments. I also wish that the Azure Devops dashboard would allow me to restrict the PATs to a specific project, but unfortunately that doesn't seem possible.

Let me know if you have any clarifications you'd like me to make!