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.
Files changed (60) hide show
  1. package/.env.example +20 -0
  2. package/.eslintrc.json +35 -0
  3. package/.github/workflows/build.yml +26 -0
  4. package/.github/workflows/coverage.yml +26 -0
  5. package/.github/workflows/sync-npm.yml +154 -0
  6. package/.nyc_output/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
  7. package/.nyc_output/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
  8. package/.nyc_output/processinfo/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
  9. package/.nyc_output/processinfo/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
  10. package/.nyc_output/processinfo/index.json +1 -0
  11. package/@types.d.ts +3 -0
  12. package/coverage/coverage-final.json +4 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/prettify.css +1 -0
  16. package/coverage/lcov-report/prettify.js +2 -0
  17. package/coverage/lcov-report/sorter.js +210 -0
  18. package/coverage/lcov.info +198 -0
  19. package/coverage-demo.test.ts +8 -0
  20. package/dist/coverage-demo.test.js +10 -0
  21. package/dist/coverage-demo.test.js.map +1 -0
  22. package/dist/globals.js +24 -0
  23. package/dist/globals.js.map +1 -0
  24. package/dist/index.js +36 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/index.test.js +166 -0
  27. package/dist/index.test.js.map +1 -0
  28. package/dist/package.json +130 -0
  29. package/dist/snapapi-screenshot.test.js +44 -0
  30. package/dist/snapapi-screenshot.test.js.map +1 -0
  31. package/dist/src/coverage-demo.js +7 -0
  32. package/dist/src/coverage-demo.js.map +1 -0
  33. package/dist/src/utils-extra.test.js +137 -0
  34. package/dist/src/utils-extra.test.js.map +1 -0
  35. package/dist/src/utils.test.js +65 -0
  36. package/dist/src/utils.test.js.map +1 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -0
  38. package/dist/utils.js +61 -0
  39. package/dist/utils.js.map +1 -0
  40. package/fetch-page-assets.test.ts +448 -0
  41. package/index.d.ts +173 -0
  42. package/index.js +628 -249
  43. package/index.test.ts +774 -0
  44. package/index.ts +155 -1530
  45. package/package.json +87 -15
  46. package/r2.js +163 -0
  47. package/readme.md +126 -72
  48. package/scripts/patch-fetch-page-assets.mjs +13 -0
  49. package/scripts/sync-from-npm.mjs +115 -0
  50. package/snapapi-screenshot.test.ts +46 -0
  51. package/src/coverage-demo.ts +3 -0
  52. package/src/utils-extra.test.ts +108 -0
  53. package/src/utils.test.ts +36 -0
  54. package/temp-block-test.js +19 -0
  55. package/tsconfig.json +25 -4
  56. package/utils.ts +56 -0
  57. package/vendor/fetch-page-assets/LICENSE.MD +21 -0
  58. package/vendor/fetch-page-assets/README.md +117 -0
  59. package/vendor/fetch-page-assets/index.js +362 -0
  60. package/vendor/fetch-page-assets/package.json +48 -0
package/package.json CHANGED
@@ -1,37 +1,57 @@
1
1
  {
2
2
  "dependencies": {
3
- "@babel/core": "^7.28.5",
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.1.2",
7
- "css-scoping": "^1.0.1",
8
- "fetch-page-assets": "^1.2.6",
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
- "html-screenshots": "^1.2.7",
11
- "image-to-base64": "^2.2.0",
12
- "node-html-to-jsx": "^1.3.7",
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
- "type": "module",
24
- "types": "index.ts",
41
+ "types": "index.d.ts",
25
42
  "name": "html-to-gutenberg",
26
- "version": "4.2.8",
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
- "test": "jest",
34
- "build": "tsc"
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
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)
13
4
 
14
- - 🪄 **Instantly transforms static HTML into Gutenberg blocks**
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
- - 🔌 **Generates a complete, installable WordPress block plugin**
18
- Outputs all necessary plugin files (JS, CSS, PHP) so you can drop them into WordPress immediately.
19
-
7
+ ## What changed
20
8
 
21
- - 🎨 **Keeps your design intact**
22
- Automatically extracts and preserves CSS from the original HTML into a separate `style.css`.
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
- - 🧩 **Modular and scalable**
26
- Separates assets (JS, CSS, components) into clean files, making it easy to maintain and extend.
27
-
16
+ ```bash
17
+ npm install html-to-gutenberg
18
+ ```
28
19
 
29
- - 📦 **Seamlessly integrates with dynamic block systems**
30
- Works perfectly for headless or custom Gutenberg setups where blocks are registered via JS, not PHP.
31
-
20
+ ## Environment
32
21
 
33
- - 🚀 **Speeds up prototyping**
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
- - 🧠 **Works with your file system or plugin builder logic**
38
- Since it returns all files as either strings or source files, you can save them however you like (via PHP, APIs, etc.).
24
+ ```bash
25
+ cp .env.example .env
26
+ ```
39
27
 
28
+ Required for R2-backed job output:
40
29
 
41
- - 🧰 **Built for automation and customization**
42
- Can be embedded in custom tools, UIs, or pipelines to generate Gutenberg blocks on demand.
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
- ## Installation
38
+ - `CLOUDFLARE_API_TOKEN`
39
+ - `SNAPAPI_KEY`
48
40
 
49
-
41
+ ## Getting and rotating Cloudflare credentials
50
42
 
51
- Install html-to-gutenberg with npm:
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
- npm install html-to-gutenberg
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
- ```javascript
59
+ ```js
60
+ import block from 'html-to-gutenberg';
68
61
 
69
- // block-generator.js
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
- import block from 'html-to-gutenberg';
72
+ console.log(result);
73
+ ```
72
74
 
73
- const htmlString = '<div>My content</div>';
75
+ Example response:
74
76
 
77
+ ```json
75
78
  {
76
- const files = await block(htmlString, { name: 'My Block' });
77
- console.log(files);
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
- When provided with a valid HTML string and the required options, the block function will generate the necessary WordPress block files with the specified configuration. To install the block and its assets, simply load the generated folder into the plugins folder and activate it.
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
- ## Example
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
- [Working demo](https://www.html-to-gutenberg.io/)
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
- ## Options object reference
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
- | Option | Description | Type | Required? | Default |
100
- |---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------------------------------------------------------------|-------------------|
101
- | name | The name of your block. This will also be used for the folder name and internal references. | string | Yes | My block |
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
+ }