npm package: @infopack/pdf-creator-app
Description: A service and library for turning infopack IRNs into PDFs or raw HTML. You can run the bundled Express server or import the library directly.
Author: Johan Asplund
Latest version: 0.2.1

# PDF Creator App

A service and library for turning infopack IRNs into PDFs or raw HTML. You can run the bundled Express server or import the library directly.

## HTTP usage

1. Provide an IRN as `GET /single?irn=<IRN>`.
2. Infopack files must be `.partial.html`.

## Library usage

Import and use the expander and PDF helpers without starting the server:

```js
import { InfopackCollectionExpander, createPdfFromCollection } from 'pdf-creator-app'

const expander = new InfopackCollectionExpander(collectionDefinition)
await expander.expand()
const expanded = expander.get()

// Render HTML (set raw:false to get a PDF buffer)
const html = await createPdfFromCollection(collectionDefinition, {
  raw: true,
  templateOverrides: {
    paths: { collection: './my-templates/collection.template.html' }
  }
})
```

Templates can be overridden by changing file paths (`templateOverrides.paths`) or by passing template strings/functions directly on `templateOverrides`.

### Templates and their roles

- `standard`: Wrapper for a single infopack file (`createPdfFromIrn`). Should include room for content and QR code (`{{qrCode}}`).
- `collection`: Outer HTML for a collection; receives `title`, `name`, `version`, `toc`, `content`.
- `toc`: Builds the table of contents for a collection (gets `files` from the expander).
- `file`: Renders each file in a collection (receives the full content object from the expander).
- `header` / `footer`: Raw HTML strings printed on every PDF page. These go straight to Puppeteer’s `headerTemplate`/`footerTemplate`, so follow Puppeteer’s rules: use `<span class="pageNumber"></span>` and `<span class="totalPages"></span>` for page numbers, and keep the total height within Puppeteer’s header/footer limits. They are not compiled with Handlebars unless you do it yourself before passing in the string.

#### Data available inside each template

- `standard` (single IRN): gets everything from `infopackUrlResolver` plus `qrCode`. Typical fields: `content` (HTML), `meta` (from `.meta.json`), `valid`, `notValid`, `messages`, `url`, `fetchedDate`, `classes` (if set), and `qrCode` (data URI for registry link).
- `collection`: receives `{ title, name, version, toc, content }` where `toc` and `content` are rendered strings produced by the templates below.
- `toc`: receives `{ files }` where `files` is the array from the collection expander. Each file item can include: `kind` (`"infopack-file"` or `"html-file"`), `collectionPath`, `packagePath`, `filePath`, `irn` (if an infopack file), `url`, `fetchedDate`, `content` (HTML if `returnContent` is true), `meta`, `valid`, `notValid`, `messages`, and optional `classes`.
- `file`: receives the same file item objects as `toc`, so you can access any of the fields above per file.
- `header` / `footer`: no data injection; treated as literal HTML passed to Puppeteer. If you need Handlebars data here, compile it yourself before passing the string.

#### Images and assets (e.g. logos)

When you add images in templates (including header/footer), use either:
- Absolute URLs reachable from the rendering environment (e.g. `https://.../logo.png`), or
- Inline data URIs (`<img src="data:image/png;base64,..." />`) if you need to bundle the asset.

Relative paths will not resolve from `page.setContent`; prefer absolute URLs or data URIs for reliable rendering.

To inline local relative images in templates (e.g. logo in header/footer), set `assetsBasePath` so the library can convert `<img src="./...">` into data URIs in templates. Content HTML is not inlined automatically:

```js
await createPdfFromCollection(collectionDefinition, {
  assetsBasePath: './assets', // only files under this dir will be read
  templateOverrides: { /* ... */ }
})
```

Supported formats for inlining: png, jpg, jpeg, gif, svg, webp. Files outside `assetsBasePath` are ignored; missing files are left unchanged.

### Caching

- Enabled by default: fetched infopack content and metadata are cached under `.cache/infopack` (git-ignored).
- TTL: 24h for URLs/IRNs containing `latest`; 90 days for all other versions.
- Disable or customize via `cache` options:

```js
// Disable cache
await createPdfFromIrn('ns:name:version:path', { cache: { enabled: false } })

// Custom cache dir/TTL via expander in collection rendering
await createPdfFromCollection(collectionDefinition, {
  expanderOptions: {
    cache: { dir: './tmp/cache', ttlMs: 7 * 24 * 60 * 60 * 1000 } // 7 days for non-latest
  }
})

// Render cache (final HTML) can also be cached on disk (default .cache/render)
await createPdfFromCollection(collectionDefinition, {
  renderCache: { enabled: true, dir: './.cache/render' }
})
```

#### Cache keys

This library uses a cache key concept to map each fetched resource to a single file on disk:

- For infopack fetches, the cache key is the full URL. The cache file name is `sha1(url).json`.
- For render caching, the cache key is a string built from the collection content and template overrides. That string is then hashed the same way.
- The hash keeps filenames short and filesystem-safe; no plain URLs are used as filenames.
- Each cache file stores a small JSON object with metadata (version, fetchedAt, ttlMs, type) plus the body payload.

Default paths live in `lib/templates.js` (`defaultTemplatePaths`). You can repoint files or supply inline templates:

```js
import { createPdfFromCollection, createPdfFromIrn } from 'pdf-creator-app'

// Point to custom files
await createPdfFromCollection(collectionDefinition, {
  templateOverrides: {
    paths: {
      collection: './my-templates/collection.template.html',
      file: './my-templates/file.template.html'
    }
  }
})

// Inline string/function (compiled with Handlebars); header/footer are raw strings
await createPdfFromCollection(collectionDefinition, {
  raw: true,
  templateOverrides: {
    collection: '<html><body>{{{toc}}}{{{content}}}</body></html>',
    header: '<span>My header</span>',
    footer: '<span>My footer</span>'
  }
})

// Single IRN with custom template
await createPdfFromIrn('namespace:name:version:path', {
  templateOverrides: {
    standard: '<html><body>{{{content}}} {{qrCode}}</body></html>'
  }
})
```

## Build

```
docker build . -t registry.gitlab.com/infopack/pdf-creator-app:latest
docker push registry.gitlab.com/infopack/pdf-creator-app:latest
```

## Installation

### Docker

The app is packaged as a Docker image; releases are available [here](https://gitlab.com/infopack/pdf-creator-app/container_registry).

```
docker run --rm --name pdf-creator-app -p 9000:9000 registry.gitlab.com/infopack/pdf-creator-app:<latest version>
```

## Release

GitLab builds and publishes the image on every tag on `main`. Latest images are in the container registry linked above.

### Helm

To update the Helm chart, ensure `helm` is installed and configured, then:

```bash
cd helm/pdf-creator-app
helm --namespace infopack-pdf-creator-app upgrade pdf-creator-app .
```