@ideasonpurpose/build-tools-wordpress 2.7.0 → 2.8.1

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.
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  steps:
15
15
  # https://github.com/marketplace/actions/checkout
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v6
17
17
 
18
18
  - name: Set TAG environment variable
19
19
  run: |
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ 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.8.0
8
+
9
+ > 3 June 2026
10
+
11
+ - bump deps
12
+ - refactor format functions and improve tests
13
+ - boilerplate and README SVGo info
14
+
15
+ #### v2.7.0
16
+
17
+ > 6 May 2026
18
+
19
+ - bump deps, migrate some tooling
20
+ - feat: add AI Coding Assistant guidelines documentation
21
+
7
22
  #### v2.6.6
8
23
 
9
24
  > 27 April 2026
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ideasonpurpose/build-tools-wordpress
2
2
 
3
- #### Version 2.7.0
3
+ #### Version 2.8.1
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)
@@ -41,6 +41,10 @@ Webpack handles SVG files differently based on import context:
41
41
 
42
42
  Data URIs are generated as `data:image/svg+xml,<url-encoded-content>` using more efficient URL-encoding, not base64.
43
43
 
44
+ ### SVG Optimization
45
+
46
+ SVGO is included along with our preferred [svgo.config.mjs][]. Run this with `npx svgo <file-to-optimize.svg>`
47
+
44
48
  ## Experimental formatting helpers
45
49
 
46
50
  This package includes two experimental formatting scripts:
@@ -94,6 +98,7 @@ A GitHub action will auto-publish version-tagged releases to npm. In order to pu
94
98
 
95
99
  #### Brought to you by IOP
96
100
 
97
- <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-white-on-black-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
101
+ | <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-white-on-black-88px.png" height="44" align="top" alt="IOP Logo"></a> | This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>. |
102
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
98
103
 
99
104
  <!-- END IOP CREDIT BLURB -->
