DocAsCode con Notion
Tags: notion, documentation, diagram, docascode
Published: 2022-12-10 13:38:08

---
title: DocAsCode con Notion
published: true
date: 2022-12-03 00:00:00 UTC
tags: notion,documentation,diagram, docascode
canonical_url: https://jorge.aguilera.soy/blog/2022/notion-krokio.html
---

"Notion es un software de gestión de proyectos y para tomar notas"

En realidad Notion es un poco más que lo que dice su descripción oficial y se ha vuelto muy popular entre las empresas (y a nivel personal) para mantener la documentación corporativa.

Puedes crear páginas, organizarlas en árbol, tiene muchos emojis (demasiados) disponibles, bases de datos por páginas donde puedes estructurar tus datos como quieras y la página asociada los renderiza, etc.

Sin embargo, lo mismo que pasaba con Confluence, la mayoría de nosotros lo usa a través de la interfaz web y acabamos teniendo mil páginas desactualizadas.

Siguiendo la propuesta de DocAsCode, donde la documentación deberíamos tratarla como el código (versionado, con revisiones y despliegues automatizados) y aprovechando que Notion tiene una API sencillo he desarrollado un pequeño script para mantener mi repositorio git, donde tengo los diagramas de arquitectura del proyecto, actualizado en Notion.

<dl>
<dt>WARNING</dt>
<dd>
<p>Lo he desarrollado en javascript, porque sí, porque puedo</p>
</dd>
</dl>

## Idea

Lo típico en todos los proyectos (con suerte) es crear una página en Notion (antes se hacía en Confluence, y antes en un Wiki, y previamente en una carpeta compartida) donde describimos la arquitectura del proyecto, incluimos algún diagrama generado con alguna herramienta como Draw.io y con mucha suerte incluimos el enlace al código del diagrama en lugar de insertar el PNG generado, (pero esto es con mucha suerte. El 99.9999% de los casos incluimos el PNG porque claro, "la arquitectura no va a cambiar")

<dl>
<dt>INFO</dt>
<dd>
<p>Por otra parte, no niego la facilidad de uso y potencia que ofrece Notion para otro tipo de comunicaciones</p>
</dd>
</dl>

Así la idea es tener una página de Notion "Arquitectura" y un repositorio git donde tendré mis diagramas C4 junto con explicaciones a los mismos. Cada vez que actualize el repo (tal vez mergeando en master, tal vez cada commit) un pipeline ejecutará mi script y reconstruirá la página con la última versión extraída del repo.

Quiero poder tener una estructura de carpetas y sub carpetas en mi repo de tal forma que se replique en Notion colgando de la página "Arquitectura"

## Preparación

En primer lugar, debemos tener un espacio ya creado en Notion (obvio) así como una página "master" de donde colgaremos las generadas. Esta página puede ser una principal o una hija. Así mismo, esta página puede contener el texto explicativo que queramos, pues el script lo que va a hacer es eliminar las hijas y recrearlas (así que no deberías crear hijas, pues el script las va a borrar)

En segundo lugar, necesitamos crear una "conection". Esto lo tiene que hacer el owner del espacio y básicamente es generar una aplicación privada y Notion nos devolverá un token a usar.

Una vez que tienes la "connection" hay que darle permisos para poder acceder a la página "master" (usando los tres puntitos arriba a la derecha)

## Repositorio

Nuestro repositorio es un repositorio normal, donde puedes tener el código o lo que quieras, y simplemente vamos a usar una carpeta del mismo para tener nuestra documentación y diagramas (como código)

<dl>
<dt>WARNING</dt>
<dd>
<p>Esta versión es muy simple y la documentación la va a subir como texto. No negritas, no italica, no titulos,
, quién sabe, tal vez en otra version lo mejore a un parseador de markdown a notion</p>
</dd>
</dl>

Por ejemplo:

```
|_ src
  |_ main
    |_ java
|_ docs
  |_ 1.- Context
    |_ content.txt
    |_ content.puml
  |_ 2.- Applications
    |_ 1.- API
        |_ content.txt
        |_ content.puml
    |_ 2.- Cron
        |_ content.txt
        |_ content.puml
```

En principio el nombre de los ficheros da igual, solo importa que haya uno con una extensión .txt y otro con extension .puml, (pero puedes cambiarlo a tu gusto)

Los ficheros .puml contienen los diagramas en formato texto y casi todos los IDEs tienen un plugin para editarlos y renderizarlos. Por ejemplo Intellij o VsCode los tienen, por lo que puedes ir viendo el diagrama a la vez que los editas

Un ejemplo de un puml

