html-to-gutenberg 4.2.9 → 4.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,68 +1,173 @@
1
- // Utility functions exported for testing
2
- export function hasTailwindCdnSource(jsFiles: string[]): boolean {
3
- const tailwindCdnRegex = /https:\/\/(cdn\.tailwindcss\.com(\?[^"'\s]*)?|cdn\.jsdelivr\.net\/npm\/@tailwindcss\/browser@4(\.\d+){0,2})/;
4
- return jsFiles.some((url: string) => tailwindCdnRegex.test(url));
5
- }
6
-
7
- export function replaceSourceUrlVars(str: string, source: any): string {
8
- if (!source) return str;
9
- const escapedSource = String(source).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
- const pattern = new RegExp(`var.url+'${escapedSource}([^']*)'`, 'g');
11
- return str.replace(pattern, (match: string, path: string) => `\${vars.url}${path}`);
12
- }
13
-
14
- // ...existing code...
15
-
16
- // Export the main block function as default
17
- // (Moved block implementation from index.js below)
18
-
19
- import fetch from 'node-fetch';
20
- import presetReact from '@babel/preset-react';
21
- import * as babel from '@babel/core';
22
- import * as cheerio from 'cheerio';
23
- import scopeCss from 'css-scoping';
24
- import extractAssets from 'fetch-page-assets';
25
- import fs from 'fs';
26
- import dotenv from 'dotenv';
27
- import pkg from './package.json';
28
- import convert from 'node-html-to-jsx';
29
- import path from 'path';
30
-
31
- import {
32
- imports,
33
- images,
34
- characters
35
- } from './globals.js';
36
-
37
- const { version } = pkg;
38
-
39
- const block = async (
40
- htmlContent: string,
41
- options: {
42
- name?: string;
43
- prefix?: string;
44
- category?: string;
45
- basePath?: string;
46
- shouldSaveFiles?: boolean;
47
- generateIconPreview?: boolean;
48
- jsFiles?: string[];
49
- cssFiles?: string[];
50
- source?: string | null;
51
- } = {
52
- name: 'My block',
53
- prefix: 'wp',
54
- category: 'common',
55
- basePath: process.cwd(),
56
- shouldSaveFiles: true,
57
- generateIconPreview: false,
58
- jsFiles: [],
59
- cssFiles: [],
60
- source: null,
61
- }
1
+ export type GeneratedFiles = Record<string, string>;
2
+
3
+ export type JobOutputFile = {
4
+ id: string;
5
+ name: string;
6
+ type: string;
7
+ size: number;
8
+ path: string;
9
+ url: string;
10
+ kind: 'source' | 'asset' | 'bundle';
11
+ };
12
+
13
+ export type JobBundle = {
14
+ id: string;
15
+ name: string;
16
+ type: string;
17
+ size: number;
18
+ path: string;
19
+ url: string;
20
+ zipUrl: string;
21
+ };
22
+
23
+ export type JobManifest = {
24
+ jobId: string;
25
+ status: 'completed';
26
+ output: {
27
+ files: JobOutputFile[];
28
+ bundle: JobBundle;
29
+ };
30
+ };
31
+
32
+ export type NormalizedBlockOptions = {
33
+ title: string;
34
+ name: string;
35
+ slug: string;
36
+ namespace: string;
37
+ prefix: string;
38
+ baseUrl: string | null;
39
+ source: string | null;
40
+ category: string;
41
+ registerCategoryIfMissing: boolean;
42
+ outputPath: string;
43
+ basePath: string;
44
+ writeFiles: boolean;
45
+ shouldSaveFiles: boolean;
46
+ generatePreviewImage: boolean;
47
+ generateIconPreview: boolean;
48
+ jsFiles: string[];
49
+ cssFiles: string[];
50
+ outputMode: 'job' | 'legacy';
51
+ uploadToR2: boolean;
52
+ jobId?: string;
53
+ };
54
+
55
+ export type BlockOptions = {
56
+ title?: string;
57
+ slug?: string;
58
+ baseUrl?: string | null;
59
+ namespace?: string;
60
+ category?: string;
61
+ registerCategoryIfMissing?: boolean;
62
+ outputPath?: string;
63
+ writeFiles?: boolean;
64
+ generatePreviewImage?: boolean;
65
+ jsFiles?: string[];
66
+ cssFiles?: string[];
67
+ outputMode?: 'job' | 'legacy';
68
+ uploadToR2?: boolean;
69
+ jobId?: string;
70
+ name?: string;
71
+ prefix?: string;
72
+ source?: string | null;
73
+ basePath?: string;
74
+ shouldSaveFiles?: boolean;
75
+ generateIconPreview?: boolean;
76
+ };
77
+
78
+ export declare const createProfiler: (enabled: boolean) => {
79
+ start(label: string): void;
80
+ end(label: string): void;
81
+ };
82
+
83
+ export declare const findSelfClosingJsxEnd: (
84
+ content: string,
85
+ startIndex: number
86
+ ) => number;
87
+
88
+ export declare const replaceSelfClosingJsxComponent: (
89
+ content: string,
90
+ componentName: string,
91
+ replacer: (componentSource: string) => string
92
+ ) => string;
93
+
94
+ export declare const getMediaUploadSaveTemplate: (
95
+ image?: {
96
+ randomUrlVariable: string;
97
+ randomAltVariable: string;
98
+ imgClass?: string;
99
+ }
100
+ ) => string;
101
+
102
+ export declare const replaceMediaUploadComponents: (
103
+ content: string,
104
+ imageRegistry: Array<{
105
+ randomUrlVariable: string;
106
+ randomAltVariable: string;
107
+ imgClass?: string;
108
+ }>
109
+ ) => string;
110
+
111
+ export declare const replaceRichTextComponents: (content: string) => string;
112
+
113
+ export declare const buildAssetExtractionOptions: (
114
+ basePath: string,
115
+ options?: {
116
+ uploadToR2?: boolean;
117
+ returnDetails?: boolean;
118
+ jobId?: string;
119
+ r2Prefix?: string;
120
+ }
62
121
  ) => {
63
- // ...existing block implementation from index.js...
122
+ basePath: string;
123
+ saveFile: false;
124
+ verbose: false;
125
+ maxRetryAttempts: 1;
126
+ retryDelay: 0;
127
+ concurrency: 8;
128
+ uploadToR2: boolean;
129
+ returnDetails: boolean;
130
+ jobId?: string;
131
+ r2Prefix?: string;
64
132
  };
65
133
 
66
- export default block;
134
+ export declare const slugifyBlockValue: (value?: string) => string;
135
+ export declare const formatCategoryLabel: (category?: string) => string;
136
+ export declare const normalizeBlockOptions: (
137
+ options?: BlockOptions
138
+ ) => NormalizedBlockOptions;
139
+
140
+ export declare const replaceRelativeUrls: (
141
+ html: string,
142
+ replacer: (url: string) => string
143
+ ) => string;
144
+
145
+ export declare const replaceRelativeUrlsInCss: (
146
+ css: string,
147
+ replacer: (url: string) => string
148
+ ) => string;
67
149
 
150
+ export declare const replaceRelativeUrlsInHtml: (
151
+ html: string,
152
+ baseUrl: string
153
+ ) => string;
68
154
 
155
+ export declare const replaceRelativeUrlsInCssWithBase: (
156
+ css: string,
157
+ cssFileUrl: string
158
+ ) => string;
159
+
160
+ export declare const unwrapBody: (code: string) => string;
161
+
162
+ export declare const transformBlockFile: (
163
+ blockCode: string
164
+ ) => { code?: string } | string;
165
+
166
+ export declare const getSnapApiUrl: () => string;
167
+
168
+ declare const block: (
169
+ htmlContent: string,
170
+ options?: BlockOptions
171
+ ) => Promise<GeneratedFiles | JobManifest>;
172
+
173
+ export default block;
package/package.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "dependencies": {
3
+ "@aws-sdk/client-s3": "^3.888.0",
3
4
  "@babel/core": "^7.29.0",
4
5
  "@babel/preset-react": "^7.28.5",
5
6
  "@svgr/core": "^8.1.0",
@@ -8,6 +9,8 @@
8
9
  "dotenv": "^17.3.1",
9
10
  "fetch-page-assets": "^1.2.7",
10
11
  "fs": "^0.0.1-security",
12
+ "jszip": "^3.10.1",
13
+ "mime-types": "^3.0.1",
11
14
  "node-fetch": "^3.3.2",
12
15
  "node-html-to-jsx": "^1.4.4",
13
16
  "path": "^0.12.7"
@@ -25,6 +28,7 @@
25
28
  "@typescript-eslint/eslint-plugin": "^8.56.1",
26
29
  "@typescript-eslint/parser": "^8.56.1",
27
30
  "chai": "^6.2.2",
31
+ "c8": "^11.0.0",
28
32
  "coveralls": "^3.1.1",
29
33
  "eslint": "^10.0.2",
30
34
  "mocha": "^11.3.0",
@@ -34,20 +38,20 @@
34
38
  "ts-node": "^10.9.2",
35
39
  "typescript-eslint": "^8.56.1"
36
40
  },
37
- "types": "index.ts",
41
+ "types": "index.d.ts",
38
42
  "name": "html-to-gutenberg",
39
- "version": "4.2.9",
43
+ "version": "4.2.10",
40
44
  "description": "Transform any valid HTML string into fully editable WP Gutenberg blocks in seconds rather than hours.",
41
45
  "main": "index.js",
42
46
  "directories": {
43
47
  "test": "test"
44
48
  },
45
49
  "scripts": {
46
- "postinstall": "mv ./node_modules/fetch-page-assets/index.ts ./node_modules/fetch-page-assets/index.ts.bak || true",
47
- "test": "mocha -r ts-node/register index.test.ts",
50
+ "postinstall": "mv ./node_modules/fetch-page-assets/index.ts ./node_modules/fetch-page-assets/index.ts.bak || true && node ./scripts/patch-fetch-page-assets.mjs",
51
+ "test": "mocha -r ts-node/register/transpile-only index.test.ts fetch-page-assets.test.ts",
48
52
  "build": "tsc",
49
- "coverage": "nyc mocha -r ts-node/register index.test.ts",
50
- "coveralls": "nyc mocha -r ts-node/register index.test.ts && nyc report --reporter=lcov && cat coverage/lcov.info | coveralls"
53
+ "coverage": "c8 mocha -r ts-node/register/transpile-only index.test.ts fetch-page-assets.test.ts",
54
+ "coveralls": "c8 mocha -r ts-node/register/transpile-only index.test.ts fetch-page-assets.test.ts && c8 report --reporter=lcov && cat coverage/lcov.info | coveralls"
51
55
  },
52
56
  "repository": {
53
57
  "type": "git",
@@ -72,28 +76,25 @@
72
76
  "url": "https://github.com/DiogoAngelim/html-to-gutenberg/issues"
73
77
  },
74
78
  "homepage": "https://www.html-to-gutenberg.io",
75
- "nyc": {
79
+ "c8": {
76
80
  "reporter": [
77
- "lcov",
78
- "text"
79
- ],
80
- "exclude": [
81
- "coverage"
81
+ "text",
82
+ "lcov"
82
83
  ],
83
84
  "include": [
84
- "src/*.ts",
85
- "src/**/*.ts"
85
+ "index.js",
86
+ "utils.ts",
87
+ "globals.js",
88
+ "vendor/fetch-page-assets/index.js"
86
89
  ],
87
- "extension": [
88
- ".ts",
89
- ".js"
90
+ "exclude": [
91
+ "coverage",
92
+ "dist",
93
+ "node_modules",
94
+ "scripts",
95
+ "**/*.test.ts"
90
96
  ],
91
- "all": true,
92
- "sourceMap": true,
93
- "instrument": true,
94
- "require": [
95
- "ts-node/register"
96
- ]
97
+ "all": true
97
98
  },
98
99
  "jest": {
99
100
  "preset": "ts-jest/presets/js-with-ts",
@@ -127,4 +128,4 @@
127
128
  "<rootDir>/dist/*.test.js"
128
129
  ]
129
130
  }
130
- }
131
+ }
package/r2.js ADDED
@@ -0,0 +1,163 @@
1
+ import crypto from 'crypto';
2
+ import dotenv from 'dotenv';
3
+ import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
4
+ import JSZip from 'jszip';
5
+ import mime from 'mime-types';
6
+ import path from 'path';
7
+
8
+ dotenv.config({ quiet: true });
9
+
10
+ let r2Client;
11
+
12
+ const trimSlashes = (value = '') => value.replace(/^\/+|\/+$/g, '');
13
+
14
+ export const createJobId = () => {
15
+ return `conv_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;
16
+ };
17
+
18
+ export const inferContentType = (fileName, fallback = 'application/octet-stream') => {
19
+ return mime.lookup(fileName) || fallback;
20
+ };
21
+
22
+ export const getR2Config = () => {
23
+ const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
24
+ const bucket = process.env.CLOUDFLARE_R2_BUCKET;
25
+ const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
26
+ const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
27
+ const publicBaseUrl = process.env.CLOUDFLARE_R2_PUBLIC_BASE_URL || '';
28
+ const endpoint =
29
+ process.env.CLOUDFLARE_R2_ENDPOINT || (accountId ? `https://${accountId}.r2.cloudflarestorage.com` : '');
30
+
31
+ return {
32
+ accountId,
33
+ bucket,
34
+ accessKeyId,
35
+ secretAccessKey,
36
+ publicBaseUrl: publicBaseUrl.replace(/\/+$/, ''),
37
+ endpoint,
38
+ mockMode: process.env.HTG_R2_MOCK === '1',
39
+ };
40
+ };
41
+
42
+ export const getR2Client = () => {
43
+ if (r2Client) {
44
+ return r2Client;
45
+ }
46
+
47
+ const config = getR2Config();
48
+
49
+ if (!config.bucket) {
50
+ throw new Error('CLOUDFLARE_R2_BUCKET is required.');
51
+ }
52
+
53
+ if (!config.endpoint) {
54
+ throw new Error('CLOUDFLARE_R2_ACCOUNT_ID or CLOUDFLARE_R2_ENDPOINT is required.');
55
+ }
56
+
57
+ if (!config.accessKeyId || !config.secretAccessKey) {
58
+ throw new Error('CLOUDFLARE_R2_ACCESS_KEY_ID and CLOUDFLARE_R2_SECRET_ACCESS_KEY are required.');
59
+ }
60
+
61
+ r2Client = new S3Client({
62
+ region: 'auto',
63
+ endpoint: config.endpoint,
64
+ credentials: {
65
+ accessKeyId: config.accessKeyId,
66
+ secretAccessKey: config.secretAccessKey,
67
+ },
68
+ });
69
+
70
+ return r2Client;
71
+ };
72
+
73
+ export const buildR2Url = (storageKey) => {
74
+ const { publicBaseUrl, mockMode } = getR2Config();
75
+ if (!publicBaseUrl) {
76
+ if (mockMode) {
77
+ return `https://storage.example.com/${trimSlashes(storageKey)}`;
78
+ }
79
+ throw new Error('CLOUDFLARE_R2_PUBLIC_BASE_URL is required for real R2 uploads.');
80
+ }
81
+ return `${publicBaseUrl}/${trimSlashes(storageKey)}`;
82
+ };
83
+
84
+ export const uploadBufferToR2 = async ({
85
+ storageKey,
86
+ body,
87
+ contentType,
88
+ cacheControl,
89
+ metadata,
90
+ }) => {
91
+ const config = getR2Config();
92
+ const normalizedKey = trimSlashes(storageKey);
93
+ const bufferBody = Buffer.isBuffer(body) ? body : Buffer.from(body);
94
+ const resolvedContentType = contentType || inferContentType(path.basename(normalizedKey));
95
+
96
+ if (config.mockMode) {
97
+ return {
98
+ storageKey: normalizedKey,
99
+ path: `/${normalizedKey}`,
100
+ url: buildR2Url(normalizedKey),
101
+ size: bufferBody.byteLength,
102
+ type: resolvedContentType,
103
+ };
104
+ }
105
+
106
+ const client = getR2Client();
107
+
108
+ await client.send(
109
+ new PutObjectCommand({
110
+ Bucket: config.bucket,
111
+ Key: normalizedKey,
112
+ Body: bufferBody,
113
+ ContentType: resolvedContentType,
114
+ CacheControl: cacheControl,
115
+ Metadata: metadata,
116
+ })
117
+ );
118
+
119
+ return {
120
+ storageKey: normalizedKey,
121
+ path: `/${normalizedKey}`,
122
+ url: buildR2Url(normalizedKey),
123
+ size: bufferBody.byteLength,
124
+ type: resolvedContentType,
125
+ };
126
+ };
127
+
128
+ export const createFileRecord = ({
129
+ id,
130
+ name,
131
+ kind,
132
+ storageKey,
133
+ size,
134
+ type,
135
+ url,
136
+ }) => {
137
+ return {
138
+ id,
139
+ name,
140
+ type,
141
+ size,
142
+ path: `/${trimSlashes(storageKey)}`,
143
+ url,
144
+ kind,
145
+ };
146
+ };
147
+
148
+ export const zipEntriesToBuffer = async (entries) => {
149
+ const zip = new JSZip();
150
+
151
+ for (const entry of entries) {
152
+ if (!entry || entry.body == null) {
153
+ continue;
154
+ }
155
+
156
+ zip.file(trimSlashes(entry.zipPath || entry.name), entry.body);
157
+ }
158
+
159
+ return zip.generateAsync({
160
+ type: 'nodebuffer',
161
+ compression: 'DEFLATE',
162
+ });
163
+ };