Como publicar seu primeiro package Typescript e automatizar com Github Actions
Tags: node, github, tutorial, npm
Published: 2021-06-15 01:30:25

## Introdução

Nesse artigo vou abordar a criação e publicação de um package escrito em Typescript para o NPM. A minha principal motivação é escrever algo que seja simples mas não deixe de levar em consideração fatores importantes, como versionamento, atualização, testes e automatização. O que será abordado:

- Criar um package em TS
- Boas práticas de teste pre release
- Publicar um package público no npm
- Gerenciar atualizações
- Noções de versionamento
- Automatizar a publicação com github actions

## Pré-requisitos
- Criar uma conta no [Github](https://github.com/join)
- Criar uma conta no [NPM](https://www.npmjs.com/signup)

## Configurando o ambiente para o package

### Criando o repositório
Nosso package vai se chamar math-ops e será responsável por fazer operações matemáticas básicas. Sendo assim, vamos criar um novo repositório com o nome escolhido:

![Screenshot from 2021-06-13 19-07-03](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3euz259a1uu7ynxddm4.png) 

### Inicializando o repositório
Vamos criar um novo diretório e acessá-lo:

```sh
mkdir math-ops && cd math-ops
```

Em seguida vamos configurar o repostório git e enviar o primeiro commit.

```sh
echo "# math-ops" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:thiagomr/math-ops.git
git push -u origin main
```

## Configurando o package

### Inicializando as configurações do NPM
```sh
npm init
```

Como resultado deste comando teremos a seguinte saída: 

```json
//package.json

{
  "name": "@thiagomr/math-ops",
  "version": "0.1.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/thiagomr/math-ops.git"
  },
  "author": "Thiago Moraes",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/thiagomr/math-ops/issues"
  },
  "homepage": "https://github.com/thiagomr/math-ops#readme"
}
```

Nessa etapa é importante notar que definimos o nome do package (você deve mudar de acordo com o seu nome de usuário ou nome do package que você desejar), que será utilizado para instalar o mesmo. Também foi definida a versão 0.1.0, seguindo os padrões de **Semantic Version**. Este é um padrão que nos permite incrementar a versão de acordo com o tipo da atualização. Você pode se aprofundar sobre o assunto [aqui](https://docs.npmjs.com/about-semantic-versioning). Vamos considerar que estamos criando uma release não oficial, ou seja, anterior à versão 1.0.0. É interessante também perceber que como ja temos o git configurado nesse diretório, o npm automaticamente sugere o preenchimento das configurações de url e homepage do mesmo.


### Instalando as dependências

Em seguida vamos instalar as dependências que utilizaremos no projeto, que são basicamente o Typescript e o Jest (ferramenta que utilizaremos para escrever testes para nossas funcionalidades):

```sh
npm install typescript jest @types/jest ts-jest --save -D
```

Vamos adicionar o arquivo `tsconfig.json` com as configurações que usaremos para compilar o projeto:

```json
//tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./lib",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "**/*.spec.ts"
  ]
}

```

Em seguida, iremos adicionar alguns scripts para fazer o build do projeto e uma configuração bem simples para o `Jest`. Nosso arquivo com as alterações ficará da seguinte forma:

```json
//package.json

{
  "name": "@thiagomr/math-ops",
  "version": "0.1.0",
  "description": "A package to make basic math operations",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "scripts": {
    "clean": "rimraf lib",
    "build": "npm run clean && tsc",
    "test": "jest",
    "prepublish": "npm run test && npm run build"
  },
  "author": "Thiago Moraes",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/thiagomr/math-ops.git"
  },
  "bugs": {
    "url": "https://github.com/thiagomr/math-ops/issues"
  },
  "homepage": "https://github.com/thiagomr/math-ops#readme",
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": [
      "/node_modules/",
      "lib"
    ]
  },
  "files": [
    "lib/**/*"
  ],
  "devDependencies": {
    "@types/jest": "^26.0.23",
    "jest": "^27.0.4",
    "ts-jest": "^27.0.3",
    "typescript": "^4.3.2"
  }
}


```

Algumas explicações mais detalhadas:

- `"main": "lib/index.js",` Será o arquivo que vai expor a API pública do nosso pacote.
- `"types": "lib/index.d.ts",` Indica a declaração de tipos do TS, gerado automáticamente de acordo com as nossas configurações de compilação feitas anteriormente.
- `"jest": {...}` Configuração para que o Jest funcione usando TS e indicação de arquivos a serem ignorados.
- `"files": {...}` Arquivos que desejamos incluir no nosso pacote.
- `"prepublish"` Executa um script antes de publicar o package. Neste caso vamos rodar os testes unitários.
- `"build"` Faz a compilação do projeto. De maneira bem resumida, seria o processo de typechecking e transpilação do código TS para JS.

Para finalizar essa etapa, vamos criar um arquivo `.gitignore`:

```ts
//.gitignore

node_modules
lib
```

E então vamos enviar toda a configuração para o repositório:

```sh
git add .
git commit -m "add dependecies"
git push
```

## Criando a primeira funcionalidade

Agora vamos adicionar o arquivo com a primeira funcionalidade, que irá retornar a soma entre dois números:

```ts
// src/sum.ts

const sum = (firstNumber: number, secondNumber: number): number => {
    return firstNumber + secondNumber;
}

export {
    sum
}
```

Vamos criar também um arquivo de entrada para exportar a API  pública do nosso package:

```ts
// src/index.ts

export * from './sum';
```

## Testando o package

Vamos escrever nosso primeiro teste untário para a funcionalidade de `sum`:

```ts
// src/sum.spec.ts

import { sum } from './sum';

test('should return a sum of two numbers', () => {
    const result = sum(3, 2);
    expect(result).toEqual(5);
});

```

Rodando os testes:

```sh
npm test
```

Agora que já temos nosso primeiro teste unitário, vamos testar o uso real do package. Nós vamos utilizar o comando `npm link`. Isso fará com que o npm crie uma referência local para esse projeto, podendo ser utilizado de forma direta em outros projetos. É uma forma de testar seu package no ambiente de desenvolvimento sem precisar publicá-lo várias vezes.

O primeiro comando será executado no diretório do package e o segundo em um novo diretório para testes:

```sh
# math-ops
npm link

# testdir
npm link @thiagomr/math-ops
```

No diretório de testes, vamos criar um arquivo que importa e utiliza a funcionalidade `sum`:

```js
// testdir/index.ts

const { sum } = require('@thiagomr/math-ops');
const result = sum(2, 3);

// sum is 5
console.log(`sum is ${result}`);
```

Para finalizar, vamos atualizar nosso repositório com as novas mudanças:

```sh
git add .
git commit -m "add sum feature"
git push
```

## Publicando no NPM registry

Agora que já temos nosso pacote funcional e testado localmente, vamos para a fase de publicação. Vamos adicionar um arquivo .npmignore que vai excluir os arquivos desnecessários, enviando apenas o essencial e diminuindo o tamanho do package:

```js
//.npmignore

src
node_modules
```
Iremos fazer o login no npm com a conta desejada:

```sh
npm login
```

Por padrão o versionamento do NPM utiliza o sistema de tags do GIT para indicar a publicação de novas versões. É importante manter o versionamento do NPM e GIT sincronizados, mas vale ressaltar que não há nenhuma regra que faça uma ligação entre as duas coisas. Existem alguns packages que facilitam o gerenciamento de ambos, mas como a intenção aqui é mostrar a funcionalidade básica e o mais pura possível, não utilizaremos nenhuma ferramenta adicional.

Vamos atualizar o repositório com a tag de versão incial e em seguida publicar no npm:

```sh
git tag v0.1.0
git push --tags
npm publish --access=public
```

Agora já temos nosso pacote publicado e disponível para instalação (O meu está em uma versão um pouco a frente pois fiz alguns testes para o artigo):

![Screenshot from 2021-06-14 20-51-47](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrjr9scufv5oc882s0d2.png) 

## Adicionando novas funcionalidades

Nessa etapa vamos adicionar uma nova feature. Isso vai reforçar os conceitos aplicados, trazendo familiaridade com o processo. Vamos adicionar uma funcionalidade que retorna o resultado da multiplicação entre dois números `index.ts`:

```ts
// src/times.ts

const times = (firstNumber: number, secondNumber: number): number => {
    return firstNumber * secondNumber;
}

export {
    times
}
```

```ts
//src/index.ts

export * from './sum'
export * from './times'
```

Seguindo o processo anterior, vamos escrever um teste unitário para a nova funcionalidade:

```ts
//src/times.spec.ts

import { times } from './times';

test('should return the multiplication of two numbers', () => {
    const result = times(3, 3);
    expect(result).toEqual(9);
});

```

Vamos atualizar o repositório e publicar a nova versão:

```sh
git add .
git commit -m "add times feature"
npm version minor
git push --tags
npm publish --access=public
```

Agora você poderá o ver o package atualizado no NPM registry.

## Automatizando a publicação com Github Actions

Agora que já vimos como fazer todo o processo manualmente, vamos automatizar a publicação utilizando Github Actions.
Vamos criar um token no NPM [aqui](https://docs.npmjs.com/creating-and-viewing-access-tokens), para que seja possível fazer a publicação através do Github. Vamos inserir nosso token como uma variável de ambiente com o nome `NPM_TOKEN`, acessando o repostório e em seguida selecionado as opçoes `Settings > Secrets > New Repository Secret`.:

![Screenshot from 2021-06-14 20-58-56](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5g4gj966vmsxaywlan3s.png)
 

Em seguida vamos criar o arquivo de configuração do pipeline para que seja executado sempre que ouver uma alteração na branch main e no arquivo `package.json`:

```yml
# .github/workflows/publish.yml
on:
  push:
    branches: [ main ]
    paths:
      - 'package.json'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: 14
      - run: npm install
      - uses: JS-DevTools/npm-publish@v1
        with:
          token: ${{ secrets.NPM_TOKEN }}
```

Para testar o pipeline, nós criaremos a última funcionalidade ao nosso package, capaz de subtrair dois números:

```ts
const subtract = (firstNumber: number, secondNumber: number): number => {
    return firstNumber - secondNumber;
}

export {
    subtract
}
```

Vamos novamente atualizar a entrada do package:

```ts
//src/index.ts

export * from './sum'
export * from './times'
export * from './subtract'
```

Assim como nos passos anteriores, vamos criar um teste unitário para o mesmo:

```ts
// src/subtract.spec.ts

import { subtract } from './subtract';

test('should return the subtraction of two numbers', () => {
    const result = subtract(4, 4);
    expect(result).toEqual(0);
});

```

Agora vamos enviar nossas alterações para o repositório e atualizar a versão do nosso package:

```sh
git add .
git commit -m "add subtract feature"
npm version minor
git push --tags
```

Se a nossa configuração estiver correta e tudo ocorrer bem, podemos verificar o pipeline no Github executado com sucesso e a nova versão publicada no NPM:

![Screenshot from 2021-06-14 21-04-51](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/moumenbtxhpe7lbvhfhy.png)

![Screenshot from 2021-06-14 21-05-12](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28774hsw4b3371yzdgrg.png)

Agora vamos adicionar um exemplo de uso do package que também estará no `README.md` do repositório:

```sh
// Install
npm install @thiagomr/math-ops
```

```ts
import { sum } from '@thiagomr/math-ops';

//6
console.log(sum(4, 2));

```

## Conclusão

Isso é tudo pessoal. Aqui está o [link do repistório](https://github.com/thiagomr/math-ops) com todo o código utilizado. Espero que tenha ficado claro e que de alguma forma possa ajudar vocês a publicarem seus próprios packages. Gostaria de ouvir feedbacks, opiniões, sugestões e o que mais desejarem. Me sigam no [Twitter](https://twitter.com/thiagodmor) para mais novidades. Grande abraço e até a próxima!