@ideasonpurpose/build-tools-wordpress 2.10.5 → 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.5
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/bin/zip.js CHANGED
@@ -15,6 +15,7 @@ import { globby } from "globby";
15
15
  import isTextPath from "is-text-path";
16
16
  import replaceStream from "replacestream";
17
17
  import stringLength from "string-length";
18
+ import open from "open";
18
19
 
19
20
  import { buildConfig } from "../index.js";
20
21
  import { prettierHrtime } from "../lib/prettier-hrtime.js";
@@ -181,7 +182,7 @@ function foundReporter(file) {
181
182
  }
182
183
  }
183
184
 
184
- function finishReporter() {
185
+ async function finishReporter() {
185
186
  const outBytes = archive.pointer();
186
187
  const end = process.hrtime(start);
187
188
  const duration = prettierHrtime(end);
@@ -225,4 +226,7 @@ function finishReporter() {
225
226
  chalk.bold(`Remember to push to ${chalk.cyan("GitHub!")}`),
226
227
  );
227
228
  console.log("✨");
229
+
230
+ // Experiment: Try opening the _builds directory at the end of the build
231
+ await open(dirname(zipFile.pathname));
228
232
  }
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.5",
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": {
@@ -67,6 +68,7 @@
67
68
  "is-text-path": "^3.0.0",
68
69
  "lodash": "^4.18.1",
69
70
  "mini-css-extract-plugin": "^2.10.2",
71
+ "open": "^11.0.0",
70
72
  "ora": "^9.4.1",
71
73
  "postcss": "^8.5.19",
72
74
  "postcss-loader": "^8.2.1",
@@ -88,12 +90,10 @@
88
90
  "version-everything": "^0.12.2",
89
91
  "webpack": "^5.108.4",
90
92
  "webpack-bundle-analyzer": "^5.3.1",
93
+ "webpack-cli": "^6.0.1",
91
94
  "webpack-dev-server": "^6.0.0",
92
95
  "webpack-manifest-plugin": "^6.0.1"
93
96
  },
94
- "peerDependencies": {
95
- "webpack-cli": "^6.0.1"
96
- },
97
97
  "engines": {
98
98
  "node": ">=22.15.0"
99
99
  },
