@ideasonpurpose/build-tools-wordpress 2.10.6 → 2.10.7

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/README.md CHANGED
@@ -1,99 +1,281 @@
1
1
  # @ideasonpurpose/build-tools-wordpress
2
2
 
3
- #### Version 2.10.6
3
+ #### Version 2.10.7
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
- [![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)
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/actions/workflows/npm-publish.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
7
8
 
8
- Build scripts and dependencies for IOP's WordPress development environments.
9
+ Build scripts and shared dependencies for WordPress development. Used in production at [Ideas On Purpose](https://www.ideasonpurpose.com).
9
10
 
10
- ## About This Package
11
+ This package centralizes Webpack configuration, asset pipelines, and helper CLIs so host projects stay thin: a small `package.json`, a one-line `webpack.config.js`, and an optional config file.
11
12
 
12
- These tools were migrated from our [Docker-based WordPress build tools](https://github.com/ideasonpurpose/docker-build) to speed up development and began the process of moving our build tools away from webpack. Gathering dependencies also simplifies the package.json and configuration files in host projects, making those slightly more manageable.
13
+ ## Requirements
13
14
 
14
- ### Versioned Releases
15
+ - **Node.js** 22.15+ (required by webpack-dev-server 6; ESM only)
15
16
 
16
- IOP versions our themes so every release creates a clear rollback snapshot. To accomplish this, every milestone build is generated into a versioned directory. This works well for themes where only one can be active, but fails for plugins where multiple versions can be simultaneously enabled so long as their directory names are different.
17
+ ## Install
17
18
 
18
- To work around this, a `type` property can be added to the config file. When `type` is `plugin`, builds will not add the version to directory names.
19
+ ```sh
20
+ npm install -D @ideasonpurpose/build-tools-wordpress
21
+ ```
19
22
 
20
- ### Optional Config
23
+ Typical host scripts (see [`boilerplate/package.json`](./boilerplate/package.json)):
21
24
 
22
- Each project may optionally include an **ideasonpurpose.config.js** file in the same directory as **package.json**. Any settings in this file will override [the defaults](https://github.com/ideasonpurpose/build-tools-wordpress/blob/main/config/ideasonpurpose.config.js).
25
+ ```json
26
+ {
27
+ "type": "module",
28
+ "scripts": {
29
+ "prebuild": "npm run clean",
30
+ "build": "NODE_ENV=production webpack",
31
+ "postbuild": "npm run zip",
32
+ "start": "webpack serve",
33
+ "zip": "iop-build-zip-archive"
34
+ },
35
+ "devDependencies": {
36
+ "@ideasonpurpose/build-tools-wordpress": "^2.10.6"
37
+ },
38
+ "prettier": "@ideasonpurpose/prettier-config",
39
+ "stylelint": {
40
+ "extends": "@ideasonpurpose/stylelint-config"
41
+ }
42
+ }
43
+ ```
23
44
 
24
- <!--
45
+ Prettier and Stylelint configs are re-exported from this package’s dependencies so hosts can extend them without separate installs.
25
46
 
26
- - **`src`** - The **source** directory containing source files which should be compiled or transposed. The contents of this directory will be omitted from builds.
27
- - **`dist`** - The **distribution** directory where processed, production-ready files will be output to. All contents of this directory will be included in builds.
28
- - **`src`** - An array of file entry points relative to the `src` directory. Each entry point will generate a like-named output file. All files and assets imported by a given entry point will be accessible from that entry's corresponding output file.
47
+ ## Quick start
29
48
 
30
- -->
49
+ **1. Point Webpack at this package**
31
50
 
32
- ## SVG Processing
51
+ ```js
52
+ // webpack.config.js
53
+ export { webpackConfig as default } from "@ideasonpurpose/build-tools-wordpress";
54
+ ```
33
55
 
34
- Webpack handles SVG files differently based on import context:
56
+ **2. Lay out sources** under the default theme paths (package `name` → theme folder):
35
57
 
36
- - **In SCSS files**: SVGs referenced via `url('file.svg')` are inlined as data URIs if under 4KB, otherwise emitted as separate files in the output directory.
58
+ ```text
59
+ wp-content/themes/<package-name>/
60
+ src/
61
+ js/main.js
62
+ js/admin.js
63
+ js/editor.js
64
+ sass/ # or styles/
65
+ dist/ # build output
66
+ ```
37
67
 
38
- - **In JS/TSX files**:
39
- - With `?url` query (e.g., `import svg from 'file.svg?url'`): Treated as assets, inlined as data URIs if under 4KB, else files.
40
- - With `?react` query (e.g., `import Icon from 'file.svg?react'`): Explicitly converted to a React component using `@svgr/webpack`.
41
- - Without `?url` or `?react`: Converted to React components using `@svgr/webpack` for direct JSX usage (default behavior when imported from `.jsx`/`.tsx`).
68
+ **3. Run**
42
69
 
43
- React components imported via `?react` (or implicitly) have `width`/`height` removed (`dimensions: false`), making them responsive while preserving `viewBox`.
70
+ ```sh
71
+ npm start # webpack-dev-server (proxies local WordPress when available)
72
+ NODE_ENV=production npm run build # production build + zip (if postbuild is set)
73
+ ```
44
74
 
45
- Data URIs are generated as `data:image/svg+xml,<url-encoded-content>` using more efficient URL-encoding, not base64.
75
+ Full IOP projects also use Docker Compose tooling from this package (`tooling/docker-compose.yml`) and scripts like `bootstrap`, `db:dump`, and `project:refresh`. Those are optional if you only need the asset pipeline.
46
76
 
47
- ### SVG Optimization
77
+ ## Configuration
48
78
 
49
- SVGO is included along with our preferred [svgo.config.mjs][]. Run this with `npx svgo <file-to-optimize.svg>`
79
+ Optional config is loaded with [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) under the name `ideasonpurpose` typically `ideasonpurpose.config.js` next to `package.json`. Values merge over [defaults](./lib/buildConfig.js).
50
80
 
51
- ## Experimental formatting helpers
81
+ ### Defaults
52
82
 
53
- This package includes two experimental formatting scripts:
83
+ | Option | Default | Notes |
84
+ | -------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------- |
85
+ | `src` | `./wp-content/themes/<name>/src` | `<name>` from host `package.json` |
86
+ | `dist` | `./wp-content/themes/<name>/dist` | |
87
+ | `entry` | `["./js/main.js", "./js/admin.js", "./js/editor.js"]` | Relative to `src`. String, array, or object |
88
+ | `publicPath` | `/wp-content/themes/<name>/dist/` | |
89
+ | `esTarget` | `"es2020"` | Passed to esbuild-loader / minimizer |
90
+ | `devtool` | `"source-map"` | |
91
+ | `proxy` | `"wordpress"` | Dev-server proxy target (Docker service name, URL, IP, or `true` to auto-detect) |
92
+ | `manifestFile` | `"./dependency-manifest.json"` | Written into `dist` |
93
+ | `type` | _(unset / theme)_ | Set `"plugin"` to change archive naming |
54
94
 
55
- ### `iop-html-php-prettier`
95
+ Paths resolve relative to the config file (or `package.json` if no config file is found).
56
96
 
57
- This uses [Prettier](https://prettier.io) to double-format mixed HTML and PHP files (like WordPress templates). PHP blocks are tokenized so Prettier can safely format the file as HTML, then PHP blocks are restored and the file is formatted again as PHP using [@prettier/plugin-php](https://github.com/prettier/plugin-php).
58
- **Usage:** `npx iop-html-php-prettier path/to/file.php`
97
+ ### Entry shapes
59
98
 
60
- ### `iop-format-wp-block-pattern`
99
+ ```js
100
+ // Array → basenames become entry keys (overlapping basenames merge)
101
+ entry: ["./js/main.js", "./js/admin.js"];
102
+
103
+ // String → single entry
104
+ entry: "./js/main.js";
105
+
106
+ // Object → passed through
107
+ entry: { app: "./js/main.js", admin: "./js/admin.js" };
108
+ ```
109
+
110
+ ### Themes vs plugins
111
+
112
+ Releases are versioned so each build is a clear rollback snapshot. For **themes**, zip archives use a versioned folder name (`my-theme-1.2.3`). That fails for **plugins**, where multiple versions can be active if directory names differ.
113
+
114
+ Set `type: "plugin"` to omit the version from the archive directory name:
115
+
116
+ ```js
117
+ // ideasonpurpose.config.js
118
+ export default {
119
+ type: "plugin",
120
+ src: `./wp-content/plugins/my-plugin/src`,
121
+ dist: `./wp-content/plugins/my-plugin/dist`,
122
+ publicPath: `/wp-content/plugins/my-plugin/dist/`,
123
+ entry: ["./js/main.js"],
124
+ };
125
+ ```
126
+
127
+ ## What the build does
128
+
129
+ | Area | Implementation |
130
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131
+ | Bundler | Webpack 5 |
132
+ | JS/TS/JSX | [esbuild-loader](https://github.com/privatenumber/esbuild-loader) |
133
+ | CSS | Sass ([sass-embedded](https://sass-lang.com/dart-sass/), modern compiler API), PostCSS (autoprefixer; cssnano in production) |
134
+ | Images | `asset` modules + [Sharp](https://sharp.pixelplumbing.com/) via image-minimizer-webpack-plugin |
135
+ | SVG | Asset / data-URI or React components via [@svgr/webpack](https://react-svgr.com/) (see below) |
136
+ | Copy | Static files from `src` (blocks, fonts, etc. handled specially) |
137
+ | WordPress | [`@wordpress/dependency-extraction-webpack-plugin`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dependency-extraction-webpack-plugin/) + custom dependency manifest |
138
+ | Dev server | Hot reload, live reload on PHP/HTML/SVG/JSON, reverse proxy to local WP |
139
+ | Production | Content hashes, minification, optional bundle analyzer report, zip via `iop-build-zip-archive` |
140
+
141
+ Sass `loadPaths` include `src/sass`, `src/styles`, and the project `node_modules`, so packages can be imported by name:
142
+
143
+ ```scss
144
+ @use "some-npm-package/styles";
145
+ ```
146
+
147
+ ### Dev server
148
+
149
+ - Proxies the site to a local WordPress container (default Docker service name `wordpress`) when `proxy` is enabled
150
+ - Watches theme PHP/HTML/SVG/JSON for full reload
151
+ - `GET /webpack/reload` triggers a client refresh (useful from PHP or external tools)
152
+
153
+ ### Zip archives
154
+
155
+ `iop-build-zip-archive` packs the theme/plugin parent of `src` into `_builds/<name>[-version].zip`. Version suffix is skipped when `type` is `"plugin"`.
156
+
157
+ ## SVG processing
158
+
159
+ Webpack treats SVGs differently by context:
160
+
161
+ - **In SCSS** (`url('file.svg')`): inlined as a data URI if under 4KB, otherwise emitted as a file.
162
+ - **In JS/TSX**:
163
+ - `import svg from 'file.svg?url'` — asset URL / data URI (4KB threshold)
164
+ - `import Icon from 'file.svg?react'` — React component via SVGR
165
+ - bare `import Icon from 'file.svg'` from `.jsx`/`.tsx` — React component (default)
166
+
167
+ React SVG components use `dimensions: false` (no fixed `width`/`height`; `viewBox` kept) so they scale with CSS.
61
168
 
62
- A specialized formatter for WordPress PHP block patterns. This makes block markup easier to read, expands JSON settings, and adjusts whitespace to work around Block Editor quirks.
63
- **Usage:** `npx iop-format-wp-block-pattern path/to/pattern.php`
169
+ Data URIs use URL-encoding (`data:image/svg+xml,<encoded>`), not base64.
64
170
 
65
- ## Local Development
171
+ ### SVGO and VS Code
66
172
 
67
- Because this project makes use of bin scripts, conventional `npm link` workflows won't work correctly. To work on this code in a development project, change the project's package.json to install from a local file path, probably something like this:
173
+ [SVGO](https://github.com/svg/svgo) and our preferred [`config/svgo.config.mjs`](./config/svgo.config.mjs) ship with the package, plus a stdin formatter for the editor.
174
+
175
+ Install [SVG Language Mode ID](https://marketplace.visualstudio.com/items?itemName=ideasonpurpose.svg-language-mode-id) and [Custom Local Formatters](https://marketplace.visualstudio.com/items?itemName=jkillian.custom-local-formatters), then in `settings.json`:
68
176
 
69
177
  ```json
70
- "devDependencies": {
71
- "@ideasonpurpose/build-tools-wordpress": "../../build-tools-wordpress"
178
+ {
179
+ "[svg]": {
180
+ "editor.defaultFormatter": "jkillian.custom-local-formatters",
181
+ "editor.formatOnSave": false
182
+ },
183
+ "customLocalFormatters.formatters": [
184
+ {
185
+ "command": "iop-vscode-svgo",
186
+ "languages": ["svg"]
187
+ }
188
+ ]
189
+ }
190
+ ```
191
+
192
+ The formatter loads a project `svgo.config` if present, otherwise falls back to this package’s config.
193
+
194
+ Notes:
195
+
196
+ - VS Code treats SVG as XML by default; the language-mode extension is required for the `[svg]` scope.
197
+ - Formatting fails if `editor.formatOnSaveMode` is `"modifications"` (range formatting does not send a full document).
198
+
199
+ **IOP SVGO defaults** ([`config/svgo.config.mjs`](./config/svgo.config.mjs)):
200
+
201
+ - Pretty-print with 4-space indent
202
+ - Preserve ID names (`cleanupIds: false`)
203
+ - Custom: copy width/height from `viewBox` onto `<svg>` when missing
204
+ - Custom: remove top-level `fill="none"`
205
+ - `removeUselessStrokeAndFill` with `removeNone: true`
206
+
207
+ ## CLI tools
208
+
209
+ | Command | Purpose |
210
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
211
+ | `iop-build-zip-archive` | Zip theme/plugin into `_builds/` |
212
+ | `iop-build-port-reporter` | Print discovered local WordPress URL/port (Docker) |
213
+ | `iop-project-refresh` | Sync host tooling from package boilerplate (`package.json` scripts, `docker-compose.yml`, `.gitignore`, dirs, optional templates). Flags: `--dry-run`, `--force` (allow dirty git tree) |
214
+ | `iop-html-php-prettier` | Experimental mixed HTML/PHP formatter |
215
+ | `iop-format-wp-block-pattern` | Experimental WordPress block-pattern PHP formatter |
216
+ | `iop-vscode-svgo` | SVGO over stdin for editor formatters |
217
+
218
+ ### `iop-html-php-prettier`
219
+
220
+ Double-formats mixed HTML and PHP (WordPress templates): PHP is tokenized, the file is formatted as HTML with Prettier, tokens are restored, then PHP is formatted with [@prettier/plugin-php](https://github.com/prettier/plugin-php).
221
+
222
+ A faster rewrite is in progress: **[Format Mixed PHP and HTML](https://github.com/ideasonpurpose/format-mixed-php-html)**.
223
+
224
+ ```sh
225
+ npx iop-html-php-prettier path/to/file.php
226
+ ```
227
+
228
+ Also accepts STDIN → STDOUT for Custom Local Formatters:
229
+
230
+ ```json
231
+ "customLocalFormatters.formatters": [
232
+ {
233
+ "command": "npx iop-html-php-prettier",
234
+ "languages": ["php"]
72
235
  }
236
+ ]
73
237
  ```
74
238
 
75
- Running a simple watch script to re-install on changes will make things somewhat seamless:
239
+ ### `iop-format-wp-block-pattern`
240
+
241
+ Formats WordPress block pattern PHP: readable block markup, expanded JSON in `<!-- wp:... -->` comments, and whitespace tuned for the block editor.
76
242
 
77
243
  ```sh
78
- cd dev-project-working-dir
79
- npm chokidar-cli "../../build-tools-wordpress/**/*" -c "npm install"
244
+ npx iop-format-wp-block-pattern path/to/pattern.php
80
245
  ```
81
246
 
82
- ### Additional Notes
247
+ ### `iop-project-refresh`
83
248
 
84
- This project expects an entirely ES Module based environment and specifies all dependencies using standard ESM import syntax. Projects importing this file should set `"type": "module"` in their package.json files.
249
+ Updates an existing project’s tooling from this package’s `boilerplate/` and `tooling/` trees. Safe defaults: refuses a dirty git working tree unless `--force`; use `--dry-run` to preview.
85
250
 
86
- #### Sass imports
251
+ ```sh
252
+ npx iop-project-refresh
253
+ npx iop-project-refresh --dry-run
254
+ npx iop-project-refresh --force
255
+ ```
87
256
 
88
- Sass can import stylesheet files directly from npm packages. Use the full path from node_modules like this:
257
+ ## Local development (this package)
89
258
 
90
- ```scss
91
- @use "@ideasonpurpose/build-tools-wordpress/example/package-style";
259
+ Bin scripts break conventional `npm link`. In a consumer project, depend on a local path instead:
260
+
261
+ ```json
262
+ "devDependencies": {
263
+ "@ideasonpurpose/build-tools-wordpress": "file:../../build-tools-wordpress"
264
+ }
265
+ ```
266
+
267
+ Reinstall on change:
268
+
269
+ ```sh
270
+ cd dev-project-working-dir
271
+ npx chokidar-cli "../../build-tools-wordpress/**/*" -c "npm install"
92
272
  ```
93
273
 
94
- #### Publishing to [npm](https://www.npmjs.com/package/@ideasonpurpose/build-tools-wordpress)
274
+ ## Publishing
275
+
276
+ Version-tagged releases publish to npm via GitHub Actions (OIDC trusted publishing). See [CHANGELOG.md](./CHANGELOG.md).
95
277
 
96
- A GitHub action will auto-publish version-tagged releases to npm. In order to publish, the repository must have an `NPM_TOKEN` secret set with the token from npm. [Log into npmjs.org](https://www.npmjs.com/login) with a publish-authorized account, then find the token page linked from the Profile page sidebar. Generate a new token and update the repository secret.
278
+ If the workflow needs a classic token instead, set repository secret `NPM_TOKEN` from an npm publish-capable account.
97
279
 
98
280
  <!-- START IOP CREDIT BLURB 2026-07-->
99
281
 
@@ -102,6 +284,6 @@ A GitHub action will auto-publish version-tagged releases to npm. In order to pu
102
284
  #### Brought to you by IOP
103
285
 
104
286
  | <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-white-on-black-88px.png" width="44" height="44" align="top" alt="IOP Logo"></a> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>. <br>&nbsp; |
105
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
287
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
106
288
 
107
289
  <!-- END IOP CREDIT BLURB -->
@@ -2,6 +2,9 @@
2
2
 
3
3
  //@ts-check
4
4
 
5
+ import prettierConfig from "@ideasonpurpose/prettier-config" with {
6
+ type: "json",
7
+ };
5
8
  /**
6
9
  * This is an experimental proof-of-concept for formatting mixed HTML & PHP
7
10
  * files from a single function.
@@ -9,15 +12,17 @@
9
12
  * TODO: Testing, naming, modularization, VS Code extension
10
13
  */
11
14
  import prettier from "prettier";
12
- import prettierConfig from "@ideasonpurpose/prettier-config" with { type: "json" };
13
15
 
14
16
  const phpPlugin = await import("@prettier/plugin-php");
15
17
 
16
18
  // Explicitly reset the plugin because global installs can't resolve it
19
+ // @ts-expect-error — prettierConfig types expect string paths, but runtime accepts plugin modules
17
20
  prettierConfig.plugins = [phpPlugin];
18
21
 
19
- import { readFile, writeFile } from "fs/promises";
20
- import { resolve, basename } from "path";
22
+ import { realpathSync } from "node:fs";
23
+ import { readFile, writeFile } from "node:fs/promises";
24
+ import { basename, resolve } from "node:path";
25
+ import { fileURLToPath } from "node:url";
21
26
 
22
27
  /**
23
28
  * Prettier API doesn't recognize overrides, so we extract them
@@ -30,6 +35,7 @@ const phpOptions = prettierConfig.overrides.find(
30
35
  (o) => o.files === "*.php",
31
36
  )?.options;
32
37
 
38
+ /** @param {string} html @param {number} offset */
33
39
  const isInTag = (html, offset) => {
34
40
  if (offset === 0) return false;
35
41
 
@@ -56,6 +62,7 @@ const isInTag = (html, offset) => {
56
62
  * being mutilated by the HTML formatting step. Cleaner than adding a string.replace
57
63
  * to unTokenizeHTML().
58
64
  */
65
+ /** @param {string} htmlContent */
59
66
  export function tokenizeHTML(htmlContent) {
60
67
  let tokenizedHTML = "";
61
68
  const phpCodeBlocks = new Map(); // Changed to Map for better performance and type safety
@@ -67,6 +74,7 @@ export function tokenizeHTML(htmlContent) {
67
74
  *
68
75
  * NOTE: This uses tokenCount from the enclosing scope
69
76
  */
77
+ /** @param {string} phpCodeBlock @param {string} prevContent */
70
78
  const tokenizeCodeBlock = (phpCodeBlock, prevContent) => {
71
79
  let start = "<";
72
80
  let end = " />";
@@ -79,27 +87,14 @@ export function tokenizeHTML(htmlContent) {
79
87
  return `${start}php_${tokenCount++}__`.padEnd(codeLength, "_") + end;
80
88
  };
81
89
 
82
- // const pattern = /<\?(?:php|=)[\s\S]*?\?>/gs;
83
- // const pattern =
84
- // /(?<before>(?:[^\s]|\s|^)\s*)(?<php><\?(?:php|=).*?(?:\?>|$))(?<after>(?:\s*)[^\s]|$)/gs;
85
- // const pattern =
86
- // /((?:[^\s]|\s|^)\s*)(<\?(?:php|=).*?(?:\?>|$))((?:\s*)[^\s]|$)/gms;
87
- // // const pattern = /([^\s]+)\s*(<\?(?:php|=).*?(?:\?>|$))\s*([^\s]*)/gms;
88
- // const pattern =
89
- // /([^\s]?\s*)?(<\?(?:php|=).*?(?:\?>|$))((?:\s*)[^\s]|$)/gms;
90
- // const pattern =
91
- // /(?<=((?:[^\s]|\s|^)\s*))(<\?(?:php|=).*?\?>)(?=((?:\s*)[^\s]|$))/gms;
92
- // try removing look ahead/behind
93
- const pattern = /(<\?(?:php|=).*?\?>)/gms;
94
-
95
90
  // const regex = new RegExp(/<\?(?:php|=).*?\?>/, "gs");
96
91
  // Trying to capture open-ended PHP codeBlocks in a single regexp
97
92
  const regex = new RegExp(/<\?(?:php|=).*?(?:\?>|$)/, "gs");
98
93
 
99
- let match;
94
+ let match = regex.exec(htmlContent);
100
95
  let token;
101
96
  let lastIndex = 0;
102
- while ((match = regex.exec(htmlContent)) !== null) {
97
+ while (match !== null) {
103
98
  tokenizedHTML += htmlContent.slice(lastIndex, match.index);
104
99
 
105
100
  token = tokenizeCodeBlock(match[0], tokenizedHTML);
@@ -107,12 +102,14 @@ export function tokenizeHTML(htmlContent) {
107
102
  tokenizedHTML += token;
108
103
 
109
104
  lastIndex = match.index + match[0].length;
105
+ match = regex.exec(htmlContent);
110
106
  }
111
107
  tokenizedHTML += htmlContent.slice(lastIndex);
112
108
 
113
109
  return { tokenizedHTML, phpCodeBlocks };
114
110
  }
115
111
 
112
+ /** @param {string} tokenizedHTML @param {Map<string, string>} phpCodeBlocks */
116
113
  export function unTokenizeHTML(tokenizedHTML, phpCodeBlocks) {
117
114
  let phpContent = tokenizedHTML;
118
115
  for (const [token, phpBlock] of phpCodeBlocks) {
@@ -132,55 +129,129 @@ export function unTokenizeHTML(tokenizedHTML, phpCodeBlocks) {
132
129
  }
133
130
 
134
131
  /**
135
- * Formats a mixed HTML & PHP file with these steps:
132
+ * @param {bigint} start
133
+ * @param {bigint} end
134
+ */
135
+ const ms = (start, end) => Number(end - start) / 1e6;
136
+
137
+ /**
138
+ * Formats mixed HTML & PHP content:
136
139
  * 1. Tokenize PHP Blocks as HTML-safe and attribute-safe strings
137
140
  * 2. Format the result as HTML
138
141
  * 3. Un-tokenize HTML back to PHP
139
142
  * 4. Format again as PHP
140
- * 5. Overwrite the file
141
143
  *
142
- * @param {string} filepath - The path to the file to format (must be a valid file path).
144
+ * @param {string} content
145
+ * @param {string} [label]
146
+ * @returns {Promise<string>}
143
147
  */
144
- export async function formatHTMLThenPHP(filepath) {
145
- try {
146
- const startTime = process.hrtime.bigint();
147
- const rawFile = await readFile(filepath, "utf8");
148
+ export async function formatHTMLThenPHPContent(content, label = "stdin") {
149
+ const t0 = process.hrtime.bigint();
150
+ const startupMs = process.uptime() * 1e3;
148
151
 
149
- const { tokenizedHTML, phpCodeBlocks } = tokenizeHTML(rawFile);
152
+ const tTokenize0 = process.hrtime.bigint();
153
+ const { tokenizedHTML, phpCodeBlocks } = tokenizeHTML(content);
154
+ const tTokenize1 = process.hrtime.bigint();
150
155
 
151
- const htmlFormatted = await prettier.format(tokenizedHTML, {
152
- ...prettierConfig,
153
- ...htmlOptions,
154
- parser: "html",
155
- embeddedLanguageFormatting: "auto",
156
- });
156
+ const htmlFormatted = await prettier.format(tokenizedHTML, {
157
+ ...prettierConfig,
158
+ ...htmlOptions,
159
+ parser: "html",
160
+ embeddedLanguageFormatting: "auto",
161
+ });
162
+ const tHtml = process.hrtime.bigint();
157
163
 
158
- const phpUnTokenized = unTokenizeHTML(htmlFormatted, phpCodeBlocks);
164
+ const phpUnTokenized = unTokenizeHTML(htmlFormatted, phpCodeBlocks);
165
+ const tUnTokenize = process.hrtime.bigint();
159
166
 
160
- const phpFormatted = await prettier.format(phpUnTokenized, {
161
- ...prettierConfig,
162
- ...phpOptions,
163
- parser: "php",
164
- embeddedLanguageFormatting: "auto",
165
- // plugins: [phpPlugin], // Explicitly pass the plugin
166
- });
167
+ const phpFormatted = await prettier.format(phpUnTokenized, {
168
+ ...prettierConfig,
169
+ ...phpOptions,
170
+ parser: "php",
171
+ embeddedLanguageFormatting: "auto",
172
+ });
173
+ const tEnd = process.hrtime.bigint();
167
174
 
168
- await writeFile(filepath, phpFormatted, "utf8");
169
- const endTime = process.hrtime.bigint();
170
- const duration = Number(endTime - startTime);
175
+ console.error(
176
+ [
177
+ label,
178
+ `startup ${startupMs.toFixed(2)}ms`,
179
+ `tokenize ${ms(tTokenize0, tTokenize1).toFixed(2)}ms`,
180
+ `formatHTML ${ms(tTokenize1, tHtml).toFixed(2)}ms`,
181
+ `unTokenize ${ms(tHtml, tUnTokenize).toFixed(2)}ms`,
182
+ `formatPHP ${ms(tUnTokenize, tEnd).toFixed(2)}ms`,
183
+ `total ${(startupMs + ms(t0, tEnd)).toFixed(2)}ms`,
184
+ ].join(" "),
185
+ );
171
186
 
172
- console.log(`${basename(filepath)} ${(duration / 1e6).toFixed(2)}ms`);
173
- } catch (error) {
174
- console.error("Error:", error);
187
+ return phpFormatted;
188
+ }
189
+
190
+ /**
191
+ * Formats a mixed HTML & PHP file in place.
192
+ *
193
+ * @param {string} filepath
194
+ */
195
+ export async function formatHTMLThenPHP(filepath) {
196
+ const rawFile = await readFile(filepath, "utf8");
197
+ const phpFormatted = await formatHTMLThenPHPContent(
198
+ rawFile,
199
+ basename(filepath),
200
+ );
201
+ await writeFile(filepath, phpFormatted, "utf8");
202
+ }
203
+
204
+ /**
205
+ * @returns {Promise<string>}
206
+ */
207
+ async function readStdin() {
208
+ const chunks = [];
209
+ for await (const chunk of process.stdin) {
210
+ chunks.push(chunk);
175
211
  }
212
+ return Buffer.concat(chunks).toString("utf8");
176
213
  }
177
214
 
215
+ /**
216
+ * CLI: filepath arg overwrites the file; no arg reads STDIN and writes STDOUT.
217
+ *
218
+ * @param {string} [filepath]
219
+ */
178
220
  export async function main(filepath = process.argv[2]) {
179
- if (!filepath) {
180
- console.error("Error: A filepath is required.");
181
- return;
221
+ try {
222
+ if (filepath) {
223
+ await formatHTMLThenPHP(resolve(filepath));
224
+ return;
225
+ }
226
+
227
+ if (process.stdin.isTTY) {
228
+ console.error(
229
+ "Usage: iop-html-php-prettier <filepath>\n iop-html-php-prettier < input.php",
230
+ );
231
+ process.exitCode = 1;
232
+ return;
233
+ }
234
+
235
+ const formatted = await formatHTMLThenPHPContent(
236
+ await readStdin(),
237
+ "stdin",
238
+ );
239
+ process.stdout.write(formatted);
240
+ } catch (error) {
241
+ console.error("Error:", error);
242
+ process.exitCode = 1;
182
243
  }
183
- await formatHTMLThenPHP(resolve(filepath));
184
244
  }
185
245
 
186
- if (process.argv[2]) main();
246
+ const isMain = (() => {
247
+ if (process.argv[1] == null) return false;
248
+ try {
249
+ return (
250
+ fileURLToPath(import.meta.url) === realpathSync(resolve(process.argv[1]))
251
+ );
252
+ } catch {
253
+ return false;
254
+ }
255
+ })();
256
+
257
+ if (isMain) main();
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ // @ts-check
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { loadConfig, optimize } from "svgo";
7
+ import fallbackConfig from "../config/svgo.config.mjs";
8
+
9
+ console.error("starting iop-vscode-svgo");
10
+
11
+ if (process.stdin.isTTY) {
12
+ process.stderr.write("Error: No SVG data received on stdin\n");
13
+ process.exit(1);
14
+ }
15
+
16
+ const svg = readFileSync(0, "utf8").trim();
17
+ if (!svg) {
18
+ process.stderr.write("Error: No SVG data received on stdin\n");
19
+ process.exit(1);
20
+ }
21
+
22
+ try {
23
+ const svgoConfig = /** @type {import("svgo").Config} */ (
24
+ (await loadConfig()) ?? fallbackConfig
25
+ );
26
+ const result = optimize(svg, svgoConfig);
27
+ process.stdout.write(result.data);
28
+ } catch (err) {
29
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : err}\n`);
30
+ process.exit(1);
31
+ }
32
+ console.error("Done!");
package/biome.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "space"
14
+ },
15
+ "linter": {
16
+ "enabled": true,
17
+ "rules": {
18
+ "preset": "recommended",
19
+ "a11y": {
20
+ "noSvgWithoutTitle": "off"
21
+ },
22
+ "style": {
23
+ "useNodejsImportProtocol": {
24
+ "level": "error",
25
+ "fix": "safe"
26
+ }
27
+ }
28
+ }
29
+ },
30
+ "javascript": {
31
+ "formatter": {
32
+ "quoteStyle": "double"
33
+ }
34
+ },
35
+ "assist": {
36
+ "enabled": true,
37
+ "actions": {
38
+ "source": {
39
+ "organizeImports": "on"
40
+ }
41
+ }
42
+ }
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ideasonpurpose/build-tools-wordpress",
3
- "version": "2.10.6",
3
+ "version": "2.10.7",
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": {
@@ -23,14 +23,15 @@
23
23
  "iop-build-zip-archive": "./bin/zip.js",
24
24
  "iop-html-php-prettier": "./bin/format-php-prettier.js",
25
25
  "iop-format-wp-block-pattern": "./bin/format-wp-block-pattern.js",
26
- "iop-project-refresh": "./bin/refresh.js"
26
+ "iop-project-refresh": "./bin/refresh.js",
27
+ "iop-vscode-svgo": "./bin/vscode-svgo.js"
27
28
  },
28
29
  "directories": {
29
30
  "lib": "lib"
30
31
  },
31
32
  "scripts": {
32
33
  "test": "vitest",
33
- "version": "version-everything && auto-changelog && git add -u"
34
+ "version": "version-everything && git add -u"
34
35
  },
35
36
  "prettier": "@ideasonpurpose/prettier-config",
36
37
  "dependencies": {
@@ -89,12 +90,10 @@
89
90
  "version-everything": "^0.12.2",
90
91
  "webpack": "^5.108.4",
91
92
  "webpack-bundle-analyzer": "^5.3.1",
93
+ "webpack-cli": "^6.0.1",
92
94
  "webpack-dev-server": "^6.0.0",
93
95
  "webpack-manifest-plugin": "^6.0.1"
94
96
  },
95
- "peerDependencies": {
96
- "webpack-cli": "^6.0.1"
97
- },
98
97
  "engines": {
99
98
  "node": ">=22.15.0"
100
99
  },
package/CHANGELOG.md DELETED
@@ -1,520 +0,0 @@
1
- ### Changelog
2
-
3
- All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4
-
5
- Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
-
7
- #### v2.10.5
8
-
9
- > 22 July 2026
10
-
11
- - logo, vscode settings
12
- - output tweaks
13
-
14
- #### v2.10.4
15
-
16
- > 18 July 2026
17
-
18
- - fix zip reporting and resolve typescript types
19
-
20
- #### v2.10.3
21
-
22
- > 17 July 2026
23
-
24
- - More reporter tweaks
25
-
26
- #### v2.10.2
27
-
28
- > 16 July 2026
29
-
30
- - tweak reporting
31
-
32
- #### v2.10.1
33
-
34
- > 15 July 2026
35
-
36
- - remove infrastructureLogging
37
-
38
- #### v2.10.0
39
-
40
- > 15 July 2026
41
-
42
- - bump deps, AGENTS
43
- - update proxy, messing with output
44
- - add discard-changes config to example composer.json
45
-
46
- #### v2.9.1
47
-
48
- > 26 June 2026
49
-
50
- - bump deps, format action
51
-
52
- #### v2.9.0
53
-
54
- > 26 June 2026
55
-
56
- - update AGENTS
57
- - strip dimensions from React-imported SVGRs.
58
-
59
- #### v2.8.5
60
-
61
- > 23 June 2026
62
-
63
- - simplify release workflow
64
- - suppress GTID warnings from mysqldump
65
-
66
- #### v2.8.4
67
-
68
- > 23 June 2026
69
-
70
- - bump deps
71
- - update SVGO config plus custom plugins
72
- - extend whitespace handling to paragraphs with attributes
73
-
74
- #### v2.8.3
75
-
76
- > 5 June 2026
77
-
78
- - make formatting conditionals work with npx again
79
- - add WP_ENVIRONMENT_TYPE to docker-compose environment
80
-
81
- #### v2.8.2
82
-
83
- > 3 June 2026
84
-
85
- - update zip script to use ESM archiver
86
-
87
- #### v2.8.1
88
-
89
- > 3 June 2026
90
-
91
- - fix db connection checks in docker-compose
92
-
93
- #### v2.8.0
94
-
95
- > 3 June 2026
96
-
97
- - bump deps
98
- - refactor format functions and improve tests
99
- - boilerplate and README SVGo info
100
-
101
- #### v2.7.0
102
-
103
- > 6 May 2026
104
-
105
- - bump deps, migrate some tooling
106
- - feat: add AI Coding Assistant guidelines documentation
107
-
108
- #### v2.6.6
109
-
110
- > 27 April 2026
111
-
112
- - bump deps
113
- - feat: add nested list structure and formatting for WordPress block patterns
114
- - feat: add experimental formatting helpers for HTML/PHP and WordPress block patterns
115
-
116
- #### v2.6.5
117
-
118
- > 5 April 2026
119
-
120
- - fix: update setup-node action version to v6 for consistency
121
-
122
- #### v2.6.4
123
-
124
- > 5 April 2026
125
-
126
- - chore: update actions versions in npm-publish workflow and bump version-everything dependency
127
- - feat: add new formatting functions for handling list elements and normalize newlines
128
-
129
- #### v2.6.3
130
-
131
- > 4 April 2026
132
-
133
- - bump deps
134
- - feat: add formatter for WordPress block pattern PHP files and corresponding tests
135
-
136
- #### v2.6.2
137
-
138
- > 23 March 2026
139
-
140
- - feat: add error handling for WebSocket proxy requests
141
-
142
- #### v2.6.1
143
-
144
- > 23 March 2026
145
-
146
- - chore: update dependencies in package.json
147
-
148
- #### v2.6.0
149
-
150
- > 13 March 2026
151
-
152
- - bump deps, remove debug logs, resulve from sass & styles
153
-
154
- #### v2.5.1
155
-
156
- > 8 March 2026
157
-
158
- - add rimraf
159
- - add SVG processing details to README
160
- - add comment for runner images in release workflow
161
-
162
- #### v2.5.0
163
-
164
- > 7 March 2026
165
-
166
- - bump deps
167
- - beginning of typing, some cleanup
168
- - inline SVGs in SCSS files by default
169
-
170
- #### v2.4.2
171
-
172
- > 22 December 2025
173
-
174
- - Update npm publish command with options
175
-
176
- #### v2.4.1
177
-
178
- > 22 December 2025
179
-
180
- - update workflow for trusted publishing
181
-
182
- #### v2.4.0
183
-
184
- > 21 December 2025
185
-
186
- - bump deps
187
- - Enable loading Sass files from node_modules
188
-
189
- #### v2.3.2
190
-
191
- > 3 November 2025
192
-
193
- - bump deps
194
- - watch tweaks, debugging, formatting and cleanup
195
-
196
- #### v2.3.1
197
-
198
- > 21 October 2025
199
-
200
- - liveReload workaround for mini-css-extract-plugin HMR issues
201
- - WIP updates
202
-
203
- #### v2.3.0
204
-
205
- > 19 October 2025
206
-
207
- - Fixes and optimizations for multiple entry points
208
-
209
- #### v2.2.6
210
-
211
- > 18 October 2025
212
-
213
- - bump deps
214
-
215
- #### v2.2.5
216
-
217
- > 6 October 2025
218
-
219
- - bump deps
220
- - update example docker-compose file with experimental WP Multisite envvars
221
-
222
- #### v2.2.4
223
-
224
- > 10 September 2025
225
-
226
- - Make iop-format-prettier work globally
227
-
228
- #### v2.2.3
229
-
230
- > 9 September 2025
231
-
232
- - trying explicit PHP plugin loading
233
-
234
- #### v2.2.2
235
-
236
- > 9 September 2025
237
-
238
- - tweaking deps
239
-
240
- #### v2.2.1
241
-
242
- > 9 September 2025
243
-
244
- - tweak format-php-prettier, bump deps
245
-
246
- #### v2.2.0
247
-
248
- > 7 September 2025
249
-
250
- - bump deps
251
- - Update websocket reload for v5, cleanup
252
- - Testing some watch fixes
253
-
254
- #### v2.1.11
255
-
256
- > 3 September 2025
257
-
258
- - bump deps
259
-
260
- #### v2.1.10
261
-
262
- > 3 September 2025
263
-
264
- - tweak devserver hot setting
265
-
266
- #### v2.1.9
267
-
268
- > 24 August 2025
269
-
270
- - bump deps
271
-
272
- #### v2.1.8
273
-
274
- > 29 May 2025
275
-
276
- - Fix inline-whitespace wrapped php token bug
277
- - typo
278
-
279
- #### v2.1.7
280
-
281
- > 28 May 2025
282
-
283
- - bump deps
284
- - enable sourcemaps on all CSS loaders
285
-
286
- #### v2.1.6
287
-
288
- > 1 May 2025
289
-
290
- - refactor tokenizeHTML, bug fixes, tests
291
- - typo
292
-
293
- #### v2.1.5
294
-
295
- > 17 April 2025
296
-
297
- - escape capture group replacement strings when unTokenizing, plus tests
298
- - change dev site reporter emoji
299
-
300
- #### v2.1.4
301
-
302
- > 16 April 2025
303
-
304
- - use look-behind and look-forward to allow overlaps, simplify replacements
305
- - fix README
306
-
307
- #### v2.1.3
308
-
309
- > 16 April 2025
310
-
311
- - Use self-closing html tags as tokens. Closes #11
312
-
313
- #### v2.1.2
314
-
315
- > 14 April 2025
316
-
317
- - bump deps
318
- - experimental html & php formatter
319
-
320
- #### v2.1.1
321
-
322
- > 9 April 2025
323
-
324
- - Clean up outputs a little
325
-
326
- #### v2.1.0
327
-
328
- > 1 April 2025
329
-
330
- - bump deps
331
- - Standardize on sass-embedded, clean up, bump deps
332
-
333
- #### v2.0.6
334
-
335
- > 20 March 2025
336
-
337
- - try moving webpack-cli to peerDependencies
338
-
339
- #### v2.0.5
340
-
341
- > 20 March 2025
342
-
343
- - add note about local development to readme
344
- - resolve webpack loaders when running linked
345
- - Fix for missing content-type header
346
-
347
- #### v2.0.4
348
-
349
- > 19 March 2025
350
-
351
- - update GitHub Actions
352
-
353
- #### v2.0.3
354
-
355
- > 19 March 2025
356
-
357
- - bump deps
358
- - update example files
359
- - update devserver-proxy tests
360
-
361
- #### v2.0.2
362
-
363
- > 13 January 2025
364
-
365
- - bump deps
366
-
367
- #### v2.0.1
368
-
369
- > 19 October 2024
370
-
371
- - another Sass bump
372
-
373
- #### v2.0.0
374
-
375
- > 19 October 2024
376
-
377
- - bump deps
378
- - update example webpack.config file
379
- - bump deps
380
-
381
- #### v1.2.1
382
-
383
- > 19 September 2024
384
-
385
- - plugins updates, config fixes
386
-
387
- #### v1.2.0
388
-
389
- > 19 September 2024
390
-
391
- - deprecation free build! closes #5
392
- - bump deps, remove some for deprecations. Ref #5
393
- - bump deps, update examples and testing
394
- - export webpackConfig
395
-
396
- #### v1.1.14
397
-
398
- > 6 May 2024
399
-
400
- - minor bumps
401
-
402
- #### v1.1.13
403
-
404
- > 10 April 2024
405
-
406
- - bump most all the deps
407
- - add read-package-up, esTarget: es2020
408
- - readme tweak
409
-
410
- #### v1.1.12
411
-
412
- > 4 March 2024
413
-
414
- - readme badge links
415
- - were all these problems just a missing shebang?
416
-
417
- #### v1.1.11
418
-
419
- > 4 March 2024
420
-
421
- - Remove version from plugin builds WIP
422
-
423
- #### v1.1.10
424
-
425
- > 4 March 2024
426
-
427
- - use process.stdout references (of course)
428
-
429
- #### v1.1.9
430
-
431
- > 4 March 2024
432
-
433
- - update actions
434
- - missed a couple readline calls
435
-
436
- #### v1.1.8
437
-
438
- > 4 March 2024
439
-
440
- - quick remove readline function
441
-
442
- #### v1.1.7
443
-
444
- > 4 March 2024
445
-
446
- - extracting the zip script, updated deps
447
- - WIP example webpack.config.js file
448
- - update readme
449
-
450
- #### v1.1.6
451
-
452
- > 30 January 2024
453
-
454
- - test stuff and fixes
455
- - misc fixes and first zip testing
456
-
457
- #### v1.1.5
458
-
459
- > 25 January 2024
460
-
461
- - move the localPort stuff into devserver-proxy
462
-
463
- #### v1.1.4
464
-
465
- > 25 January 2024
466
-
467
- - deps, formatting, debugging
468
-
469
- #### v1.1.3
470
-
471
- > 24 January 2024
472
-
473
- - reconfigure imports, port-reporter to module, lots of deps
474
- - add changelog, update readme (badges)
475
- - fix path to port-reporter script
476
-
477
- #### v1.1.2
478
-
479
- > 23 January 2024
480
-
481
- #### v1.1.1
482
-
483
- > 23 January 2024
484
-
485
- - versioning, github actions, bump deps, README
486
- - update version in readme
487
-
488
- #### v1.1.0
489
-
490
- > 23 January 2024
491
-
492
- - add packages
493
- - name change, prepare for npm publish
494
- - add port-reporter script, giving up on linking local pacakges
495
-
496
- #### v1.0.4
497
-
498
- > 16 January 2024
499
-
500
- - embed default.config
501
-
502
- #### v1.0.3
503
-
504
- > 16 January 2024
505
-
506
- - buildConfig
507
-
508
- #### v1.0.2
509
-
510
- > 16 January 2024
511
-
512
- - add sass-embedded
513
-
514
- #### v1.0.1
515
-
516
- > 16 January 2024
517
-
518
- - add modules
519
- - add lib, temp exports
520
- - Initial commit
File without changes