@ideasonpurpose/build-tools-wordpress 2.6.2 → 2.6.5

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.
@@ -15,10 +15,10 @@ jobs:
15
15
  runs-on: ubuntu-24.04
16
16
  steps:
17
17
  # https://github.com/marketplace/actions/checkout
18
- - uses: actions/checkout@v4
18
+ - uses: actions/checkout@v6
19
19
 
20
20
  # https://github.com/actions/setup-node
21
- - uses: actions/setup-node@v4
21
+ - uses: actions/setup-node@v6
22
22
  with:
23
23
  node-version: '24'
24
24
  registry-url: 'https://registry.npmjs.org'
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ 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.4
8
+
9
+ > 5 April 2026
10
+
11
+ - chore: update actions versions in npm-publish workflow and bump version-everything dependency
12
+ - feat: add new formatting functions for handling list elements and normalize newlines
13
+
14
+ #### v2.6.3
15
+
16
+ > 4 April 2026
17
+
18
+ - bump deps
19
+ - feat: add formatter for WordPress block pattern PHP files and corresponding tests
20
+
21
+ #### v2.6.2
22
+
23
+ > 23 March 2026
24
+
25
+ - feat: add error handling for WebSocket proxy requests
26
+
7
27
  #### v2.6.1
8
28
 
9
29
  > 23 March 2026
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ideasonpurpose/build-tools-wordpress
2
2
 
