A poor attempt at making a static blogging engine in Typescript
Made strings dynamic, replaced marked with commonmark, fixed markdown parsing - html appending bug, added skip to main content button, changed page structure for accessbility reasons
First public commit


browse  log 



You can also use your local clone with git send-email.


A poor attempt at making a static blogging engine in Typescript.


This is garbage software, the README is unreadable, and I will remake the whole README at another time.

Also, something something alpha software something something will change without warning something.


Probably only works on Linux.

> node -v
> npm ls
engrafi@0.0.1 /home/denise/blog
├── @types/jsdom@21.1.6
├── @types/marked@6.0.0
├── @types/node@20.10.4
├── country-code-emoji@2.3.0
├── esbuild@0.19.9
├── esthetic@0.6.4-beta.1
├── jsdom@23.0.1
├── marked@11.0.1
├── postject@1.0.0-alpha.6
└── typescript@5.3.3

#How things are split

Everything is either an article, a section, a header or a footer.

Articles are things written by authors, with dates, and titles.

An article has a Markdown page to go along with it necessarily, with some special syntax.

Sections are things that were written. We don't care about the dates nor the authors.

A section can be a link to somewhere that will show up on the section list under the header. It does not necessarily need a Markdown page to go along with it.

The header and the footer is defined in the main configuration and will appear on every rendered page.

You can set a preferred section, which will act as a home page / main page. In this case, the latest article will be prepended to the section content (unless you set latest to false).

#How to use

#How to compile

Run npm run build or npx tsc.

A ./dist folder should appear.

#How to configure

#General configuration

Create a general configuration in ./dist.

This will serve as the place on which you will define:

  • the blog name;

  • the blog description;

  • the blog domain;

  • the default general language;

  • the relative paths for the sections and the articles;

  • the default section title, which is case sensitive (home page);

  • the blog footer;

  • whether or not you want rss;

  • whether or not you want the archives;

RSS and archives appear as sections on the section bar.

For example:

    "header": {
        "title": "~denise",
        "description": "Adminsys, queeritude et plus"
    "sectionsPath": "./sections",
    "postsPath": "./posts",
    "language": "fr",
    "domain": "https://blog.simplydenise.eu",
    "defaultSection": "page principale",
    "footer": "<p>This blog is generated by <a href='https://git.sr.ht/~denisebitca/engrafi'>~denisebitca/engrafi</a>, AGPLv3. All content is <a href='https://creativecommons.org/licenses/by-sa/4.0/'>CC BY-SA 4.0</a> except if specified otherwise</p>",
    "rss": true,
    "archives": true,
    "latest": true

The section which you define as default will have the latest article prepended to it, unless there are no articles or you set "latest" to false.

#Section configuration

A section resides in ./sections by default and looks like this.

    "url": "/index.html",
    "title": "page principale",
    "contentPath": "markdown/main.md",
    "hide": false

the URL is relative to the domain, the title is case sensitive (if you want to make the section a default section), and the content path is relative to the sections folder.

The markdown file is a regular Markdown file that is valid according to CommonMark specs.

For example:

## contacts

- fediverse: <a rel="me" href="https://potate.space/@denise">@denise@potate.space</a>
- matrix: @denise:envs.net

All sections are added to the links under the header by default. You can hide a section from the header links by setting "hide" to true.

contentPath can be left empty - the URL will be interpreted as absolute.

#Article configuration

Articles come in two files: the article metadata which points to the article content.

Both these files reside within ./posts.

The article metadata looks like this:

    "title": "Et c'est reparti pour encore un changement !",
    "date": "December 12 2023",
    "author": "Denise",
    "language": "fr",
    "description": "Nouveau logiciel, nouvelle année ?",
    "contentPath": "markdown/nouveau_logiciel.md"

And the article content can look like this:

Le blog a changé légèrement. Pour l'instant, je ne dévoile pas le code qui a méné à ce changement, mais je peux vous donner des détails :

- Il n'y presque plus de templates en HTML pur

- Tout est du markdown presque

- C'est conçu pour être utilisé par plus de gens que moi

:::::en- I can set the lang attribute per paragraph

Paragraphs can be set to specific languages by prefixing :::::[ISO 639-1 language code].

Currently, the supported languages are "en" | "fr" | "pt" | "de" | "ro" | "es".

#How to build

#If you want the binary
> npm run build
#If you want the bundled code
> npm run buildCode

#How to run

Assuming you have the following directory tree:

├── posts
├── sections

and that in the directory there is a config.json and a strings.json,

> engrafi config.json strings.json

All the content will be generated within ./public. No JS included, it's all HTML and one XML file.

By default, the program is quiet (except for errors). You can set debug output with environment variable DEBUG (0 for no, 1 for yes) and set up a special benchmark with environment variable BENCHMARK (0 for no, 1 for yes).

#How to deploy

Here's an idea:

  • get the latest binary release
  • copy the binary into the working directory
  • have a separate repository with the following tree:
    (your images)
        (your article markdown files)
    (your article metadata files)
        (your section markdown files)
    (your section metadata files)
  • clone the repository into working directory
  • ./engrafi config.json strings.json
  • cp -r images public
  • cp style.css public
  • tar compress the public folder and push it to your preferred page


You must run the software with a "strings.json". Some strings (not marked as debug or fails) will appear on the website.


AGPLv3. Check "LICENSE" for more details.