```
@startuml
!include https://raw.githubusercontent.com/adrianvlupu/C4-PlantUML/latest/C4_Context.puml

LAYOUT_WITH_LEGEND()

Person(pbc, "Customer", "A customer of our application.")
System(api, "MyCompany", "Place orders.")
System_Ext(es, "Exchange system", "Exchange systems.")
System_Ext(nf, "Notifications system", "Notifications system")

Rel(pbc, api, "Uses", "REST")

Rel(api, es, "Uses")

Rel(api, nf, "Uses")

Rel(nf, pbc, "Sends notificaions")
@enduml
```

Al ser texto plano es fácil versionarlo, ver las diferencias, crear PR, etc

## Script

Por último añadiremos a nuestro repo el siguiente script (en javascript, toma ya)

```
const fs = require('fs');
const path = require('path');

const { Client } = require("@notionhq/client")

const pako = require("pako")

require('dotenv').config();

// Initializing a client
const notion = new Client({
    auth: process.env.NOTION_TOKEN,
})

async function removeSubPages( id ){
    const page = await notion.blocks.children.list({
        block_id: id
    })
    const blocks = page.results.filter( (c)=> c.type =='child_page' ).map( (c)=> c.id)
    for(b of blocks){
        console.log(`deleting ${b}`)
        const deleted = await notion.blocks.delete({
            block_id: b
        })
    }
}

async function processFolder(directory, parentId, title){
    console.log(`Processing ${directory}`)
    const blocks = []

    const files = fs.readdirSync(directory)
    const fcontent = files.filter( (f)=>path.extname(f) ==".txt")
    const fuml = files.filter( (f)=>path.extname(f)==".puml")

    if( fcontent.length == 1) {
        const content = fs.readFileSync(`${directory}/${fcontent[0]}`, {
            encoding: 'utf8',
            flag: 'r'
        });
        blocks.push({
            "type": "paragraph",
            "paragraph": {
                "rich_text": [{
                    "type": "text",
                    "text": {
                        "content": content,
                        "link": null
                    }
                }]
            }
        })
    }
    if( fuml.length == 1) {
        const diagramSource = fs.readFileSync(`${directory}/${fuml[0]}`, {
            encoding: 'utf8',
            flag: 'r'
        });
        const data = Buffer.from(diagramSource, 'utf8')
        const compressed = pako.deflate(data, {level: 9})
        const result = Buffer.from(compressed)
            .toString('base64')
            .replace(/\+/g, '-').replace(/\//g, '_')

        blocks.push({
            "type": "embed",
            "embed": {
                "url": "https://kroki.io/plantuml/png/" + result
            }
        })
    }
    blocks.push({
        "type": "paragraph",
        "paragraph": {
            "rich_text": [{
                "type": "text",
                "text": {
                    "content": `\n(No modify this page, it's generated automatically)\n`,
                    "link": null
                }
            }]
        }
    })
    const newpage = await notion.pages.create({
        parent: {
            page_id: parentId
        },
        properties: {
            title: [{
                "text": {
                    "content": title
                }
            }]
        },
        children: blocks
    })
    console.log(`Page ${newpage.id} created`)
    const folders = fs.readdirSync(directory);
    for( subdir of folders) {
        if( fs.statSync(`${directory}/${subdir}`).isDirectory() )
            await processFolder( `${directory}/${subdir}`, newpage.id, subdir)
    }
}

;(async () => {
    // clean page
    await removeSubPages(process.env.PAGE);

    const folders = fs.readdirSync('docs')
    const parentId = process.env.PAGE
    for( subdir of folders) {
        if( fs.statSync(`docs/${subdir}`).isDirectory() )
            await processFolder( `docs/${subdir}`, parentId, subdir)
    }
})()
```

Para no incluir el token en el código y así poder versional el script junto con nuestro código, usaremos un fichero`.env` donde pondremos los siguientes valores:

.env

```
NOTION_TOKEN=secret_nN8Zxxxxxxxxxxxx
PAGE=2f188xxxxxxxxxxxxxxxxx
```

Donde PAGE es la parte final de la url de notion de nuestra página master (los numeros despues del titulo)

## Ejecución

Simplemente, necesitamos hacer un `npm install` una vez para que se bajen las dependencias y una vez bajadas ejecutaremos

`node upload2notion.js`

Si todo va bien verás como las páginas hijas de la master desaparecen (si había) y se van creando las nuevas.

Cada página hija contendrá un texto y/o una imagen (en realidad un enlace al servicio de kroki.io)

## Disclaimer

NO tengo ni idea de JavaScript asi que el código seguramente sea un atentado a ojos de gente con más experiencia