3
- #### Version 2.6.2
3
+ #### Version 2.6.5
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,194 @@
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:list-item",
88
+ "wp:media-text",
89
+ "wp:paragraph",
90
+ "wp:pattern",
91
+ "wp:pullquote",
92
+ "wp:quote",
93
+ ];
94
+
95
+ containerBlocks.forEach((block) => {
96
+ const openingTagRegex = new RegExp(`(<!--\\s*${block}[^>]*-->)`, "g");
97
+ const closingTagRegex = new RegExp(`>\\s*(<!--\\s*/${block}\\s*-->)`, "g");
98
+
99
+ // Ensure one blank line before opening tag
100
+ newContent = newContent.replace(
101
+ openingTagRegex,
102
+ /** @param {string} match @param {string} p1 */ (match, p1) => `\n${p1}`,
103
+ );
104
+
105
+ // Ensure one blank line after closing tag
106
+ newContent = newContent.replace(
107
+ closingTagRegex,
108
+ /** @param {string} match @param {string} p1 */ (match, p1) =>
109
+ `>\n${p1}\n`,
110
+ );
111
+ });
112
+ return newContent;
113
+ }
114
+
115
+ /**
116
+ * Remove extra blank lines (more than 2) and trim leading/trailing whitespace
117
+ * @param {string} content
118
+ * @returns {string}
119
+ */
120
+ export function normalizeNewlines(content) {
121
+ return (
122
+ content
123
+ .replace(/^[ \t]+$/gm, "")
124
+ .replace(/\n{3,}/g, "\n\n")
125
+ .trim() + "\n"
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Special handling of space inside <li> elements
131
+ * @param {string} content
132
+ * @returns {string}
133
+ */
134
+ export function trimInsideListElements(content) {
135
+ return content
136
+ .replace(/\s*<!-- wp:list /g, "<!-- wp:list ")
137
+ .replace(/>\s*<!-- wp:list /g, ">\n\n<!-- wp:list ")
138
+ .replace(/>\s*<!-- wp:list-item /g, ">\n\n<!-- wp:list-item ")
139
+ .replace(/<!-- \/wp:list-item -->\s*</g, "<!-- \/wp:list-item -->\n\n<")
140
+ .replace(/<li>\s*/g, "<li>")
141
+ .replace(/\s*<\/li>/g, "</li>");
142
+ }
143
+
144
+ /**
145
+ * @param {string} content
146
+ * @returns {Promise<string>}
147
+ */
148
+ export function formatWithPrettier(content) {
149
+ return prettier.format(content, {
150
+ parser: "html",
151
+ tabWidth: 2,
152
+ useTabs: false,
153
+ printWidth: 80,
154
+ htmlWhitespaceSensitivity: "css",
155
+ });
156
+ }
157
+
158
+ /**
159
+ * @param {String} filepath
160
+ */
161
+ async function formatWPBlockPattern(filepath) {
162
+ try {
163
+ const startTime = process.hrtime.bigint();
164
+ const rawFile = await readFile(filepath, "utf8");
165
+
166
+ const formatters = [
167
+ formatWithPrettier,
168
+ normalizeCommentTagSpacing,
169
+ formatAllWpComments,
170
+ trimInsideListElements,
171
+ normalizeNewlines,
172
+ ];
173
+
174
+ const outputHtml = await formatters.reduce(
175
+ async (acc, fn) => fn(await acc),
176
+ Promise.resolve(rawFile),
177
+ );
178
+
179
+ await writeFile(filepath, outputHtml, "utf8");
180
+ const endTime = process.hrtime.bigint();
181
+ const duration = Number(endTime - startTime);
182
+
183
+ console.log(`${basename(filepath)} ${(duration / 1e6).toFixed(2)}ms`);
184
+ } catch (error) {
185
+ console.error("Error:", error);
186
+ }
187
+ }
188
+
189
+ if (process.argv[2]) {
190
+ const fullPath = resolve(process.argv[2]);
191
+ formatWPBlockPattern(fullPath);
192
+ } else {
193
+ console.error("Error: A filepath is required.");
194
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ideasonpurpose/build-tools-wordpress",
3
- "version": "2.6.2",
3
+ "version": "2.6.5",
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
- "version-everything": "^0.11.4",
90
+ "svgo-loader": "^5.0.0",
91
+ "version-everything": "^0.12.2",
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,114 @@
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
+ trimInsideListElements,
12
+ } from "../bin/format-wp-block-pattern.js";
13
+
14
+ describe("Format JSON in WP Block comments", () => {
15
+ test("Pass through wp:* comments with no JSON", async () => {
16
+ const input = "<!-- wp:post-date /-->";
17
+ const expected = "<!-- wp:post-date /-->";
18
+
19
+ const actual = formatWpCommentJson(input);
20
+ expect(actual).toBe(expected);
21
+ });
22
+
23
+ test("Return short JSON for simple wp:* comments", async () => {
24
+ const input = '<!-- wp:group { "align": "full"} -->';
25
+ const expected = '<!-- wp:group {"align":"full"} -->';
26
+
27
+ const actual = formatWpCommentJson(input);
28
+ expect(actual).toBe(expected);
29
+ });
30
+
31
+ test("Return cleaned short JSON for simple wp:* comments", async () => {
32
+ const input = '<!-- wp:group {\n "align": "full"\n} -->';
33
+ const expected = '<!-- wp:group {"align":"full"} -->';
34
+
35
+ const actual = formatWpCommentJson(input);
36
+ expect(actual).toBe(expected);
37
+ });
38
+
39
+ test("Return short JSON for two-key wp:* comments", async () => {
40
+ const input = '<!-- wp:group { "align": "full", "className": "group-name"} -->';
41
+ const expected = '<!-- wp:group {"align":"full","className":"group-name"} -->';
42
+
43
+ const actual = formatWpCommentJson(input);
44
+ expect(actual).toBe(expected);
45
+ });
46
+
47
+ test("Prettyprint three-key wp:* comments", async () => {
48
+ const input = '<!-- wp:group { "align": "full","isLink":true, "className": "group-name"} -->';
49
+ const expected = '<!-- wp:group {\n "align": "full",\n "isLink": true,\n "className": "group-name"\n} -->';
50
+
51
+ const actual = formatWpCommentJson(input);
52
+ expect(actual).toBe(expected);
53
+ });
54
+
55
+ test("Prettyprint long JSON in wp:* comments", async () => {
56
+ const input =
57
+ '<!-- wp:group {"className":"group-name","layout":{"type":"constrained"}} -->';
58
+ const expected =
59
+ '<!-- wp:group {\n "className": "group-name",\n "layout": {\n "type": "constrained"\n }\n} -->';
60
+
61
+ const actual = formatWpCommentJson(input);
62
+ expect(actual).toBe(expected);
63
+ });
64
+
65
+ test("Throw error on malformed JSON", async () => {
66
+ const input = '<!-- wp:group {"just-a-key"} -->';
67
+
68
+ expect(() => formatWpCommentJson(input)).toThrow();
69
+ });
70
+ });
71
+
72
+ describe("Format WP Block Patterns", () => {
73
+ test("Format JSON in all comments", async () => {
74
+ const input = (
75
+ await readFile(
76
+ "./test/fixtures/format-wp-block-pattern/basic-pattern.php",
77
+ )
78
+ ).toString();
79
+
80
+ const actual = formatAllWpComments(input);
81
+ expect(actual).toMatch(/"layout":\s+\{\n\s+"type": "constrained"\n\s+\}/);
82
+ expect(actual).toMatch(/"align":"full"/);
83
+ expect(actual).toMatch(/\s+"align": "full",/);
84
+ // console.log(actual);
85
+ });
86
+ });
87
+
88
+
89
+ describe("Normalize whitespace", () => {
90
+ test("Fix Comment tag spacing", async () => {
91
+ const input = (
92
+ await readFile(
93
+ "./test/fixtures/format-wp-block-pattern/basic-pattern.php",
94
+ )
95
+ ).toString();
96
+
97
+ const actual = normalizeCommentTagSpacing(input);
98
+ expect(actual).toMatch(/<\/div>\n<!-- \/wp:group -->/);
99
+ });
100
+
101
+ test("List handling", async () => {
102
+ const input = (
103
+ await readFile(
104
+ "./test/fixtures/format-wp-block-pattern/nested-list.php",
105
+ )
106
+ ).toString();
107
+
108
+ const actual = trimInsideListElements(input);
109
+ expect(actual).toMatch(/<\/div>\n<!-- \/wp:group -->/);
110
+ });
111
+ });
112
+
113
+
114
+ // {"align":"full","className":"group-name"}