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/.env.example +20 -3
- package/.github/workflows/sync-npm.yml +154 -0
- package/fetch-page-assets.test.ts +448 -0
- package/index.d.ts +173 -0
- package/index.js +570 -224
- package/index.test.ts +633 -4
- package/index.ts +168 -63
- package/package.json +25 -24
- package/r2.js +163 -0
- package/readme.md +122 -88
- package/scripts/patch-fetch-page-assets.mjs +13 -0
- package/scripts/sync-from-npm.mjs +115 -0
- package/tsconfig.json +17 -2
- package/vendor/fetch-page-assets/LICENSE.MD +21 -0
- package/vendor/fetch-page-assets/README.md +117 -0
- package/vendor/fetch-page-assets/index.js +362 -0
- package/vendor/fetch-page-assets/package.json +48 -0
- package/.env +0 -1
package/index.ts
CHANGED
|
@@ -1,68 +1,173 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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": "
|
|
50
|
-
"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
|
-
"
|
|
79
|
+
"c8": {
|
|
76
80
|
"reporter": [
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
],
|
|
80
|
-
"exclude": [
|
|
81
|
-
"coverage"
|
|
81
|
+
"text",
|
|
82
|
+
"lcov"
|
|
82
83
|
],
|
|
83
84
|
"include": [
|
|
84
|
-
"
|
|
85
|
-
"
|
|
85
|
+
"index.js",
|
|
86
|
+
"utils.ts",
|
|
87
|
+
"globals.js",
|
|
88
|
+
"vendor/fetch-page-assets/index.js"
|
|
86
89
|
],
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
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
|
+
};
|