html-to-gutenberg 4.2.8 → 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 -0
- package/.eslintrc.json +35 -0
- package/.github/workflows/build.yml +26 -0
- package/.github/workflows/coverage.yml +26 -0
- package/.github/workflows/sync-npm.yml +154 -0
- package/.nyc_output/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
- package/.nyc_output/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
- package/.nyc_output/processinfo/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
- package/.nyc_output/processinfo/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/@types.d.ts +3 -0
- package/coverage/coverage-final.json +4 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +198 -0
- package/coverage-demo.test.ts +8 -0
- package/dist/coverage-demo.test.js +10 -0
- package/dist/coverage-demo.test.js.map +1 -0
- package/dist/globals.js +24 -0
- package/dist/globals.js.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.js +166 -0
- package/dist/index.test.js.map +1 -0
- package/dist/package.json +130 -0
- package/dist/snapapi-screenshot.test.js +44 -0
- package/dist/snapapi-screenshot.test.js.map +1 -0
- package/dist/src/coverage-demo.js +7 -0
- package/dist/src/coverage-demo.js.map +1 -0
- package/dist/src/utils-extra.test.js +137 -0
- package/dist/src/utils-extra.test.js.map +1 -0
- package/dist/src/utils.test.js +65 -0
- package/dist/src/utils.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils.js +61 -0
- package/dist/utils.js.map +1 -0
- package/fetch-page-assets.test.ts +448 -0
- package/index.d.ts +173 -0
- package/index.js +628 -249
- package/index.test.ts +774 -0
- package/index.ts +155 -1530
- package/package.json +87 -15
- package/r2.js +163 -0
- package/readme.md +126 -72
- package/scripts/patch-fetch-page-assets.mjs +13 -0
- package/scripts/sync-from-npm.mjs +115 -0
- package/snapapi-screenshot.test.ts +46 -0
- package/src/coverage-demo.ts +3 -0
- package/src/utils-extra.test.ts +108 -0
- package/src/utils.test.ts +36 -0
- package/temp-block-test.js +19 -0
- package/tsconfig.json +25 -4
- package/utils.ts +56 -0
- 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/package.json
CHANGED
|
@@ -1,37 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@
|
|
3
|
+
"@aws-sdk/client-s3": "^3.888.0",
|
|
4
|
+
"@babel/core": "^7.29.0",
|
|
4
5
|
"@babel/preset-react": "^7.28.5",
|
|
5
6
|
"@svgr/core": "^8.1.0",
|
|
6
|
-
"cheerio": "^1.
|
|
7
|
-
"css-scoping": "^1.0.
|
|
8
|
-
"
|
|
7
|
+
"cheerio": "^1.2.0",
|
|
8
|
+
"css-scoping": "^1.0.5",
|
|
9
|
+
"dotenv": "^17.3.1",
|
|
10
|
+
"fetch-page-assets": "^1.2.7",
|
|
9
11
|
"fs": "^0.0.1-security",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"node-
|
|
12
|
+
"jszip": "^3.10.1",
|
|
13
|
+
"mime-types": "^3.0.1",
|
|
14
|
+
"node-fetch": "^3.3.2",
|
|
15
|
+
"node-html-to-jsx": "^1.4.4",
|
|
13
16
|
"path": "^0.12.7"
|
|
14
17
|
},
|
|
15
18
|
"devDependencies": {
|
|
19
|
+
"@eslint/js": "^10.0.1",
|
|
16
20
|
"@types/babel__core": "^7.20.5",
|
|
17
21
|
"@types/beautify": "^0.0.3",
|
|
22
|
+
"@types/chai": "^5.2.3",
|
|
18
23
|
"@types/cheerio": "^1.0.0",
|
|
19
24
|
"@types/image-to-base64": "^2.1.2",
|
|
25
|
+
"@types/mocha": "^10.0.10",
|
|
20
26
|
"@types/prettier": "^3.0.0",
|
|
21
|
-
"@types/svgo": "^3.0.0"
|
|
27
|
+
"@types/svgo": "^3.0.0",
|
|
28
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
29
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
30
|
+
"chai": "^6.2.2",
|
|
31
|
+
"c8": "^11.0.0",
|
|
32
|
+
"coveralls": "^3.1.1",
|
|
33
|
+
"eslint": "^10.0.2",
|
|
34
|
+
"mocha": "^11.3.0",
|
|
35
|
+
"nyc": "^18.0.0",
|
|
36
|
+
"source-map-support": "^0.5.21",
|
|
37
|
+
"ts-jest": "^29.4.6",
|
|
38
|
+
"ts-node": "^10.9.2",
|
|
39
|
+
"typescript-eslint": "^8.56.1"
|
|
22
40
|
},
|
|
23
|
-
"
|
|
24
|
-
"types": "index.ts",
|
|
41
|
+
"types": "index.d.ts",
|
|
25
42
|
"name": "html-to-gutenberg",
|
|
26
|
-
"version": "4.2.
|
|
43
|
+
"version": "4.2.10",
|
|
27
44
|
"description": "Transform any valid HTML string into fully editable WP Gutenberg blocks in seconds rather than hours.",
|
|
28
45
|
"main": "index.js",
|
|
29
46
|
"directories": {
|
|
30
47
|
"test": "test"
|
|
31
48
|
},
|
|
32
49
|
"scripts": {
|
|
33
|
-
"
|
|
34
|
-
"
|
|
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",
|
|
52
|
+
"build": "tsc",
|
|
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"
|
|
35
55
|
},
|
|
36
56
|
"repository": {
|
|
37
57
|
"type": "git",
|
|
@@ -55,5 +75,57 @@
|
|
|
55
75
|
"bugs": {
|
|
56
76
|
"url": "https://github.com/DiogoAngelim/html-to-gutenberg/issues"
|
|
57
77
|
},
|
|
58
|
-
"homepage": "https://www.html-to-gutenberg.io"
|
|
59
|
-
|
|
78
|
+
"homepage": "https://www.html-to-gutenberg.io",
|
|
79
|
+
"c8": {
|
|
80
|
+
"reporter": [
|
|
81
|
+
"text",
|
|
82
|
+
"lcov"
|
|
83
|
+
],
|
|
84
|
+
"include": [
|
|
85
|
+
"index.js",
|
|
86
|
+
"utils.ts",
|
|
87
|
+
"globals.js",
|
|
88
|
+
"vendor/fetch-page-assets/index.js"
|
|
89
|
+
],
|
|
90
|
+
"exclude": [
|
|
91
|
+
"coverage",
|
|
92
|
+
"dist",
|
|
93
|
+
"node_modules",
|
|
94
|
+
"scripts",
|
|
95
|
+
"**/*.test.ts"
|
|
96
|
+
],
|
|
97
|
+
"all": true
|
|
98
|
+
},
|
|
99
|
+
"jest": {
|
|
100
|
+
"preset": "ts-jest/presets/js-with-ts",
|
|
101
|
+
"testEnvironment": "node",
|
|
102
|
+
"extensionsToTreatAsEsm": [
|
|
103
|
+
".ts"
|
|
104
|
+
],
|
|
105
|
+
"transform": {
|
|
106
|
+
"^.+\\.tsx?$": [
|
|
107
|
+
"ts-jest",
|
|
108
|
+
{
|
|
109
|
+
"useESM": true
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"globals": {
|
|
114
|
+
"ts-jest": {
|
|
115
|
+
"tsconfig": "tsconfig.json",
|
|
116
|
+
"useESM": true
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"moduleNameMapper": {
|
|
120
|
+
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
121
|
+
},
|
|
122
|
+
"collectCoverage": true,
|
|
123
|
+
"collectCoverageFrom": [
|
|
124
|
+
"dist/*.js",
|
|
125
|
+
"!dist/*.test.js"
|
|
126
|
+
],
|
|
127
|
+
"testMatch": [
|
|
128
|
+
"<rootDir>/dist/*.test.js"
|
|
129
|
+
]
|
|
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
|
+
};
|
package/readme.md
CHANGED
|
@@ -1,116 +1,170 @@
|
|
|
1
|
-
|
|
2
1
|
# HTML to Gutenberg Converter
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Convert HTML strings to valid, editable WordPress Gutenberg blocks in seconds instead of hours. With this script, you can create and build valid Gutenberg blocks that feature editable text, forms, inline and background images, as well as SVGs. It includes support for TailwindCSS.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Features
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
[](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
Saves hours of manual work by automating block creation from any valid HTML snippet.
|
|
5
|
+
Convert HTML into editable WordPress Gutenberg blocks and publish the generated package to Cloudflare R2 without writing the output to disk.
|
|
16
6
|
|
|
17
|
-
|
|
18
|
-
Outputs all necessary plugin files (JS, CSS, PHP) so you can drop them into WordPress immediately.
|
|
19
|
-
|
|
7
|
+
## What changed
|
|
20
8
|
|
|
21
|
-
-
|
|
22
|
-
|
|
9
|
+
- `html-to-gutenberg` now supports a `job` output mode that uploads generated files to R2 and returns a JSON manifest.
|
|
10
|
+
- `fetch-page-assets` can upload downloaded assets directly to R2 and return their metadata.
|
|
11
|
+
- Output bundles are zipped in memory and uploaded to R2 as `output.zip`.
|
|
12
|
+
- Secrets stay in `.env` and should never be committed.
|
|
23
13
|
|
|
14
|
+
## Installation
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
```bash
|
|
17
|
+
npm install html-to-gutenberg
|
|
18
|
+
```
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
Works perfectly for headless or custom Gutenberg setups where blocks are registered via JS, not PHP.
|
|
31
|
-
|
|
20
|
+
## Environment
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
Ideal for quickly testing block ideas or converting landing pages and templates into WordPress blocks.
|
|
22
|
+
Copy `.env.example` to `.env` and keep the real values private.
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
```bash
|
|
25
|
+
cp .env.example .env
|
|
26
|
+
```
|
|
39
27
|
|
|
28
|
+
Required for R2-backed job output:
|
|
40
29
|
|
|
41
|
-
-
|
|
42
|
-
|
|
30
|
+
- `CLOUDFLARE_R2_ACCOUNT_ID`
|
|
31
|
+
- `CLOUDFLARE_R2_BUCKET`
|
|
32
|
+
- `CLOUDFLARE_R2_ACCESS_KEY_ID`
|
|
33
|
+
- `CLOUDFLARE_R2_SECRET_ACCESS_KEY`
|
|
34
|
+
- `CLOUDFLARE_R2_PUBLIC_BASE_URL`
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
Optional:
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
- `CLOUDFLARE_API_TOKEN`
|
|
39
|
+
- `SNAPAPI_KEY`
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
## Getting and rotating Cloudflare credentials
|
|
50
42
|
|
|
51
|
-
|
|
43
|
+
1. Open the Cloudflare dashboard.
|
|
44
|
+
2. Create or update your R2 access keys for the target bucket.
|
|
45
|
+
3. Store the new values in `.env`.
|
|
46
|
+
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`.
|
|
47
|
+
5. Restart your app or redeploy after updating `.env`.
|
|
48
|
+
6. Revoke the old token or key after the new one is live.
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
To verify a Cloudflare API token without exposing it in code, use an environment variable:
|
|
54
51
|
|
|
55
52
|
```bash
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
curl "https://api.cloudflare.com/client/v4/user/tokens/verify" \
|
|
54
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
|
|
59
55
|
```
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## Usage/Examples
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
## Usage
|
|
66
58
|
|
|
67
|
-
```
|
|
59
|
+
```js
|
|
60
|
+
import block from 'html-to-gutenberg';
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
const result = await block('<div>Hello world</div>', {
|
|
63
|
+
title: 'Marketing Hero',
|
|
64
|
+
slug: 'marketing-hero',
|
|
65
|
+
namespace: 'wp',
|
|
66
|
+
baseUrl: 'https://example.com',
|
|
67
|
+
outputMode: 'job',
|
|
68
|
+
uploadToR2: true,
|
|
69
|
+
jobId: 'conv_123'
|
|
70
|
+
});
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
console.log(result);
|
|
73
|
+
```
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
Example response:
|
|
74
76
|
|
|
77
|
+
```json
|
|
75
78
|
{
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
"jobId": "conv_123",
|
|
80
|
+
"status": "completed",
|
|
81
|
+
"output": {
|
|
82
|
+
"files": [
|
|
83
|
+
{
|
|
84
|
+
"id": "file_1",
|
|
85
|
+
"name": "block.js",
|
|
86
|
+
"type": "text/javascript",
|
|
87
|
+
"size": 18234,
|
|
88
|
+
"path": "/generated/conv_123/block.js",
|
|
89
|
+
"url": "https://storage.example.com/generated/conv_123/block.js",
|
|
90
|
+
"kind": "source"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "file_2",
|
|
94
|
+
"name": "asset.png",
|
|
95
|
+
"type": "image/png",
|
|
96
|
+
"size": 48211,
|
|
97
|
+
"path": "/generated/conv_123/assets/asset.png",
|
|
98
|
+
"url": "https://storage.example.com/generated/conv_123/assets/asset.png",
|
|
99
|
+
"kind": "asset"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"bundle": {
|
|
103
|
+
"name": "output.zip",
|
|
104
|
+
"path": "/generated/conv_123/output.zip",
|
|
105
|
+
"url": "https://storage.example.com/generated/conv_123/output.zip",
|
|
106
|
+
"zipUrl": "https://storage.example.com/generated/conv_123/output.zip"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
78
109
|
}
|
|
79
|
-
|
|
80
110
|
```
|
|
81
111
|
|
|
82
|
-
|
|
112
|
+
## Legacy mode
|
|
83
113
|
|
|
84
|
-
|
|
114
|
+
If you still need the previous local-string output for existing tooling or tests, use:
|
|
85
115
|
|
|
86
|
-
|
|
116
|
+
```js
|
|
117
|
+
const files = await block('<div>Hello world</div>', {
|
|
118
|
+
title: 'Legacy Block',
|
|
119
|
+
outputPath: process.cwd(),
|
|
120
|
+
writeFiles: false,
|
|
121
|
+
outputMode: 'legacy'
|
|
122
|
+
});
|
|
123
|
+
```
|
|
87
124
|
|
|
88
|
-
|
|
125
|
+
In `legacy` mode, the function returns the generated file contents instead of the R2 job manifest.
|
|
89
126
|
|
|
90
|
-
|
|
127
|
+
## Options
|
|
91
128
|
|
|
92
|
-
|
|
129
|
+
| Option | Description | Type | Default |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| `title` | Human-readable block title shown in the editor. | `string` | `My block` |
|
|
132
|
+
| `slug` | Filesystem-safe internal block name. Defaults to a slugified title. | `string` | slugified `title` |
|
|
133
|
+
| `baseUrl` | Base URL used to resolve relative asset paths in HTML and CSS. | `string \| null` | `null` |
|
|
134
|
+
| `namespace` | Gutenberg block namespace. | `string` | `wp` |
|
|
135
|
+
| `category` | Gutenberg block category. | `string` | `common` |
|
|
136
|
+
| `registerCategoryIfMissing` | Adds a custom editor category before block registration when needed. | `boolean` | `false` |
|
|
137
|
+
| `outputPath` | Absolute directory used for local legacy output. In `job` mode it is only a logical working base. | `string` | current directory |
|
|
138
|
+
| `writeFiles` | Writes local files in `legacy` mode. When `false`, returns generated files in memory. | `boolean` | `false` in the streamlined API |
|
|
139
|
+
| `generatePreviewImage` | Generates and uploads `preview.jpeg` using SnapAPI. | `boolean` | `false` |
|
|
140
|
+
| `jsFiles` | Remote JS dependencies to enqueue. | `string[]` | `[]` |
|
|
141
|
+
| `cssFiles` | Remote CSS dependencies to enqueue. | `string[]` | `[]` |
|
|
142
|
+
| `outputMode` | Advanced option. `job` uploads to R2 and returns JSON. `legacy` returns raw file contents. | `'job' \| 'legacy'` | `job`, unless local-output options imply `legacy` |
|
|
143
|
+
| `uploadToR2` | Advanced option to force or disable R2 uploads. | `boolean` | `true` in `job` mode |
|
|
144
|
+
| `jobId` | Advanced stable conversion identifier. | `string` | autogenerated |
|
|
93
145
|
|
|
94
|
-
|
|
146
|
+
Legacy aliases still work for backwards compatibility:
|
|
95
147
|
|
|
96
|
-
|
|
148
|
+
- `name` -> `title`
|
|
149
|
+
- `prefix` -> `namespace`
|
|
150
|
+
- `source` -> `baseUrl`
|
|
151
|
+
- `basePath` -> `outputPath`
|
|
152
|
+
- `shouldSaveFiles` -> `writeFiles`
|
|
153
|
+
- `generateIconPreview` -> `generatePreviewImage`
|
|
97
154
|
|
|
155
|
+
## Notes
|
|
98
156
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| source | A URL where relative paths resolve. E.g., `http://localhost/website`. | string | Yes, only if the HTML string or the stylesheet has relative paths. | null |
|
|
103
|
-
| prefix | A namespace prefix for the block name, typically aligned with your project (e.g., "wp" or "myplugins"). | string | No | wp |
|
|
104
|
-
| category | The WordPress block category where the block appears in the editor. Use an existing one or register a custom category if needed. | string | No | common |
|
|
105
|
-
| basePath | The absolute path where the output files and folders will be saved. | string | No | Current directory |
|
|
106
|
-
| generateIconPreview | If `true`, generates a static image preview (JPEG) of the block's icon for display in the block picker. | boolean | No | false |
|
|
107
|
-
| shouldSaveFiles | When `true`, the generated block files are saved directly to disk. When `false`, returns an object containing the file contents as strings instead. | boolean | No | true |
|
|
108
|
-
| jsFiles | An array of external JavaScript file URLs to enqueue with the block on the editor and the frontend. Useful for adding remote libraries. | string[] | No | [] |
|
|
109
|
-
| cssFiles | An array of external CSS file URLs to enqueue with the block on the editor and the frontend. Useful for adding additional remote stylesheets. | string[] | No | [] |
|
|
157
|
+
- Generated output is zipped in memory before upload.
|
|
158
|
+
- R2 uploads use the values from `.env`.
|
|
159
|
+
- Do not hardcode real tokens or keys in source code, docs, or tests.
|
|
110
160
|
|
|
161
|
+
## Running tests
|
|
111
162
|
|
|
163
|
+
```bash
|
|
164
|
+
npm install
|
|
165
|
+
npm test
|
|
166
|
+
```
|
|
112
167
|
|
|
113
168
|
## License
|
|
114
|
-
|
|
115
169
|
|
|
116
|
-
[MIT](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)
|
|
170
|
+
[MIT](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
const sourcePath = path.join(projectRoot, "vendor", "fetch-page-assets", "index.js");
|
|
6
|
+
const targetPath = path.join(projectRoot, "node_modules", "fetch-page-assets", "index.js");
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(sourcePath) || !fs.existsSync(targetPath)) {
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
13
|
+
console.log("Applied local fetch-page-assets performance patch");
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const repoRoot = process.cwd();
|
|
7
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sync-from-npm-'));
|
|
8
|
+
const targetDir = process.env.NPM_SYNC_TARGET_DIR?.trim() || '.';
|
|
9
|
+
const targetRoot = path.resolve(repoRoot, targetDir);
|
|
10
|
+
|
|
11
|
+
const readPackageName = () => {
|
|
12
|
+
const packageJsonPath = path.join(targetRoot, 'package.json');
|
|
13
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
14
|
+
return packageJson.name;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const parsePreserveList = (value) => {
|
|
18
|
+
const rawEntries = (value || '.git,.github,node_modules,.env,scripts')
|
|
19
|
+
.split(',')
|
|
20
|
+
.map((entry) => entry.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
|
|
23
|
+
return new Set(rawEntries);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const shouldPreserve = (entryName, preserveList) => {
|
|
27
|
+
return preserveList.has(entryName);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const removeUnsyncedEntries = (sourceRoot, preserveList, dryRun) => {
|
|
31
|
+
if (!fs.existsSync(targetRoot)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const entry of fs.readdirSync(targetRoot)) {
|
|
36
|
+
if (shouldPreserve(entry, preserveList)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const targetPath = path.join(targetRoot, entry);
|
|
41
|
+
const sourcePath = path.join(sourceRoot, entry);
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(sourcePath)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (dryRun) {
|
|
48
|
+
console.log(`Would remove ${entry}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const copyPackageEntries = (sourceRoot, preserveList, dryRun) => {
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const entry of fs.readdirSync(sourceRoot)) {
|
|
62
|
+
if (shouldPreserve(entry, preserveList)) {
|
|
63
|
+
console.log(`Skipping preserved path ${entry}`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sourcePath = path.join(sourceRoot, entry);
|
|
68
|
+
const targetPath = path.join(targetRoot, entry);
|
|
69
|
+
|
|
70
|
+
if (dryRun) {
|
|
71
|
+
console.log(`Would sync ${entry}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
76
|
+
fs.cpSync(sourcePath, targetPath, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const packageName = process.env.NPM_PACKAGE_NAME || readPackageName();
|
|
81
|
+
const requestedVersion = process.env.NPM_SYNC_VERSION?.trim();
|
|
82
|
+
const distTag = process.env.NPM_SYNC_DIST_TAG?.trim() || 'latest';
|
|
83
|
+
const preserveList = parsePreserveList(process.env.NPM_SYNC_PRESERVE_PATHS);
|
|
84
|
+
const dryRun = process.env.NPM_SYNC_DRY_RUN === '1';
|
|
85
|
+
const packageSpec = requestedVersion ? `${packageName}@${requestedVersion}` : `${packageName}@${distTag}`;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
console.log(`Packing ${packageSpec} into ${targetDir}`);
|
|
89
|
+
const packedTarball = execFileSync('npm', ['pack', packageSpec, '--silent'], {
|
|
90
|
+
cwd: tempRoot,
|
|
91
|
+
encoding: 'utf8',
|
|
92
|
+
}).trim().split('\n').pop();
|
|
93
|
+
|
|
94
|
+
if (!packedTarball) {
|
|
95
|
+
throw new Error(`Failed to pack ${packageSpec}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
execFileSync('tar', ['-xzf', packedTarball], { cwd: tempRoot });
|
|
99
|
+
|
|
100
|
+
const sourceRoot = path.join(tempRoot, 'package');
|
|
101
|
+
if (!fs.existsSync(sourceRoot)) {
|
|
102
|
+
throw new Error('Packed npm tarball did not contain a package/ directory.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
removeUnsyncedEntries(sourceRoot, preserveList, dryRun);
|
|
106
|
+
copyPackageEntries(sourceRoot, preserveList, dryRun);
|
|
107
|
+
|
|
108
|
+
const syncedPackageJson = JSON.parse(
|
|
109
|
+
fs.readFileSync(path.join(sourceRoot, 'package.json'), 'utf8')
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
console.log(`Synced ${packageName} version ${syncedPackageJson.version} into ${targetDir}`);
|
|
113
|
+
} finally {
|
|
114
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
115
|
+
}
|