package/CHANGELOG.md DELETED
@@ -1,513 +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.4
8
-
9
- > 18 July 2026
10
-
11
- - fix zip reporting and resolve typescript types
12
-
13
- #### v2.10.3
14
-
15
- > 17 July 2026
16
-
17
- - More reporter tweaks
18
-
19
- #### v2.10.2
20
-
21
- > 16 July 2026
22
-
23
- - tweak reporting
24
-
25
- #### v2.10.1
26
-
27
- > 15 July 2026
28
-
29
- - remove infrastructureLogging
30
-
31
- #### v2.10.0
32
-
33
- > 15 July 2026
34
-
35
- - bump deps, AGENTS
36
- - update proxy, messing with output
37
- - add discard-changes config to example composer.json
38
-
39
- #### v2.9.1
40
-
41
- > 26 June 2026
42
-
43
- - bump deps, format action
44
-
45
- #### v2.9.0
46
-
47
- > 26 June 2026
48
-
49
- - update AGENTS
50
- - strip dimensions from React-imported SVGRs.
51
-
52
- #### v2.8.5
53
-
54
- > 23 June 2026
55
-
56
- - simplify release workflow
57
- - suppress GTID warnings from mysqldump
58
-
59
- #### v2.8.4
60
-
61
- > 23 June 2026
62
-
63
- - bump deps
64
- - update SVGO config plus custom plugins
65
- - extend whitespace handling to paragraphs with attributes
66
-
67
- #### v2.8.3
68
-
69
- > 5 June 2026
70
-
71
- - make formatting conditionals work with npx again
72
- - add WP_ENVIRONMENT_TYPE to docker-compose environment
73
-
74
- #### v2.8.2
75
-
76
- > 3 June 2026
77
-
78
- - update zip script to use ESM archiver
79
-
80
- #### v2.8.1
81
-
82
- > 3 June 2026
83
-
84
- - fix db connection checks in docker-compose
85
-
86
- #### v2.8.0
87
-
88
- > 3 June 2026
89
-
90
- - bump deps
91
- - refactor format functions and improve tests
92
- - boilerplate and README SVGo info
93
-
94
- #### v2.7.0
95
-
96
- > 6 May 2026
97
-
98
- - bump deps, migrate some tooling
99
- - feat: add AI Coding Assistant guidelines documentation
100
-
101
- #### v2.6.6
102
-
103
- > 27 April 2026
104
-
105
- - bump deps
106
- - feat: add nested list structure and formatting for WordPress block patterns
107
- - feat: add experimental formatting helpers for HTML/PHP and WordPress block patterns
108
-
109
- #### v2.6.5
110
-
111
- > 5 April 2026
112
-
113
- - fix: update setup-node action version to v6 for consistency
114
-
115
- #### v2.6.4
116
-
117
- > 5 April 2026
118
-
119
- - chore: update actions versions in npm-publish workflow and bump version-everything dependency
120
- - feat: add new formatting functions for handling list elements and normalize newlines
121
-
122
- #### v2.6.3
123
-
124
- > 4 April 2026
125
-
126
- - bump deps
127
- - feat: add formatter for WordPress block pattern PHP files and corresponding tests
128
-
129
- #### v2.6.2
130
-
131
- > 23 March 2026
132
-
133
- - feat: add error handling for WebSocket proxy requests
134
-
135
- #### v2.6.1
136
-
137
- > 23 March 2026
138
-
139
- - chore: update dependencies in package.json
140
-
141
- #### v2.6.0
142
-
143
- > 13 March 2026
144
-
145
- - bump deps, remove debug logs, resulve from sass & styles
146
-
147
- #### v2.5.1
148
-
149
- > 8 March 2026
150
-
151
- - add rimraf
152
- - add SVG processing details to README
153
- - add comment for runner images in release workflow
154
-
155
- #### v2.5.0
156
-
157
- > 7 March 2026
158
-
159
- - bump deps
160
- - beginning of typing, some cleanup
161
- - inline SVGs in SCSS files by default
162
-
163
- #### v2.4.2
164
-
165
- > 22 December 2025
166
-
167
- - Update npm publish command with options
168
-
169
- #### v2.4.1
170
-
171
- > 22 December 2025
172
-
173
- - update workflow for trusted publishing
174
-
175
- #### v2.4.0
176
-
177
- > 21 December 2025
178
-
179
- - bump deps
180
- - Enable loading Sass files from node_modules
181
-
182
- #### v2.3.2
183
-
184
- > 3 November 2025
185
-
186
- - bump deps
187
- - watch tweaks, debugging, formatting and cleanup
188
-
189
- #### v2.3.1
190
-
191
- > 21 October 2025
192
-
193
- - liveReload workaround for mini-css-extract-plugin HMR issues
194
- - WIP updates
195
-
196
- #### v2.3.0
197
-
198
- > 19 October 2025
199
-
200
- - Fixes and optimizations for multiple entry points
201
-
202
- #### v2.2.6
203
-
204
- > 18 October 2025
205
-
206
- - bump deps
207
-
208
- #### v2.2.5
209
-
210
- > 6 October 2025
211
-
212
- - bump deps
213
- - update example docker-compose file with experimental WP Multisite envvars
214
-
215
- #### v2.2.4
216
-
217
- > 10 September 2025
218
-
219
- - Make iop-format-prettier work globally
220
-
221
- #### v2.2.3
222
-
223
- > 9 September 2025
224
-
225
- - trying explicit PHP plugin loading
226
-
227
- #### v2.2.2
228
-
229
- > 9 September 2025
230
-
231
- - tweaking deps
232
-
233
- #### v2.2.1
234
-
235
- > 9 September 2025
236
-
237
- - tweak format-php-prettier, bump deps
238
-
239
- #### v2.2.0
240
-
241
- > 7 September 2025
242
-
243
- - bump deps
244
- - Update websocket reload for v5, cleanup
245
- - Testing some watch fixes
246
-
247
- #### v2.1.11
248
-
249
- > 3 September 2025
250
-
251
- - bump deps
252
-
253
- #### v2.1.10
254
-
255
- > 3 September 2025
256
-
257
- - tweak devserver hot setting
258
-
259
- #### v2.1.9
260
-
261
- > 24 August 2025
262
-
263
- - bump deps
264
-
265
- #### v2.1.8
266
-
267
- > 29 May 2025
268
-
269
- - Fix inline-whitespace wrapped php token bug
270
- - typo
271
-
272
- #### v2.1.7
273
-
274
- > 28 May 2025
275
-
276
- - bump deps
277
- - enable sourcemaps on all CSS loaders
278
-
279
- #### v2.1.6
280
-
281
- > 1 May 2025
282
-
283
- - refactor tokenizeHTML, bug fixes, tests
284
- - typo
285
-
286
- #### v2.1.5
287
-
288
- > 17 April 2025
289
-
290
- - escape capture group replacement strings when unTokenizing, plus tests
291
- - change dev site reporter emoji
292
-
293
- #### v2.1.4
294
-
295
- > 16 April 2025
296
-
297
- - use look-behind and look-forward to allow overlaps, simplify replacements
298
- - fix README
299
-
300
- #### v2.1.3
301
-
302
- > 16 April 2025
303
-
304
- - Use self-closing html tags as tokens. Closes #11
305
-
306
- #### v2.1.2
307
-
308
- > 14 April 2025
309
-
310
- - bump deps
311
- - experimental html & php formatter
312
-
313
- #### v2.1.1
314
-
315
- > 9 April 2025
316
-
317
- - Clean up outputs a little
318
-
319
- #### v2.1.0
320
-
321
- > 1 April 2025
322
-
323
- - bump deps
324
- - Standardize on sass-embedded, clean up, bump deps
325
-
326
- #### v2.0.6
327
-
328
- > 20 March 2025
329
-
330
- - try moving webpack-cli to peerDependencies
331
-
332
- #### v2.0.5
333
-
334
- > 20 March 2025
335
-
336
- - add note about local development to readme
337
- - resolve webpack loaders when running linked
338
- - Fix for missing content-type header
339
-
340
- #### v2.0.4
341
-
342
- > 19 March 2025
343
-
344
- - update GitHub Actions
345
-
346
- #### v2.0.3
347
-
348
- > 19 March 2025
349
-
350
- - bump deps
351
- - update example files
352
- - update devserver-proxy tests
353
-
354
- #### v2.0.2
355
-
356
- > 13 January 2025
357
-
358
- - bump deps
359
-
360
- #### v2.0.1
361
-
362
- > 19 October 2024
363
-
364
- - another Sass bump
365
-
366
- #### v2.0.0
367
-
368
- > 19 October 2024
369
-
370
- - bump deps
371
- - update example webpack.config file
372
- - bump deps
373
-
374
- #### v1.2.1
375
-
376
- > 19 September 2024
377
-
378
- - plugins updates, config fixes
379
-
380
- #### v1.2.0
381
-
382
- > 19 September 2024
383
-
384
- - deprecation free build! closes #5
385
- - bump deps, remove some for deprecations. Ref #5
386
- - bump deps, update examples and testing
387
- - export webpackConfig
388
-
389
- #### v1.1.14
390
-
391
- > 6 May 2024
392
-
393
- - minor bumps
394
-
395
- #### v1.1.13
396
-
397
- > 10 April 2024
398
-
399
- - bump most all the deps
400
- - add read-package-up, esTarget: es2020
401
- - readme tweak
402
-
403
- #### v1.1.12
404
-
405
- > 4 March 2024
406
-
407
- - readme badge links
408
- - were all these problems just a missing shebang?
409
-
410
- #### v1.1.11
411
-
412
- > 4 March 2024
413
-
414
- - Remove version from plugin builds WIP
415
-
416
- #### v1.1.10
417
-
418
- > 4 March 2024
419
-
420
- - use process.stdout references (of course)
421
-
422
- #### v1.1.9
423
-
424
- > 4 March 2024
425
-
426
- - update actions
427
- - missed a couple readline calls
428
-
429
- #### v1.1.8
430
-
431
- > 4 March 2024
432
-
433
- - quick remove readline function
434
-
435
- #### v1.1.7
436
-
437
- > 4 March 2024
438
-
439
- - extracting the zip script, updated deps
440
- - WIP example webpack.config.js file
441
- - update readme
442
-
443
- #### v1.1.6
444
-
445
- > 30 January 2024
446
-
447
- - test stuff and fixes
448
- - misc fixes and first zip testing
449
-
450
- #### v1.1.5
451
-
452
- > 25 January 2024
453
-
454
- - move the localPort stuff into devserver-proxy
455
-
456
- #### v1.1.4
457
-
458
- > 25 January 2024
459
-
460
- - deps, formatting, debugging
461
-
462
- #### v1.1.3
463
-
464
- > 24 January 2024
465
-
466
- - reconfigure imports, port-reporter to module, lots of deps
467
- - add changelog, update readme (badges)
468
- - fix path to port-reporter script
469
-
470
- #### v1.1.2
471
-
472
- > 23 January 2024
473
-
474
- #### v1.1.1
475
-
476
- > 23 January 2024
477
-
478
- - versioning, github actions, bump deps, README
479
- - update version in readme
480
-
481
- #### v1.1.0
482
-
483
- > 23 January 2024
484
-
485
- - add packages
486
- - name change, prepare for npm publish
487
- - add port-reporter script, giving up on linking local pacakges
488
-
489
- #### v1.0.4
490
-
491
- > 16 January 2024
492
-
493
- - embed default.config
494
-
495
- #### v1.0.3
496
-
497
- > 16 January 2024
498
-
499
- - buildConfig
500
-
501
- #### v1.0.2
502
-
503
- > 16 January 2024
504
-
505
- - add sass-embedded
506
-
507
- #### v1.0.1
508
-
509
- > 16 January 2024
510
-
511
- - add modules
512
- - add lib, temp exports
513
- - Initial commit
File without changes