@@ -141,7 +141,7 @@ export function unTokenizeHTML(tokenizedHTML, phpCodeBlocks) {
141
141
  *
142
142
  * @param {string} filepath - The path to the file to format (must be a valid file path).
143
143
  */
144
- async function formatHTMLThenPHP(filepath) {
144
+ export async function formatHTMLThenPHP(filepath) {
145
145
  try {
146
146
  const startTime = process.hrtime.bigint();
147
147
  const rawFile = await readFile(filepath, "utf8");
@@ -175,9 +175,11 @@ async function formatHTMLThenPHP(filepath) {
175
175
  }
176
176
  }
177
177
 
178
- if (process.argv[2]) {
179
- const fullPath = resolve(process.argv[2]);
180
- formatHTMLThenPHP(fullPath);
181
- } else {
182
- console.error("Error: A filepath is required.");
178
+ export async function main(filepath = process.argv[2]) {
179
+ if (!filepath) {
180
+ console.error("Error: A filepath is required.");
181
+ return;
182
+ }
183
+ await formatHTMLThenPHP(resolve(filepath));
183
184
  }
185
+ if (import.meta.url === `file://${process.argv[1]}`) main();
@@ -108,6 +108,16 @@ export function normalizeCommentTagSpacing(content) {
108
108
  /** @param {string} match @param {string} p1 */ (match, p1) =>
109
109
  `>\n${p1}\n`,
110
110
  );
111
+
112
+ if (block === "wp:paragraph") {
113
+ newContent = newContent
114
+ .replace(/\n\s*<p>\s*/g, "\n<p>")
115
+ .replace(/\s*<\/p>/g, "</p>")
116
+ .replace(/<p>[\s\S]*?<\/p>/g, (match) =>
117
+ match.replace(/\s+/g, " ").trim(),
118
+ );
119
+ newContent = newContent.replace(/\n{3,}/g, "\n\n").replace(/\s+$/, "\n");
120
+ }
111
121
  });
112
122
  return newContent;
113
123
  }
@@ -169,38 +179,36 @@ export function formatWithPrettier(content) {
169
179
  /**
170
180
  * @param {String} filepath
171
181
  */
172
- async function formatWPBlockPattern(filepath) {
173
- try {
174
- const startTime = process.hrtime.bigint();
175
- const rawFile = await readFile(filepath, "utf8");
176
-
177
- const formatters = [
178
- formatWithPrettier,
179
- normalizeCommentTagSpacing,
180
- formatAllWpComments,
181
- trimInsideListElements,
182
- trimInsideHeadings,
183
- normalizeNewlines,
184
- ];
185
-
186
- const outputHtml = await formatters.reduce(
187
- async (acc, fn) => fn(await acc),
188
- Promise.resolve(rawFile),
189
- );
182
+ export async function formatWPBlockPattern(filepath) {
183
+ const startTime = process.hrtime.bigint();
184
+ const rawFile = await readFile(filepath, "utf8");
185
+
186
+ const formatters = [
187
+ formatWithPrettier,
188
+ normalizeCommentTagSpacing,
189
+ formatAllWpComments,
190
+ trimInsideListElements,
191
+ trimInsideHeadings,
192
+ normalizeNewlines,
193
+ ];
190
194
 
191
- await writeFile(filepath, outputHtml, "utf8");
192
- const endTime = process.hrtime.bigint();
193
- const duration = Number(endTime - startTime);
195
+ const outputHtml = await formatters.reduce(
196
+ async (acc, fn) => fn(await acc),
197
+ Promise.resolve(rawFile),
198
+ );
194
199
 
195
- console.log(`${basename(filepath)} ${(duration / 1e6).toFixed(2)}ms`);
196
- } catch (error) {
197
- console.error("Error:", error);
198
- }
200
+ await writeFile(filepath, outputHtml, "utf8");
201
+ const endTime = process.hrtime.bigint();
202
+ const duration = Number(endTime - startTime);
203
+
204
+ console.log(`${basename(filepath)} ${(duration / 1e6).toFixed(2)}ms`);
199
205
  }
200
206
 
201
- if (process.argv[2]) {
202
- const fullPath = resolve(process.argv[2]);
203
- formatWPBlockPattern(fullPath);
204
- } else {
205
- console.error("Error: A filepath is required.");
207
+ export async function main(filepath = process.argv[2]) {
208
+ if (!filepath) {
209
+ console.error("Error: A filepath is required.");
210
+ return;
211
+ }
212
+ await formatWPBlockPattern(resolve(filepath));
206
213
  }
214
+ if (import.meta.url === `file://${process.argv[1]}`) main();
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "ideasonpurpose/iop-theme",
3
+ "description": "Block-based hybrid theme for the Stone Rabbit NYC website.",
4
+ "authors": [
5
+ {
6
+ "name": "Ideas On Purpose",
7
+ "homepage": "https://www.ideasonpurpose.com"
8
+ }
9
+ ],
10
+ "config": {
11
+ "optimize-autoloader": true,
12
+ "sort-packages": true,
13
+ "vendor-dir": "wp-content/themes/iop-theme/vendor",
14
+ "platform": {
15
+ "php": "8.4",
16
+ "ext-intl": "0"
17
+ }
18
+ },
19
+ "autoload": {
20
+ "psr-4": {
21
+ "IdeasOnPurpose\\": [
22
+ "wp-content/themes/iop-theme/lib"
23
+ ]
24
+ }
25
+ },
26
+ "require": {
27
+ "ideasonpurpose/wp-admin-separators": "1.0.8",
28
+ "ideasonpurpose/wp-google-analytics": "^1.1.0",
29
+ "ideasonpurpose/wp-svg-lib": "^3.2.0",
30
+ "ideasonpurpose/wp-theme-i18n": "dev-main",
31
+ "ideasonpurpose/wp-theme-init": "^2.20.5"
32
+ },
33
+ "repositories": [
34
+ {
35
+ "type": "vcs",
36
+ "url": "https://github.com/ideasonpurpose/wp-theme-i18n"
37
+ }
38
+ ],
39
+ "require-dev": {
40
+ "php-stubs/acf-pro-stubs": "^6.5"
41
+ }
42
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "iop-theme",
3
+ "version": "0.1.10",
4
+ "private": true,
5
+ "description": "Theme for the CLIENT_NAME website",
6
+ "license": "UNLICENSED",
7
+ "author": "Ideas On Purpose (https://www.ideasonpurpose.com/)",
8
+ "contributors": [
9
+ "Joe Maller <joe@ideasonpurpose.com>",
10
+ "Codrin Pavel <codrin@ideasonpurpose.com>"
11
+ ],
12
+ "type": "module",
13
+ "main": "index.js",
14
+ "scripts": {
15
+ "_bootstrap:composer": "npm run composer:install",
16
+ "_bootstrap:npm": "npm ci",
17
+ "prebootstrap": "npm run permissions:repair",
18
+ "bootstrap": "npm run _bootstrap:npm && npm run _bootstrap:composer && npm run theme:activate",
19
+ "postbootstrap": "echo && echo ' 🚀' && echo ' ✨ All set!' && echo '🌏 Run this to get started:' && chalk bold yellow ' npm run start' && echo",
20
+ "prebuild": "npm run clean",
21
+ "build": "NODE_ENV=production webpack",
22
+ "postbuild": "npm run zip",
23
+ "clean": "npx rimraf -g 'wp-content/themes/iop*/dist'",
24
+ "composer": "npm run composer:install",
25
+ "composer:install": "docker compose run --rm composer",
26
+ "composer:require": "docker compose run --rm composer require",
27
+ "composer:update": "docker compose run --rm composer update",
28
+ "db:admin": "docker compose run --rm --service-ports phpmyadmin",
29
+ "db:dump": "docker compose run --rm db-dump",
30
+ "db:pull": "npm run pull:db",
31
+ "db:reload": "docker compose run --rm db-reload",
32
+ "postdb:reload": "npm run theme:activate",
33
+ "dev": "npm run start",
34
+ "log:wordpress": "npm run logs:wordpress",
35
+ "logs:wordpress": "docker compose exec wordpress tail -f /var/log/wordpress/debug.log",
36
+ "mysqldump": "npm run db:dump",
37
+ "permissions:repair": "docker compose run --rm repair-permissions",
38
+ "phpmyadmin": "npm run db:admin",
39
+ "preproject:refresh": "docker compose pull refresh",
40
+ "project:refresh": "docker compose run --rm refresh",
41
+ "postproject:refresh": "docker compose pull wordpress",
42
+ "pull": "npm run pull:db && npm run pull:plugins && npm run pull:uploads",
43
+ "prepull:db": "npm run db:dump",
44
+ "pull:db": "docker compose run --rm pull database",
45
+ "postpull:db": "npm run db:reload",
46
+ "pull:plugins": "docker compose run --rm pull plugins",
47
+ "pull:uploads": "docker compose run --rm pull uploads",
48
+ "pull:uploads-all": "docker compose run --rm pull uploads all",
49
+ "prestart": "npm run clean && docker compose run --rm --service-ports -d wordpress && iop-build-port-reporter",
50
+ "start": "webpack serve",
51
+ "poststart": "npm run stop",
52
+ "stop": "docker compose down --remove-orphans",
53
+ "test": "playwright test --ui",
54
+ "theme:activate": "docker compose run --rm theme-activate",
55
+ "version": "version-everything && git add -u",
56
+ "postversion": "npm run build",
57
+ "webgrind": "docker compose run --rm --service-ports webgrind",
58
+ "wp-cli": "docker compose exec -u wp wordpress",
59
+ "zip": "iop-build-zip-archive"
60
+ },
61
+ "prettier": "@ideasonpurpose/prettier-config",
62
+ "stylelint": {
63
+ "extends": "@ideasonpurpose/stylelint-config"
64
+ },
65
+ "devDependencies": {
66
+ "@ideasonpurpose/build-tools-wordpress": "^2.5.1"
67
+ },
68
+ "version-everything": {
69
+ "files": [
70
+ "README.md",
71
+ "wp-content/themes/iop-theme/style.css"
72
+ ]
73
+ }
74
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * This is primarily intended to work with the SVGO VS Code Plugin, and to
3
+ * save trips out to SVGOMG.
4
+ *
5
+ * This config allows for immediate optimization of SVGs in the editor
6
+ *
7
+ *
8
+ * @links
9
+ * - SVGO: https://github.com/svg/svgo
10
+ * - SVGO preset-default docs: https://svgo.dev/docs/preset-default
11
+ * - VS Code Plugin: https://marketplace.visualstudio.com/items?itemName=1000ch.svgo
12
+ * - SVGOMG: https://jakearchibald.github.io/svgomg/
13
+ */
14
+
15
+ export default {
16
+ js2svg: {
17
+ indent: 4, // number
18
+ pretty: true, // boolean
19
+ },
20
+
21
+ plugins: [
22
+ {
23
+ name: "preset-default",
24
+ params: {
25
+ overrides: {
26
+ cleanupIds: false,
27
+ },
28
+ },
29
+ },
30
+ "removeDimensions",
31
+ ],
32
+ };
@@ -0,0 +1,7 @@
1
+ include:
2
+ - path: ./node_modules/@ideasonpurpose/build-tools-wordpress/tooling/docker-compose.yml
3
+ # Set project_directory to force volumes to use this file as the relative root for included docker-compose files
4
+ project_directory: .
5
+
6
+ # Uncomment this line to use a local dev build of the WordPress Docker image.
7
+ # services: { wordpress: { image: "ideasonpurpose/wordpress:dev" } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ideasonpurpose/build-tools-wordpress",
3
- "version": "2.7.0",
3
+ "version": "2.8.1",
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": {
@@ -21,9 +21,9 @@
21
21
  "bin": {
22
22
  "iop-build-port-reporter": "./bin/port-reporter.js",
23
23
  "iop-build-zip-archive": "./bin/zip.js",
24
- "iop-project-init": "./bin/project-init.js",
25
24
  "iop-html-php-prettier": "./bin/format-php-prettier.js",
26
- "iop-format-wp-block-pattern": "./bin/format-wp-block-pattern.js"
25
+ "iop-format-wp-block-pattern": "./bin/format-wp-block-pattern.js",
26
+ "iop-project-refresh": "./bin/refresh.js"
27
27
  },
28
28
  "directories": {
29
29
  "lib": "lib"
@@ -35,18 +35,18 @@
35
35
  "prettier": "@ideasonpurpose/prettier-config",
36
36
  "dependencies": {
37
37
  "@ideasonpurpose/prettier-config": "^1.0.1",
38
- "@ideasonpurpose/stylelint-config": "^1.1.4",
38
+ "@ideasonpurpose/stylelint-config": "^1.1.6",
39
39
  "@prettier/plugin-php": "^0.25.0",
40
- "@rollup/plugin-commonjs": "^29.0.2",
40
+ "@rollup/plugin-commonjs": "^29.0.3",
41
41
  "@rollup/plugin-json": "^6.1.0",
42
42
  "@rollup/plugin-node-resolve": "^16.0.3",
43
43
  "@svgr/webpack": "^8.1.0",
44
- "@wordpress/dependency-extraction-webpack-plugin": "^6.45.0",
44
+ "@wordpress/dependency-extraction-webpack-plugin": "^6.47.0",
45
45
  "ansi-html": "^0.0.9",
46
- "archiver": "^7.0.1",
46
+ "archiver": "^8.0.0",
47
47
  "autoprefixer": "^10.5.0",
48
48
  "babel-loader": "^10.1.1",
49
- "caniuse-lite": "^1.0.30001792",
49
+ "caniuse-lite": "^1.0.30001793",
50
50
  "chalk": "^5.6.2",
51
51
  "chalk-cli": "^6.0.0",
52
52
  "classnames": "^2.5.1",
@@ -54,10 +54,10 @@
54
54
  "copy-webpack-plugin": "^14.0.0",
55
55
  "cosmiconfig": "^9.0.1",
56
56
  "css-loader": "^7.1.4",
57
- "cssnano": "^8.0.0",
57
+ "cssnano": "^8.0.1",
58
58
  "dotenv": "^17.4.2",
59
59
  "esbuild-loader": "^4.4.3",
60
- "eslint": "^10.3.0",
60
+ "eslint": "^10.4.1",
61
61
  "filesize": "^11.0.17",
62
62
  "fs-extra": "^11.3.5",
63
63
  "globby": "^16.2.0",
@@ -69,7 +69,7 @@
69
69
  "lodash": "^4.18.1",
70
70
  "mini-css-extract-plugin": "^2.10.2",
71
71
  "ora": "^9.4.0",
72
- "postcss": "^8.5.14",
72
+ "postcss": "^8.5.15",
73
73
  "postcss-loader": "^8.2.1",
74
74
  "postcss-scss": "^4.0.9",
75
75
  "prettier": "^3.8.3",
@@ -77,28 +77,28 @@
77
77
  "read-package-up": "^12.0.0",
78
78
  "replacestream": "^4.0.3",
79
79
  "rimraf": "^6.1.3",
80
- "sass-embedded": "^1.99.0",
81
- "sass-loader": "^16.0.7",
82
- "semver": "^7.7.4",
80
+ "sass-embedded": "^1.100.0",
81
+ "sass-loader": "^17.0.0",
82
+ "semver": "^7.8.1",
83
83
  "sharp": "^0.34.5",
84
- "sort-package-json": "^3.6.1",
84
+ "sort-package-json": "^3.7.0",
85
85
  "string-length": "^7.0.1",
86
86
  "style-loader": "^4.0.0",
87
87
  "svgo": "^4.0.1",
88
88
  "svgo-loader": "^5.0.0",
89
89
  "version-everything": "^0.12.2",
90
- "webpack": "^5.106.2",
90
+ "webpack": "^5.107.2",
91
91
  "webpack-bundle-analyzer": "^5.3.0",
92
92
  "webpack-dev-middleware": "^8.0.3",
93
- "webpack-dev-server": "^5.2.3",
93
+ "webpack-dev-server": "^5.2.4",
94
94
  "webpack-manifest-plugin": "^6.0.1"
95
95
  },
96
96
  "peerDependencies": {
97
97
  "webpack-cli": "^6.0.1"
98
98
  },
99
99
  "devDependencies": {
100
- "@vitest/coverage-v8": "^4.1.5",
101
- "vitest": "^4.1.5"
100
+ "@vitest/coverage-v8": "^4.1.8",
101
+ "vitest": "^4.1.8"
102
102
  },
103
103
  "version-everything": {
104
104
  "files": [
@@ -1,23 +1,19 @@
1
1
  //@ts-check
2
2
 
3
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
-
5
- import fs from "fs";
6
- import dns from "dns";
3
+ import { afterEach, beforeEach, expect, test, vi } from "vitest";
7
4
 
8
5
  import { EventEmitter } from "events";
9
6
 
10
7
  import { devserverProxy } from "../lib/devserver-proxy.js";
11
8
  import { findLocalPort } from "../lib/find-local-docker-port.js";
12
9
 
13
- const expected = "11.22.33.44";
14
-
15
10
  const expectedPort = 56789;
16
11
  const expectedHostName = "stella.dog";
17
12
  const expectedTarget = `http://${expectedHostName}:${expectedPort}`;
18
13
 
19
14
  vi.mock("../lib/find-local-docker-port.js");
20
15
 
16
+ /** @type {Promise<{port:number,hostname:string}>} */
21
17
  let localPort;
22
18
 
23
19
  beforeEach(() => {
@@ -33,80 +29,6 @@ beforeEach(() => {
33
29
  afterEach(() => {
34
30
  vi.restoreAllMocks();
35
31
  });
36
- // beforeEach(() => {
37
- // jest.spyOn(dns, "promises", "get").mockImplementation(() => {
38
- // return { resolve: async () => [expected] };
39
- // });
40
-
41
- // console.log = jest.fn();
42
- // });
43
-
44
- // afterEach(() => {
45
- // jest.clearAllMocks();
46
- // jest.resetAllMocks();
47
- // // mockResolve.mockResolvedValue(["11.22.33.44"]);
48
- // });
49
-
50
- // disabled because we're now mocking the library
51
- // test.skip("dns works normally", async () => {
52
- // const actual = await dns.promises.resolve("apple.com");
53
- // expect(actual[0]).toMatch(/^17\.253/);
54
- // });
55
-
56
- // test("mock dns.promises.resolve", async () => {
57
- // const actual = await dns.promises.resolve("hello");
58
- // expect(actual).toBe(expected);
59
- // });
60
-
61
- // test("resolve from file", async () => {
62
- // const actual = await resolveFromFile("wordpress");
63
- // expect(actual).toBe(expected);
64
- // });
65
-
66
- // test("Send legacy token where there's no wordpress service", async () => {
67
- // jest.spyOn(dns, "promises", "get").mockImplementation(() => {
68
- // // console.log("ONLY ONCE");
69
- // return { resolve: () => new Promise((resolve, reject) => reject()) };
70
- // });
71
-
72
- // let proxy =
73
- // "http://devserver-proxy-token--d939bef2a41c4aa154ddb8db903ce19fff338b61";
74
-
75
- // const logSpy = jest.spyOn(console, "log");
76
- // const actual = await devserverProxy({ proxy });
77
-
78
- // expect(actual).toStrictEqual({});
79
- // // expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("ONCE"));
80
- // expect(logSpy).toHaveBeenCalledWith(
81
- // expect.any(String),
82
- // expect.stringContaining("devserver-proxy-token"),
83
- // expect.any(String)
84
- // );
85
- // });
86
-
87
- // test("prefix http onto string", async () => {
88
- // jest.spyOn(dns, "promises", "get").mockImplementation(() => {
89
- // return {
90
- // resolve: () => new Promise((resolve, reject) => resolve(["fake-url"])),
91
- // };
92
- // });
93
-
94
- // let proxy = "placeholder string";
95
- // const actual = await devserverProxy({ proxy });
96
- // expect(actual).toHaveProperty("proxy.**.target", "http://fake-url");
97
- // });
98
-
99
- // test("fail to prefix http onto string", async () => {
100
- // jest.spyOn(dns, "promises", "get").mockImplementation(() => {
101
- // return {
102
- // resolve: () => new Promise((resolve, reject) => reject()),
103
- // };
104
- // });
105
-
106
- // let proxy = "placeholder string";
107
- // const actual = await devserverProxy({ proxy });
108
- // expect(actual).toStrictEqual({});
109
- // });
110
32
 
111
33
  test("Test proxy settings", async () => {
112
34
  let proxy = true;
@@ -134,6 +56,13 @@ test("proxy is a plain string", async () => {
134
56
  expect(findLocalPort).toHaveBeenCalledWith(proxy);
135
57
  });
136
58
 
59
+ test("plain string proxy with no local port", async () => {
60
+ vi.mocked(findLocalPort).mockReturnValue(Promise.resolve(null));
61
+ const result = await devserverProxy({ proxy: "sandwich" });
62
+ expect(result.proxy).toHaveLength(1);
63
+ expect(result.proxy[0].target).toBeUndefined();
64
+ });
65
+
137
66
  test("Proxy boolean true", async () => {
138
67
  vi.mocked(findLocalPort).mockReturnValue(localPort);
139
68
  const proxy = true;
@@ -141,6 +70,13 @@ test("Proxy boolean true", async () => {
141
70
  expect(actual).toHaveProperty("target", expectedTarget);
142
71
  });
143
72
 
73
+ test("Proxy boolean true with no local port", async () => {
74
+ vi.mocked(findLocalPort).mockReturnValue(Promise.resolve(null));
75
+ const result = await devserverProxy({ proxy: true });
76
+ expect(result.proxy).toHaveLength(1);
77
+ expect(result.proxy[0].target).toBeUndefined();
78
+ });
79
+
144
80
  test("Proxy boolean false", async () => {
145
81
  const proxy = false;
146
82
  expect(await devserverProxy({ proxy })).toStrictEqual({ proxy: [] });
@@ -216,7 +152,6 @@ test("onProxyRes Handler", async () => {
216
152
  );
217
153
  });
218
154
 
219
-
220
155
  test("onProxyRes Handler passthrough", async () => {
221
156
  const config = { proxy: "http://localhost:3000" };
222
157
  const result = await devserverProxy(config);
@@ -224,9 +159,9 @@ test("onProxyRes Handler passthrough", async () => {
224
159
 
225
160
  const mockProxyRes = new EventEmitter();
226
161
  mockProxyRes.statusCode = 200;
227
- mockProxyRes.statusMessage = "OK";
228
162
  mockProxyRes.headers = {
229
163
  "content-type": "nope/nope",
164
+ "x-num": 42,
230
165
  };
231
166
  const mockReq = {
232
167
  headers: { host: "example.com" },
@@ -255,124 +190,19 @@ test("onProxyRes Handler passthrough", async () => {
255
190
  );
256
191
  });
257
192
 
193
+ test("onProxyReqWs handler", async () => {
194
+ const result = await devserverProxy({ proxy: "http://example.com" });
195
+ const onProxyReqWs = result.proxy[0].onProxyReqWs;
196
+
197
+ const socket = new EventEmitter();
198
+ const errSpy = vi.spyOn(console, "error");
258
199
 
200
+ onProxyReqWs(null, null, socket, null, null);
201
+ socket.emit("error", { code: "EPIPE" });
202
+ expect(errSpy).not.toHaveBeenCalled();
259
203
 
260
- // test("test proxy's onProxyRes handler", async () => {
261
- // let proxy = "https://example.com";
262
- // const logSpy = jest.spyOn(console, "log");
263
- // const actual = await devserverProxy({ proxy });
264
-
265
- // const route = actual.proxy["**"];
266
- // const mockProxyRes = fs.createReadStream(new URL(import.meta.url));
267
-
268
- // mockProxyRes.headers = {
269
- // headerKey: "value",
270
- // host: "example.com",
271
- // "content-type": "text/html; charset=utf-8",
272
- // };
273
- // mockProxyRes.statusCode = "statusCode";
274
- // mockProxyRes.statusMessage = "statusMessage";
275
-
276
- // const events = {};
277
- // jest.spyOn(mockProxyRes, "on").mockImplementation((event, handler) => {
278
- // events[event] = handler;
279
- // return mockProxyRes;
280
- // });
281
-
282
- // const setHeader = jest.fn();
283
- // const end = jest.fn();
284
-
285
- // const res = { setHeader, end };
286
- // const req = {
287
- // headers: { host: "req.headers.host" },
288
- // path: "path",
289
- // };
290
-
291
- // route.onProxyRes(mockProxyRes, req, res);
292
- // events.data(Buffer.from("A string with 28 characters."));
293
- // events.end();
294
-
295
- // expect(res.statusCode).toBe(mockProxyRes.statusCode);
296
- // expect(res.statusMessage).toBe(mockProxyRes.statusMessage);
297
- // expect(req.headers.host).toBe("req.headers.host");
298
- // expect(setHeader).toHaveBeenLastCalledWith("Content-Length", 28);
299
- // });
300
-
301
- // test("test proxy's onProxyRes handler onEnd passthrough", async () => {
302
- // let proxy = "https://example.com";
303
- // // const logSpy = jest.spyOn(console, "log");
304
- // const actual = await devserverProxy({ proxy });
305
-
306
- // const route = actual.proxy["**"];
307
- // const mockProxyRes = fs.createReadStream(new URL(import.meta.url));
308
-
309
- // console.log("hello");
310
- // mockProxyRes.headers = {
311
- // headerKey: "value",
312
- // host: "example.com",
313
- // "Content-Length": 123,
314
- // };
315
-
316
- // const events = {};
317
- // jest.spyOn(mockProxyRes, "on").mockImplementation((event, handler) => {
318
- // events[event] = handler;
319
- // return mockProxyRes;
320
- // });
321
-
322
- // const setHeader = jest.fn();
323
- // const end = jest.fn();
324
-
325
- // const res = { setHeader, end };
326
- // const req = {
327
- // headers: { host: "req.headers.host" },
328
- // path: "/wp-admin/fake.css",
329
- // };
330
-
331
- // route.onProxyRes(mockProxyRes, req, res);
332
- // events.end();
333
-
334
- // expect(end).toHaveBeenCalled();
335
- // });
336
-
337
- // test("proxy should rewrite http:// and http:\\/\\/", async () => {
338
- // let proxy = "wordpress";
339
-
340
- // const logSpy = jest.spyOn(console, "log");
341
- // const actual = await devserverProxy({ proxy });
342
- // const route = actual.proxy["**"];
343
- // const mockProxyRes = fs.createReadStream(new URL(import.meta.url));
344
-
345
- // mockProxyRes.headers = {
346
- // headerKey: "value",
347
- // host: "example.com",
348
- // "content-type": "text/html; charset=utf-8",
349
- // };
350
-
351
- // const events = {};
352
- // jest.spyOn(mockProxyRes, "on").mockImplementation((event, handler) => {
353
- // events[event] = handler;
354
- // return mockProxyRes;
355
- // });
356
-
357
- // const setHeader = jest.fn();
358
- // const end = jest.fn();
359
-
360
- // const res = { setHeader, end };
361
- // const req = {
362
- // headers: { host: "req.headers.host" },
363
- // path: "path",
364
- // };
365
-
366
- // route.onProxyRes(mockProxyRes, req, res);
367
- // events.data(Buffer.from("http://11.22.33.44\n"));
368
- // events.data(Buffer.from("http:\\/\\/11.22.33.44\n"));
369
- // events.end();
370
-
371
- // expect(end.mock.calls[0][0].toString("utf8")).toMatch(
372
- // /http:\/\/req.headers.host/
373
- // );
374
-
375
- // expect(end.mock.calls[0][0].toString("utf8")).toMatch(
376
- // /http:\\\/\\\/req.headers.host/
377
- // );
378
- // });
204
+ socket.emit("error", { code: "OTHER" });
205
+ expect(errSpy).toHaveBeenCalledWith("WebSocket proxy error:", {
206
+ code: "OTHER",
207
+ });
208
+ });
@@ -1,3 +1,9 @@
1
+ <?php
2
+ /**
3
+ * Title: Nested List Test
4
+ */
5
+ ?>
6
+
1
7
  <!-- wp:list -->
2
8
  <ul class="wp-block-list"><!-- wp:list-item -->
3
9
  <li> UL Item 1 </li>
@@ -1,3 +1,9 @@
1
+ <?php
2
+ /**
3
+ * Title: Nested List Test
4
+ */
5
+ ?>
6
+
1
7
  <!-- wp:list -->
2
8
  <ul class="wp-block-list">
3
9
 
@@ -0,0 +1,14 @@
1
+ <?php
2
+ /**
3
+ * Title: Paragraph Whitespace Test
4
+ */
5
+ ?>
6
+
7
+ <!-- wp:paragraph -->
8
+ <p>
9
+ There are four button styles. Default, Primary, Secondary and Text. Each
10
+ style can include an icon which can be positioned on either side by changing
11
+ the button's text-alignment. Button colors can be customized by selecting
12
+ different Text and Background colors.
13
+ </p>
14
+ <!-- /wp:paragraph -->
@@ -0,0 +1,9 @@
1
+ <?php
2
+ /**
3
+ * Title: Paragraph Whitespace Test
4
+ */
5
+ ?>
6
+
7
+ <!-- wp:paragraph -->
8
+ <p>There are four button styles. Default, Primary, Secondary and Text. Each style can include an icon which can be positioned on either side by changing the button's text-alignment. Button colors can be customized by selecting different Text and Background colors.</p>
9
+ <!-- /wp:paragraph -->
@@ -1,6 +1,13 @@
1
1
  //@ts-check
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { describe, expect, test, vi } from "vitest";
4
+ import prettier from "prettier";
5
+
6
+ vi.mock("prettier", () => ({
7
+ default: {
8
+ format: vi.fn().mockResolvedValue("<formatted/>"),
9
+ },
10
+ }));
4
11
 
5
12
  import { readFile } from "node:fs/promises";
6
13
 
@@ -9,6 +16,11 @@ import {
9
16
  formatAllWpComments,
10
17
  normalizeCommentTagSpacing,
11
18
  trimInsideListElements,
19
+ normalizeNewlines,
20
+ trimInsideHeadings,
21
+ formatWithPrettier,
22
+ formatWPBlockPattern,
23
+ main,
12
24
  } from "../bin/format-wp-block-pattern.js";
13
25
 
14
26
  describe("Format JSON in WP Block comments", () => {
@@ -106,15 +118,62 @@ describe("Normalize whitespace", () => {
106
118
  await readFile("./test/fixtures/format-wp-block-pattern/nested-list.php")
107
119
  ).toString();
108
120
 
109
- const expected = (
110
- await readFile("./test/fixtures/format-wp-block-pattern/nested-list__formatted.php")
121
+ const expected = (
122
+ await readFile(
123
+ "./test/fixtures/format-wp-block-pattern/nested-list__formatted.php",
124
+ )
111
125
  ).toString();
112
126
 
113
127
  const actual = trimInsideListElements(input);
114
- expect(actual).toMatch(/<li>UL/);
128
+ expect(actual).toMatch(/<li>UL/);
129
+
130
+ expect(actual).toBe(expected);
131
+ });
132
+
133
+ test("Whitespace handling in paragraphs", async () => {
134
+ const input = (
135
+ await readFile(
136
+ "./test/fixtures/format-wp-block-pattern/paragraph-whitespace.php",
137
+ )
138
+ ).toString();
139
+
140
+ const expected = (
141
+ await readFile(
142
+ "./test/fixtures/format-wp-block-pattern/paragraph-whitespace__formatted.php",
143
+ )
144
+ ).toString();
145
+
146
+ const actual = normalizeCommentTagSpacing(input);
147
+ expect(actual).toBe(expected);
148
+ });
149
+
150
+ test("normalizeNewlines for coverage", async () => {
151
+ const input = "Line 1\nLine 2\n \n \n\n\n\nLine 3\nLine 4";
152
+ const expected = "Line 1\nLine 2\n\nLine 3\nLine 4\n";
153
+
154
+ const actual = normalizeNewlines(input);
155
+ expect(actual).toBe(expected);
156
+ });
115
157
 
158
+ test("trimInsideHeadings for coverage", async () => {
159
+ const input = "<h2> Heading with extra spaces </h2>";
160
+ const expected = "<h2>Heading with extra spaces</h2>";
161
+
162
+ const actual = trimInsideHeadings(input);
116
163
  expect(actual).toBe(expected);
117
164
  });
165
+
166
+ test("formatWithPrettier for coverage", async () => {
167
+ const input = "<div>foo</div>";
168
+ await formatWithPrettier(input);
169
+ expect(prettier.format).toHaveBeenCalled();
170
+ });
171
+
172
+ test("main requires filepath", async () => {
173
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
174
+ await main();
175
+ expect(spy).toHaveBeenCalled();
176
+ spy.mockRestore();
177
+ });
118
178
  });
119
179
 
120
- // {"align":"full","className":"group-name"}
@@ -2,7 +2,7 @@ services:
2
2
  # Primary database for the local WordPress development environment.
3
3
  # Image from: https://hub.docker.com/_/mysql
4
4
  db:
5
- image: &db_img mysql:8.4
5
+ image: &db_img mysql:9.7
6
6
  restart: always
7
7
  volumes:
8
8
  - db_data:/var/lib/mysql
@@ -31,13 +31,13 @@ services:
31
31
  max-file: "3"
32
32
 
33
33
  # Ideas On Purpose's local WordPress development environment.
34
- # Update image version with `docker run ideasonpurpose/wordpress init`
34
+ # Update image version with `docker run ideasonpurpose/wordpress init`
35
35
  # Project info: https://github.com/ideasonpurpose/docker-wordpress-dev
36
36
  wordpress:
37
37
  depends_on:
38
38
  - db
39
39
  # image: &wp_img ideasonpurpose/wordpress:dev
40
- image: &wp_img ideasonpurpose/wordpress:6.9.4
40
+ image: &wp_img ideasonpurpose/wordpress:7.0
41
41
  restart: always
42
42
  volumes:
43
43
  - wp_data:/var/www/html
@@ -82,7 +82,7 @@ services:
82
82
  # Utility service for running composer commands
83
83
  # Image from: https://hub.docker.com/_/composer
84
84
  composer:
85
- image: composer:2.9
85
+ image: composer:2.10
86
86
  profiles: ["utility"]
87
87
  user: "${UID:-1000}:${GID:-1000}"
88
88
  environment:
@@ -173,7 +173,7 @@ services:
173
173
  command: |
174
174
  bash -c 'for i in {1..10}
175
175
  do echo -e "⏳ \033[33mWaiting for DB server...\033[0m" &&
176
- mysql --ssl-mode=DISABLED -s -h db -e "exit" && break || sleep 3
176
+ mysql --ssl-mode=DISABLED -s -h db -e "SELECT 1" && break || sleep 3
177
177
  done &&
178
178
  sleep 2 &&
179
179
  echo -e "✔️ \033[32mConnected to DB\033[0m" &&
@@ -197,7 +197,7 @@ services:
197
197
  command: |
198
198
  bash -c 'for i in {1..10}
199
199
  do echo -e "⏳ \033[33mWaiting for DB server...\033[0m" &&
200
- mysql --ssl-mode=DISABLED -s -h db -e "exit" && break || sleep 3
200
+ mysql --ssl-mode=DISABLED -s -h db -e "SELECT 1" && break || sleep 3
201
201
  done &&
202
202
  sleep 2 &&
203
203
  echo -e "✔️ \033[32mConnected to DB\033[0m" &&
@@ -205,7 +205,7 @@ services:
205
205
  mysqladmin --ssl-mode=DISABLED -hdb -v -f create $${MYSQL_DATABASE} &&
206
206
  echo Database \"$${MYSQL_DATABASE}\" created &&
207
207
  echo Reloading database from dumpfile &&
208
- mysql --ssl-mode=DISABLED -hdb $${MYSQL_DATABASE} < $$(ls /usr/src/dumpfiles/*.sql | tail -n1)'
208
+ mysql --ssl-mode=DISABLED -hdb $${MYSQL_DATABASE} < $$(ls -t /usr/src/dumpfiles/*.sql | head -n1)'
209
209
 
210
210
  # Activates the theme directly in the database
211
211
  theme-activate:
@@ -220,7 +220,7 @@ services:
220
220
  command: |
221
221
  bash -c 'for i in {1..10}
222
222
  do echo -e "⏳ \033[33mWaiting for DB server...\033[0m" &&
223
- mysql --ssl-mode=DISABLED -s -h db -e "exit" && break || sleep 3
223
+ mysql --ssl-mode=DISABLED -s -h db -e "SELECT 1" && break || sleep 3
224
224
  done &&
225
225
  sleep 2 &&
226
226
  echo -e "✔️ \033[32mConnected to DB\033[0m" &&