# HTML to Gutenberg Converter

[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)

Convert HTML into editable WordPress Gutenberg blocks and publish the generated package to Cloudflare R2 without writing the output to disk.

## What changed

- `html-to-gutenberg` now supports a `job` output mode that uploads generated files to R2 and returns a JSON manifest.
- `fetch-page-assets` can upload downloaded assets directly to R2 and return their metadata.
- Output bundles are zipped in memory and uploaded to R2 as `output.zip`.
- Secrets stay in `.env` and should never be committed.

## Installation

```bash
npm install html-to-gutenberg
```

## Environment

Copy `.env.example` to `.env` and keep the real values private.

```bash
cp .env.example .env
```

Required for R2-backed job output:

- `CLOUDFLARE_R2_ACCOUNT_ID`
- `CLOUDFLARE_R2_BUCKET`
- `CLOUDFLARE_R2_ACCESS_KEY_ID`
- `CLOUDFLARE_R2_SECRET_ACCESS_KEY`
- `CLOUDFLARE_R2_PUBLIC_BASE_URL`

Optional:

- `CLOUDFLARE_API_TOKEN`
- `SNAPAPI_KEY`

## Getting and rotating Cloudflare credentials

1. Open the Cloudflare dashboard.
2. Create or update your R2 access keys for the target bucket.
3. Store the new values in `.env`.
4. If you use a Cloudflare API token for verification or account workflows, create a new token in the API Tokens section and update `.env`.
5. Restart your app or redeploy after updating `.env`.
6. Revoke the old token or key after the new one is live.

To verify a Cloudflare API token without exposing it in code, use an environment variable:

```bash
curl "https://api.cloudflare.com/client/v4/user/tokens/verify" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
```

## Usage

```js
import block from 'html-to-gutenberg';

const result = await block('<div>Hello world</div>', {
  title: 'Marketing Hero',
  slug: 'marketing-hero',
  namespace: 'wp',
  baseUrl: 'https://example.com',
  outputMode: 'job',
  uploadToR2: true,
  jobId: 'conv_123'
});

console.log(result);
```

Example response:

```json
{
  "jobId": "conv_123",
  "status": "completed",
  "output": {
    "files": [
      {
        "id": "file_1",
        "name": "block.js",
        "type": "text/javascript",
        "size": 18234,
        "path": "/generated/conv_123/block.js",
        "url": "https://storage.example.com/generated/conv_123/block.js",
        "kind": "source"
      },
      {
        "id": "file_2",
        "name": "asset.png",
        "type": "image/png",
        "size": 48211,
        "path": "/generated/conv_123/assets/asset.png",
        "url": "https://storage.example.com/generated/conv_123/assets/asset.png",
        "kind": "asset"
      }
    ],
    "bundle": {
      "name": "output.zip",
      "path": "/generated/conv_123/output.zip",
      "url": "https://storage.example.com/generated/conv_123/output.zip",
      "zipUrl": "https://storage.example.com/generated/conv_123/output.zip"
    }
  }
}
```

## Legacy mode

If you still need the previous local-string output for existing tooling or tests, use:

```js
const files = await block('<div>Hello world</div>', {
  title: 'Legacy Block',
  outputPath: process.cwd(),
  writeFiles: false,
  outputMode: 'legacy'
});
```

In `legacy` mode, the function returns the generated file contents instead of the R2 job manifest.

## Options

| Option | Description | Type | Default |
| --- | --- | --- | --- |
| `title` | Human-readable block title shown in the editor. | `string` | `My block` |
| `slug` | Filesystem-safe internal block name. Defaults to a slugified title. | `string` | slugified `title` |
| `baseUrl` | Base URL used to resolve relative asset paths in HTML and CSS. | `string \| null` | `null` |
| `namespace` | Gutenberg block namespace. | `string` | `wp` |
| `category` | Gutenberg block category. | `string` | `common` |
| `registerCategoryIfMissing` | Adds a custom editor category before block registration when needed. | `boolean` | `false` |
| `outputPath` | Absolute directory used for local legacy output. In `job` mode it is only a logical working base. | `string` | current directory |
| `writeFiles` | Writes local files in `legacy` mode. When `false`, returns generated files in memory. | `boolean` | `false` in the streamlined API |
| `generatePreviewImage` | Generates and uploads `preview.jpeg` using SnapAPI. | `boolean` | `false` |
| `jsFiles` | Remote JS dependencies to enqueue. | `string[]` | `[]` |
| `cssFiles` | Remote CSS dependencies to enqueue. | `string[]` | `[]` |
| `outputMode` | Advanced option. `job` uploads to R2 and returns JSON. `legacy` returns raw file contents. | `'job' \| 'legacy'` | `job`, unless local-output options imply `legacy` |
| `uploadToR2` | Advanced option to force or disable R2 uploads. | `boolean` | `true` in `job` mode |
| `jobId` | Advanced stable conversion identifier. | `string` | autogenerated |

Legacy aliases still work for backwards compatibility:

- `name` -> `title`
- `prefix` -> `namespace`
- `source` -> `baseUrl`
- `basePath` -> `outputPath`
- `shouldSaveFiles` -> `writeFiles`
- `generateIconPreview` -> `generatePreviewImage`

## Notes

- Generated output is zipped in memory before upload.
- R2 uploads use the values from `.env`.
- Do not hardcode real tokens or keys in source code, docs, or tests.

## Running tests

```bash
npm install
npm test
```

## License

[MIT](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)
