@ideasonpurpose/build-tools-wordpress 2.6.1 → 2.6.3

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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### v2.6.2
8
+
9
+ > 23 March 2026
10
+
11
+ - feat: add error handling for WebSocket proxy requests
12
+
13
+ #### v2.6.1
14
+
15
+ > 23 March 2026
16
+
17
+ - chore: update dependencies in package.json
18
+
7
19
  #### v2.6.0
8
20
 
9
21
  > 13 March 2026
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ideasonpurpose/build-tools-wordpress
2
2
 
3
- #### Version 2.6.1
3
+ #### Version 2.6.3
4
4
 
5
5
  [![NPM Version](https://img.shields.io/npm/v/%40ideasonpurpose%2Fbuild-tools-wordpress?logo=npm)](https://www.npmjs.com/package/@ideasonpurpose/build-tools-wordpress)
6
6
  [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ideasonpurpose/build-tools-wordpress/npm-publish.yml?logo=github&logoColor=white)](https://github.com/ideasonpurpose/build-tools-wordpress#readme)
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ //@ts-check
4
+
5
+ /**
6
+ * Work in progress formatter for WordPress block pattern PHP files.
7
+ *
8
+ * TODO: Testing, naming, modularization, VS Code extension
9
+ */
10
+ import prettier from "prettier";
11
+
12
+ /**
13
+ * These container blocks should always have a single blank line
14
+ * before their opening comment and another blank line after their
15
+ * closing comment.
16
+ * - wp:paragraph
17
+ * - wp:column
18
+ * - wp:group
19
+ * - wp:image
20
+ * - wp:heading
21
+ * - wp:list
22
+ * - wp:quote
23
+ * - wp:pullquote
24
+ * - wp:buttons
25
+ * - wp:columns
26
+ * - wp:media-text
27
+ * - wp:gallery
28
+ */
29
+
30
+ import { readFile, writeFile } from "fs/promises";
31
+ import { resolve, basename } from "path";
32
+
33
+ /**
34
+ *
35
+ * @param {String} wpCommentTag
36
+ */
37
+ export function formatWpCommentJson(wpCommentTag) {
38
+ const match = wpCommentTag.match(/<!--\s*wp:[\w-]+?\s*(\{.*?\})\s*-->/s);
39
+ if (!match) {
40
+ return wpCommentTag; // Return original if no match
41
+ }
42
+
43
+ const jsonStr = match[1];
44
+ try {
45
+ const jsonObj = JSON.parse(jsonStr);
46
+ const indent =
47
+ JSON.stringify(jsonObj).length > 50 || Object.keys(jsonObj).length > 2
48
+ ? 2
49
+ : 0;
50
+ const prettyJson = JSON.stringify(jsonObj, null, indent);
51
+ return wpCommentTag.replace(jsonStr, prettyJson);
52
+ } catch (error) {
53
+ const message = error instanceof Error ? error.message : String(error);
54
+ throw new Error(`Invalid JSON in wp comment: ${message}`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * @param {string} content
60
+ * @returns {string}
61
+ */
62
+ export function formatAllWpComments(content) {
63
+ const htmlCommentRegex = /<!--[\s\S]*?-->/g;
64
+ return content.replace(
65
+ htmlCommentRegex,
66
+ /** @param {string} comment */ (comment) => {
67
+ return formatWpCommentJson(comment);
68
+ },
69
+ );
70
+ }
71
+
72
+ /**
73
+ * @param {string} content
74
+ * @returns {string}
75
+ */
76
+ export function normalizeCommentTagSpacing(content) {
77
+ let newContent = content;
78
+ const containerBlocks = [
79
+ "wp:buttons",
80
+ "wp:column",
81
+ "wp:columns",
82
+ "wp:gallery",
83
+ "wp:group",
84
+ "wp:heading",
85
+ "wp:image",
86
+ "wp:list",
87
+ "wp:media-text",
88
+ "wp:paragraph",
89
+ "wp:pattern",
90
+ "wp:pullquote",
91
+ "wp:quote",
92
+ ];
93
+
94
+ containerBlocks.forEach((block) => {
95
+ const openingTagRegex = new RegExp(`(<!--\\s*${block}[^>]*-->)`, "g");
96
+ const closingTagRegex = new RegExp(`>\\s*(<!--\\s*/${block}\\s*-->)`, "g");
97
+
98
+ // Ensure one blank line before opening tag
99
+ newContent = newContent.replace(
100
+ openingTagRegex,
101
+ /** @param {string} match @param {string} p1 */ (match, p1) => `\n${p1}`,
102
+ );
103
+
104
+ // Ensure one blank line after closing tag
105
+ newContent = newContent.replace(
106
+ closingTagRegex,
107
+ /** @param {string} match @param {string} p1 */ (match, p1) =>
108
+ `>\n${p1}\n`,
109
+ );
110
+ });
111
+ return newContent;
112
+ }
113
+
114
+ /**
115
+ * @param {String} filepath
116
+ */
117
+ async function formatWPBlockPattern(filepath) {
118
+ try {
119
+ const startTime = process.hrtime.bigint();
120
+ const rawFile = await readFile(filepath, "utf8");
121
+
122
+ const formattedHTML = await prettier.format(rawFile, {
123
+ parser: "html",
124
+ tabWidth: 0,
125
+ useTabs: false,
126
+ printWidth: 80,
127
+ htmlWhitespaceSensitivity: "css",
128
+ });
129
+
130
+ const cleanedWhiteSpace = normalizeCommentTagSpacing(formattedHTML);
131
+ const formattedCommentsJSON = formatAllWpComments(cleanedWhiteSpace);
132
+ const finalFormatted =
133
+ formattedCommentsJSON.replace(/\n{3,}/g, "\n\n").trim() + "\n";
134
+
135
+ await writeFile(filepath, finalFormatted, "utf8");
136
+ const endTime = process.hrtime.bigint();
137
+ const duration = Number(endTime - startTime);
138
+
139
+ console.log(`${basename(filepath)} ${(duration / 1e6).toFixed(2)}ms`);
140
+ } catch (error) {
141
+ console.error("Error:", error);
142
+ }
143
+ }
144
+
145
+ if (process.argv[2]) {
146
+ const fullPath = resolve(process.argv[2]);
147
+ formatWPBlockPattern(fullPath);
148
+ } else {
149
+ console.error("Error: A filepath is required.");
150
+ }
@@ -87,6 +87,14 @@ export async function devserverProxy(config) {
87
87
  }
88
88
  },
89
89
 
90
+ onProxyReqWs: (proxyReq, req, socket, options, head) => {
91
+ socket.on("error", (err) => {
92
+ if (err.code !== "EPIPE") {
93
+ console.error("WebSocket proxy error:", err);
94
+ }
95
+ });
96
+ },
97
+
90
98
  onProxyRes: function (proxyRes, req, res) {
91
99
  /**
92
100
  * Update urls in files with these content-types
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ideasonpurpose/build-tools-wordpress",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "Build scripts and dependencies for IOP's WordPress development environments.",
5
5
  "homepage": "https://github.com/ideasonpurpose/build-tools-wordpress#readme",
6
6
  "bugs": {
@@ -22,7 +22,8 @@
22
22
  "iop-build-port-reporter": "./bin/port-reporter.js",
23
23
  "iop-build-zip-archive": "./bin/zip.js",
24
24
  "iop-project-init": "./bin/project-init.js",
25
- "iop-html-php-prettier": "./bin/format-php-prettier.js"
25
+ "iop-html-php-prettier": "./bin/format-php-prettier.js",
26
+ "iop-format-wp-block-pattern": "./bin/format-wp-block-pattern.js"
26
27
  },
27
28
  "directories": {
28
29
  "lib": "lib"
@@ -35,40 +36,40 @@
35
36
  "dependencies": {
36
37
  "@ideasonpurpose/prettier-config": "^1.0.1",
37
38
  "@ideasonpurpose/stylelint-config": "^1.1.4",
38
- "@prettier/plugin-php": "^0.24.0",
39
+ "@prettier/plugin-php": "^0.25.0",
39
40
  "@rollup/plugin-commonjs": "^29.0.2",
40
41
  "@rollup/plugin-json": "^6.1.0",
41
42
  "@rollup/plugin-node-resolve": "^16.0.3",
42
43
  "@svgr/webpack": "^8.1.0",
43
- "@wordpress/dependency-extraction-webpack-plugin": "^6.42.0",
44
+ "@wordpress/dependency-extraction-webpack-plugin": "^6.43.0",
44
45
  "ansi-html": "^0.0.9",
45
46
  "archiver": "^7.0.1",
46
47
  "auto-changelog": "^2.5.0",
47
48
  "autoprefixer": "^10.4.27",
48
49
  "babel-loader": "^10.1.1",
49
- "caniuse-lite": "^1.0.30001781",
50
+ "caniuse-lite": "^1.0.30001785",
50
51
  "chalk": "^5.6.2",
51
52
  "chalk-cli": "^6.0.0",
52
53
  "classnames": "^2.5.1",
53
- "cli-truncate": "^5.2.0",
54
+ "cli-truncate": "^6.0.0",
54
55
  "copy-webpack-plugin": "^14.0.0",
55
56
  "cosmiconfig": "^9.0.1",
56
57
  "cross-env": "^10.1.0",
57
58
  "css-loader": "^7.1.4",
58
- "cssnano": "^7.1.3",
59
- "dotenv": "^17.3.1",
60
- "esbuild-loader": "^4.4.2",
61
- "eslint": "^10.1.0",
62
- "filesize": "^11.0.13",
59
+ "cssnano": "^7.1.4",
60
+ "dotenv": "^17.4.0",
61
+ "esbuild-loader": "^4.4.3",
62
+ "eslint": "^10.2.0",
63
+ "filesize": "^11.0.15",
63
64
  "fs-extra": "^11.3.4",
64
- "globby": "^16.1.1",
65
+ "globby": "^16.2.0",
65
66
  "html-webpack-plugin": "^5.6.6",
66
67
  "http-proxy": "^1.18.1",
67
68
  "humanize-duration": "^3.33.2",
68
69
  "image-minimizer-webpack-plugin": "^5.0.0",
69
70
  "is-text-path": "^3.0.0",
70
- "lodash": "^4.17.23",
71
- "mini-css-extract-plugin": "^2.10.1",
71
+ "lodash": "^4.18.1",
72
+ "mini-css-extract-plugin": "^2.10.2",
72
73
  "ora": "^9.3.0",
73
74
  "postcss": "^8.5.8",
74
75
  "postcss-loader": "^8.2.1",
@@ -78,7 +79,7 @@
78
79
  "read-package-up": "^12.0.0",
79
80
  "replacestream": "^4.0.3",
80
81
  "rimraf": "^6.1.3",
81
- "sass-embedded": "^1.98.0",
82
+ "sass-embedded": "^1.99.0",
82
83
  "sass-loader": "^16.0.7",
83
84
  "semver": "^7.7.4",
84
85
  "sharp": "^0.34.5",
@@ -86,11 +87,11 @@
86
87
  "string-length": "^7.0.1",
87
88
  "style-loader": "^4.0.0",
88
89
  "svgo": "^4.0.1",
89
- "svgo-loader": "^4.0.0",
90
+ "svgo-loader": "^5.0.0",
90
91
  "version-everything": "^0.11.4",
91
92
  "webpack": "^5.105.4",
92
- "webpack-bundle-analyzer": "^5.2.0",
93
- "webpack-dev-middleware": "^8.0.0",
93
+ "webpack-bundle-analyzer": "^5.3.0",
94
+ "webpack-dev-middleware": "^8.0.2",
94
95
  "webpack-dev-server": "^5.2.3",
95
96
  "webpack-manifest-plugin": "^6.0.1"
96
97
  },
@@ -98,8 +99,8 @@
98
99
  "webpack-cli": "^6.0.1"
99
100
  },
100
101
  "devDependencies": {
101
- "@vitest/coverage-v8": "^4.1.0",
102
- "vitest": "^4.1.0"
102
+ "@vitest/coverage-v8": "^4.1.2",
103
+ "vitest": "^4.1.2"
103
104
  },
104
105
  "version-everything": {
105
106
  "files": [
@@ -0,0 +1,28 @@
1
+ <?php
2
+ /**
3
+ * Title: Home Carousel & News Hero
4
+ * Slug: iop/home-carousel-news-hero
5
+ * Description: Carousel and news panel for top of home template
6
+ * Categories: home-page
7
+ * Viewport Width: 940
8
+ * Inserter: true
9
+ */
10
+ ?>
11
+
12
+ <!-- wp:group {"align":"full", "className":"home-carousel-hero"} -->
13
+ <div class="wp-block-group alignfull home-carousel-hero">
14
+
15
+ <!-- wp:pattern {"slug":"iop/carousel"} /-->
16
+
17
+ <!-- wp:group { "align": "full", "style": { "color": { "background": "#9deaaa" }, "spacing": { "blockGap": "0", "margin": { "top": "0", "bottom": "0" } } }, "layout": { "type": "constrained" }
18
+ } -->
19
+ <div class="wp-block-group alignfull has-background" style="background-color:#9deaaa;margin-top:0;margin-bottom:0">
20
+
21
+ <!-- wp:pattern {"slug":"iop/home-news-panel"} /-->
22
+
23
+ </div>
24
+
25
+ <!-- /wp:group -->
26
+
27
+ </div>
28
+ <!-- /wp:group -->
@@ -0,0 +1,102 @@
1
+ //@ts-check
2
+
3
+ import { describe, expect, test } from "vitest";
4
+
5
+ import { readFile } from "node:fs/promises";
6
+
7
+ import {
8
+ formatWpCommentJson,
9
+ formatAllWpComments,
10
+ normalizeCommentTagSpacing,
11
+ } from "../bin/format-wp-block-pattern.js";
12
+
13
+ describe("Format JSON in WP Block comments", () => {
14
+ test("Pass through wp:* comments with no JSON", async () => {
15
+ const input = "<!-- wp:post-date /-->";
16
+ const expected = "<!-- wp:post-date /-->";
17
+
18
+ const actual = formatWpCommentJson(input);
19
+ expect(actual).toBe(expected);
20
+ });
21
+
22
+ test("Return short JSON for simple wp:* comments", async () => {
23
+ const input = '<!-- wp:group { "align": "full"} -->';
24
+ const expected = '<!-- wp:group {"align":"full"} -->';
25
+
26
+ const actual = formatWpCommentJson(input);
27
+ expect(actual).toBe(expected);
28
+ });
29
+
30
+ test("Return cleaned short JSON for simple wp:* comments", async () => {
31
+ const input = '<!-- wp:group {\n "align": "full"\n} -->';
32
+ const expected = '<!-- wp:group {"align":"full"} -->';
33
+
34
+ const actual = formatWpCommentJson(input);
35
+ expect(actual).toBe(expected);
36
+ });
37
+
38
+ test("Return short JSON for two-key wp:* comments", async () => {
39
+ const input = '<!-- wp:group { "align": "full", "className": "group-name"} -->';
40
+ const expected = '<!-- wp:group {"align":"full","className":"group-name"} -->';
41
+
42
+ const actual = formatWpCommentJson(input);
43
+ expect(actual).toBe(expected);
44
+ });
45
+
46
+ test("Prettyprint three-key wp:* comments", async () => {
47
+ const input = '<!-- wp:group { "align": "full","isLink":true, "className": "group-name"} -->';
48
+ const expected = '<!-- wp:group {\n "align": "full",\n "isLink": true,\n "className": "group-name"\n} -->';
49
+
50
+ const actual = formatWpCommentJson(input);
51
+ expect(actual).toBe(expected);
52
+ });
53
+
54
+ test("Prettyprint long JSON in wp:* comments", async () => {
55
+ const input =
56
+ '<!-- wp:group {"className":"group-name","layout":{"type":"constrained"}} -->';
57
+ const expected =
58
+ '<!-- wp:group {\n "className": "group-name",\n "layout": {\n "type": "constrained"\n }\n} -->';
59
+
60
+ const actual = formatWpCommentJson(input);
61
+ expect(actual).toBe(expected);
62
+ });
63
+
64
+ test("Throw error on malformed JSON", async () => {
65
+ const input = '<!-- wp:group {"just-a-key"} -->';
66
+
67
+ expect(() => formatWpCommentJson(input)).toThrow();
68
+ });
69
+ });
70
+
71
+ describe("Format WP Block Patterns", () => {
72
+ test("Format JSON in all comments", async () => {
73
+ const input = (
74
+ await readFile(
75
+ "./test/fixtures/format-wp-block-pattern/basic-pattern.php",
76
+ )
77
+ ).toString();
78
+
79
+ const actual = formatAllWpComments(input);
80
+ expect(actual).toMatch(/"layout":\s+\{\n\s+"type": "constrained"\n\s+\}/);
81
+ expect(actual).toMatch(/"align":"full"/);
82
+ expect(actual).toMatch(/\s+"align": "full",/);
83
+ // console.log(actual);
84
+ });
85
+ });
86
+
87
+
88
+ describe("Normalize whitespace", () => {
89
+ test("Fix Comment tag spacing", async () => {
90
+ const input = (
91
+ await readFile(
92
+ "./test/fixtures/format-wp-block-pattern/basic-pattern.php",
93
+ )
94
+ ).toString();
95
+
96
+ const actual = normalizeCommentTagSpacing(input);
97
+ expect(actual).toMatch(/<\/div>\n<!-- \/wp:group -->/);
98
+ });
99
+ });
100
+
101
+
102
+ // {"align":"full","className":"group-name"}