From 4e45598d70f02e117b9d88e3875ecfbe8fedcf1f Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 15:28:16 -0700 Subject: [PATCH 01/20] Add theme-utils dependencies --- package-lock.json | 637 +++++++++++++++++++++++++++++++++++++++++----- package.json | 11 +- 2 files changed, 578 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index a3d3f480..5cb34e1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,16 @@ "license": "GPL-2.0-or-later", "devDependencies": { "@wordpress/stylelint-config": "^22.5.0", - "stylelint": "^14.16.1" + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "chalk": "^5.3.0", + "fast-glob": "^3.3.2", + "inquirer": "^11.0.2", + "parse5-html-rewriting-stream": "^7.0.0", + "semver": "^7.6.3", + "string-progressbar": "^1.0.4", + "stylelint": "^14.16.1", + "table": "^6.8.2" }, "engines": { "node": ">=20.10.0", @@ -57,6 +66,47 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "node_modules/@csstools/selector-specificity": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", @@ -74,6 +124,225 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", + "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -119,6 +388,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -133,6 +420,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@wordpress/stylelint-config": { "version": "22.5.0", "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-22.5.0.tgz", @@ -156,7 +449,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -168,6 +460,47 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -179,16 +512,18 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/array-union": { @@ -298,36 +633,49 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, "engines": { - "node": ">=4" + "node": ">= 12" } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/colord": { "version": "2.9.3", @@ -458,6 +806,18 @@ "dev": true, "license": "MIT" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -473,11 +833,24 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.0" } }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -490,7 +863,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -714,7 +1086,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -758,6 +1129,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -851,6 +1234,25 @@ "dev": true, "license": "ISC" }, + "node_modules/inquirer": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.0.2.tgz", + "integrity": "sha512-pnbn3nL+JFrTw/pLhzyE/IQ3+gA3n5JxTAZQDjB6qu4gbjOaiTnpZbxT6HY2DDCT7bzDjTTsd3snRP+B6N//Pg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/prompts": "^6.0.1", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "ansi-escapes": "^4.3.2", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "rxjs": "^7.8.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -1159,6 +1561,15 @@ "dev": true, "license": "MIT" }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1214,6 +1625,15 @@ "wrappy": "1" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -1285,6 +1705,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1645,6 +2103,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -1669,12 +2136,26 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1717,42 +2198,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -1799,6 +2244,12 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/string-progressbar": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string-progressbar/-/string-progressbar-1.0.4.tgz", + "integrity": "sha512-OpkcFxlFjn7kz5jTWmPGY+FFJVN21lQ9k0fkK0XS5GVvlCgS1stgDNEoMyqnkbZEcVP3Gv6IxqgG7tnD7ChBSw==", + "dev": true + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1950,7 +2401,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -2019,7 +2469,6 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -2031,6 +2480,18 @@ "node": ">=10.0.0" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2054,6 +2515,12 @@ "node": ">=8" } }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -2067,6 +2534,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2105,6 +2578,20 @@ "which": "bin/which" } }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2152,6 +2639,18 @@ "engines": { "node": ">=10" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 9685360c..8e21b9c3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,16 @@ }, "devDependencies": { "@wordpress/stylelint-config": "^22.5.0", - "stylelint": "^14.16.1" + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "chalk": "^5.3.0", + "fast-glob": "^3.3.2", + "inquirer": "^11.0.2", + "parse5-html-rewriting-stream": "^7.0.0", + "semver": "^7.6.3", + "string-progressbar": "^1.0.4", + "stylelint": "^14.16.1", + "table": "^6.8.2" }, "scripts": { "lint:css": "stylelint **/*.css -i .gitignore", From 318e766f9b1db8b964ec0344c30458fd506b6283 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 15:28:38 -0700 Subject: [PATCH 02/20] Add theme-utils.mjs --- theme-utils.mjs | 1049 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1049 insertions(+) create mode 100644 theme-utils.mjs diff --git a/theme-utils.mjs b/theme-utils.mjs new file mode 100644 index 00000000..de30e50e --- /dev/null +++ b/theme-utils.mjs @@ -0,0 +1,1049 @@ +import { spawn } from 'child_process'; +import fs from 'fs'; +import util from 'util'; +import process from 'process'; +import inquirer from 'inquirer'; +import { RewritingStream } from 'parse5-html-rewriting-stream'; +import { table, getBorderCharacters } from 'table'; +import progressbar from 'string-progressbar'; +import semver from 'semver'; +import Ajv from 'ajv'; +import AjvDraft04 from 'ajv-draft-04'; +import glob from 'fast-glob'; +import chalk, { Chalk } from 'chalk'; + +const isWin = process.platform === 'win32'; + +const commands = { + 'escape-patterns': { + helpText: 'Escapes block patterns for pattern files.', + run: ( args ) => escapePatterns( args[ 1 ].split( /[ ,]+/ ) ), + }, + 'validate-theme': { + helpText: [ + 'Validates a theme against the WordPress theme requirements.', + '--format=FORMAT', + wrapIndent( 'Output format. Possible values: *table*, json, dir.' ), + '--color=WHEN', + wrapIndent( + 'Colorize the output for table or dir formats. The automatic mode only enables colors if an interactive terminal is detected. Possible values: *auto*, always, never.' + ), + '--table-width=COLUMNS', + wrapIndent( + 'Explicitly set the width of the table format instead of determining it automatically. Will default to 120 if omitted and width cannot be determined automatically.' + ), + ].join( '\n\n' ), + additionalArgs: + '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', + run: async ( args ) => { + args.shift(); + const options = {}; + while ( args[ 0 ].startsWith( '--' ) ) { + const flag = args.shift().slice( 2 ); + const [ key, value ] = flag.split( '=' ); + const camelCaseKey = key.replace( /-([a-z])/g, ( [ , c ] ) => + c.toUpperCase() + ); + options[ camelCaseKey ] = value ?? true; + } + const themes = args[ 0 ].split( /[ ,]+/ ); + await validateThemes( themes, options ); + }, + }, + help: { + helpText: 'Displays the main help message.', + run: ( args ) => showHelp( args?.[ 1 ] ), + }, +}; + +( async function start() { + let args = process.argv.slice( 2 ); + let command = args?.[ 0 ]; + + if ( ! commands[ command ] ) { + showHelp(); + process.exit( 1 ); + } + + await commands[ command ].run( args ); +} )(); + +function wrapIndent( + text, + indent = ' ', + newline = '\n', + width = process.stdout.columns || 80 +) { + return text + .match( + new RegExp( + `.{1,${ width - indent.length - 1 }}(\\s+|$)|[^\\s]+?(\\s+|$)`, + 'g' + ) + ) + .map( ( line ) => indent + line ) + .join( newline ); +} + +function showHelp( command = '' ) { + if ( ! command || ! commands.hasOwnProperty( command ) ) { + console.log( ` +node theme-utils.mjs [command] + +Available commands: +_(theme-utils.mjs help [command] for more details)_ + +\t${ Object.keys( commands ).join( '\n\t' ) } + ` ); + return; + } + + const { helpText, additionalArgs } = commands[ command ]; + console.log( ` +${ command } ${ additionalArgs ?? '' } + +${ helpText } + ` ); +} + +export function getThemeMetadata( styleCss, attribute ) { + if ( ! styleCss || ! attribute ) { + return null; + } + + return styleCss + .match( + new RegExp( + `(?<=${ attribute }:\\s*).*?(?=\\s*\\r?\\n|\\rg)`, + 'gs' + ) + )?.[ 0 ] + ?.trim(); +} + +/* + Execute a command locally. +*/ +export async function executeCommand( command, logResponse ) { + const timeout = 2 * 60 * 1000; // 2 min + + return new Promise( ( resolove, reject ) => { + let child; + let response = ''; + let errResponse = ''; + + if ( isWin ) { + child = spawn( 'cmd.exe', [ '/s', '/c', '"' + command + '"' ], { + windowsVerbatimArguments: true, + stdio: [ process.stdin, 'pipe', 'pipe' ], + detached: true, + } ); + } else { + /* + * Determines the shell to execute the command. + * - Prioritizes using the user's default shell unless it's fish, a known problematic shell. + * - In this case, falls back to `/bin/bash` for better syntax compatibility. + */ + let shellPath = process.env.SHELL || '/bin/bash'; + if ( + shellPath.includes( 'fish' ) && + fs.existsSync( '/bin/bash' ) + ) { + shellPath = '/bin/bash'; + } + + child = spawn( shellPath, [ '-c', command ], { + stdio: [ process.stdin, 'pipe', 'pipe' ], + detached: true, + } ); + } + + var timer = setTimeout( () => { + try { + process.kill( -child.pid, 'SIGKILL' ); + } catch ( e ) { + console.log( 'Cannot kill process' ); + } + }, timeout ); + + child.stdout.on( 'data', ( data ) => { + response += data; + if ( logResponse ) { + console.log( data.toString() ); + } + } ); + + child.stderr.on( 'data', ( data ) => { + errResponse += data; + if ( logResponse ) { + console.log( data.toString() ); + } + } ); + + child.on( 'exit', ( code ) => { + clearTimeout( timer ); + if ( code !== 0 ) { + reject( errResponse.trim() ); + } + resolove( response.trim() ); + } ); + } ); +} + +async function escapePatterns( themes ) { + // all patterns php + const patterns = await Promise.all( + themes.map( ( themeSlug ) => glob( `${ themeSlug }/patterns/*.php` ) ) + ).then( ( globs ) => globs.flat() ); + + // arrange patterns by theme + const themePatterns = patterns.reduce( ( acc, file ) => { + console.log( file ); + const themeSlug = file.split( '/' ).shift(); + return { + ...acc, + [ themeSlug ]: ( acc[ themeSlug ] || [] ).concat( file ), + }; + }, {} ); + + Object.entries( themePatterns ).forEach( + async ( [ themeSlug, patterns ] ) => { + console.log( getPatternTable( themeSlug, patterns ) ); + + const prompt = await inquirer.prompt( [ + { + type: 'input', + message: 'Verify the theme slug', + name: 'themeSlug', + default: themeSlug, + }, + ] ); + + if ( ! prompt.themeSlug ) { + return; + } + + patterns.forEach( ( file ) => { + const rewriter = getReWriter( prompt.themeSlug ); + const tmpFile = `${ file }-tmp`; + const readStream = fs.createReadStream( file, { + encoding: 'UTF-8', + } ); + const writeStream = fs.createWriteStream( tmpFile, { + encoding: 'UTF-8', + } ); + writeStream.on( 'finish', () => { + fs.renameSync( tmpFile, file ); + } ); + + readStream.pipe( rewriter ).pipe( writeStream ); + } ); + } + ); + + // Helper functions + function getReWriter( themeSlug ) { + const rewriter = new RewritingStream(); + + rewriter.on( 'text', ( _, raw ) => { + rewriter.emitRaw( escapeText( raw, themeSlug ) ); + } ); + + rewriter.on( 'startTag', ( startTag, rawHtml ) => { + if ( startTag.tagName === 'img' ) { + const attrs = startTag.attrs.filter( ( attr ) => + [ 'src', 'alt' ].includes( attr.name ) + ); + attrs.forEach( ( attr ) => { + if ( attr.name === 'src' ) { + attr.value = escapeImagePath( attr.value ); + } else if ( attr.name === 'alt' ) { + attr.value = escapeText( attr.value, themeSlug, true ); + } + } ); + } + + rewriter.emitStartTag( startTag ); + } ); + + rewriter.on( 'comment', ( comment, rawHtml ) => { + if ( comment.text.startsWith( '?php' ) ) { + rewriter.emitRaw( rawHtml ); + return; + } + // escape the strings in block config (blocks that are represented as comments) + // ex: + const block = escapeBlockAttrs( comment.text, themeSlug ); + rewriter.emitComment( { ...comment, text: block } ); + } ); + + return rewriter; + } + + function escapeBlockAttrs( block, themeSlug ) { + // Set isAttr to true if it is an attribute in the result HTML + // If set to true, it generates esc_attr_, otherwise it generates esc_html_ + const allowedAttrs = [ + { name: 'label' }, + { name: 'placeholder', isAttr: true }, + { name: 'buttonText' }, + { name: 'content' }, + ]; + const start = block.indexOf( '{' ); + const end = block.lastIndexOf( '}' ); + + const configPrefix = block.slice( 0, start ); + const config = block.slice( start, end + 1 ); + const configSuffix = block.slice( end + 1 ); + + try { + const configJson = JSON.parse( config ); + allowedAttrs.forEach( ( attr ) => { + if ( ! configJson[ attr.name ] ) return; + configJson[ attr.name ] = escapeText( + configJson[ attr.name ], + themeSlug, + attr.isAttr + ); + } ); + return configPrefix + JSON.stringify( configJson ) + configSuffix; + } catch ( error ) { + // do nothing + return block; + } + } + + function escapeText( text, themeSlug, isAttr = false ) { + const trimmedText = text && text.trim(); + if ( ! themeSlug || ! trimmedText || trimmedText.startsWith( ``; + } + + function escapeImagePath( src ) { + if ( ! src || src.trim().startsWith( '/${ resultSrc }`; + } + + function getPatternTable( themeSlug, patterns ) { + const tableConfig = { + columnDefault: { + width: 40, + }, + header: { + alignment: 'center', + content: `THEME: ${ themeSlug }\n\n Following patterns may get updated with escaped strings and/or image paths`, + }, + }; + + return table( + patterns.map( ( p ) => [ p ] ), + tableConfig + ); + } +} +/** + * Validates theme files against their respective JSON schemas. + * + * @param {string} themes List of theme directories to validate + * @param {Object} options Options for the validation + * @param {string} options.format Output format (table, json, dir) + * @param {string} options.color Colorize output (auto, always, never) + * @param {number} options.tableWidth Width of the table output + */ +async function validateThemes( themes, { format, color, tableWidth } ) { + util.inspect.styles.name = 'whiteBright'; + switch ( color ) { + case 'always': + chalk.level = 1; + break; + case 'never': + chalk.level = 0; + break; + } + const isColorized = chalk.level > 0; + + const chalkStr = new Chalk( { + level: ! format || format === 'table' ? 1 : 0, + } ); + + function readJson( file ) { + return fs.promises.readFile( file, 'utf-8' ).then( JSON.parse ); + } + + async function loadSchema( uri ) { + if ( ! uri ) { + // prettier-ignore + throw { + url: uri, + message: `Missing $schema URI: ${ chalkStr.whiteBright( uri ) }`, + }; + } + + let schema; + if ( URL.canParse( uri ) ) { + const url = new URL( uri ); + switch ( url.protocol ) { + case 'http:': + case 'https:': + { + const res = await fetch( url ); + if ( ! res.ok ) { + throw { + type: res.type, + url: res.url, + redirected: res.redirected, + status: res.status, + ok: res.ok, + statusText: res.statusText, + headers: res.headers, + message: await res.text(), + }; + } + schema = await res.json(); + } + break; + case 'file:': { + schema = readJson( + path.resolve( dirname, url.href.slice( 7 ) ) + ); + break; + } + default: + // prettier-ignore + throw { + url: uri, + message: `Unsupported ${ chalkStr.whiteBright( '$schema' ) } protocol: ${ chalkStr.whiteBright( url.protocol + '//' ) }`, + }; + } + } else { + schema = await readJson( path.resolve( dirname, uri ) ); + } + + // Handle schemastore $ref for older schemas + if ( ! schema.$schema && typeof schema.$ref === 'string' ) { + return loadSchema( schema.$ref ); + } + + return schema; + } + + const ajvOptions = { + strict: false, + allErrors: true, + loadSchema, + }; + const ajv = { + 'http://json-schema.org/draft-07/schema#': new Ajv( ajvOptions ), + 'http://json-schema.org/draft-04/schema#': new AjvDraft04( ajvOptions ), + }; + + const progress = startProgress( themes.length ); + + let problems = []; + for ( const themeSlug of themes ) { + const styleCssPath = `${ themeSlug }/style.css`; + const themeJsonPath = `${ themeSlug }/theme.json`; + + if ( ! fs.existsSync( themeSlug ) ) { + problems.push( + createProblem( { + type: 'error', + theme: themeSlug, + file: chalkStr.gray( 'undefined' ), + data: { message: `the theme does not exist` }, + } ) + ); + progress.increment(); + continue; + } + + if ( ! fs.existsSync( styleCssPath ) ) { + problems.push( + createProblem( { + type: 'error', + file: styleCssPath, + data: { message: `the theme is missing a style.css file` }, + } ) + ); + progress.increment(); + continue; + } + + const styleCss = await fs.promises.readFile( styleCssPath, 'utf-8' ); + const themeRequires = getThemeMetadata( + styleCss, + 'Requires at least', + true + ); + const wpVersion = themeRequires + ? `${ themeRequires }.0`.split( '.', 2 ).join( '.' ) + : undefined; + const hasThemeJsonSupport = + wpVersion && + semver.valid( `${ wpVersion }.0` ) && + semver.gte( `${ wpVersion }.0`, '5.9.0' ); + const hasThemeJson = fs.existsSync( themeJsonPath ); + + if ( hasThemeJson && ! hasThemeJsonSupport ) { + problems.push( + createProblem( { + type: 'warning', + file: styleCssPath, + // prettier-ignore + data: { + actual: chalkStr.yellow( wpVersion ), + expected: `${ chalkStr.yellow( '5.9' ) } or greater`, + message: `the ${ chalkStr.green( "'Requires at least'" ) } version does not support theme.json`, + }, + } ) + ); + } + + const validators = { + validateVersion( attr, value, validLengths = [ 3 ] ) { + const problems = []; + const adjustedValue = + value && `${ value }.0`.split( '.', 3 ).join( '.' ); + if ( + ! value || + ! validLengths.includes( value.split( '.' ).length ) || + ! semver.valid( adjustedValue ) + ) { + problems.push( { + actual: `${ chalkStr.green( + attr + ) }: ${ chalkStr.yellow( value ) }`, + expected: `format ${ chalkStr.yellow( + Array.from( { length: Math.min( validLengths ) } ) + .fill( 'x' ) + .join( '.' ) + ) }`, + message: `${ value } is not a valid version`, + } ); + } + return { isValid: ! problems.length, problems }; + }, + validateVersionGte( attr, value, version ) { + const problems = []; + const adjustedValue = + value && `${ value }.0`.split( '.', 3 ).join( '.' ); + const adjustedVersion = + version && `${ version }.0`.split( '.', 3 ).join( '.' ); + if ( + ! value || + ! version || + ! semver.valid( adjustedValue ) || + ! semver.valid( adjustedVersion ) || + ! semver.gte( adjustedValue, adjustedVersion ) + ) { + problems.push( { + actual: `${ chalkStr.green( + attr + ) }: ${ chalkStr.yellow( value ) }`, + expected: `${ chalkStr.yellow( version ) } or greater`, + message: `provide a valid version value`, + } ); + } + return { isValid: ! problems.length, problems }; + }, + validateUri: ( attr, value ) => { + const problems = []; + if ( value && ! URL.canParse( value ) ) { + problems.push( { + actual: `${ chalkStr.green( + attr + ) }: ${ chalkStr.yellow( value ) }`, + expected: `a valid URI`, + message: `${ value } is not a valid URI`, + } ); + } + return { isValid: ! problems.length, problems }; + }, + validateThemeSlug: ( attr, value ) => { + const problems = []; + if ( value && ! /^[a-z0-9-]+$/.test( value ) ) { + problems.push( { + actual: `${ chalkStr.green( + attr + ) }: ${ chalkStr.yellow( value ) }`, + expected: `a valid value`, + message: `${ value } is not a valid value`, + } ); + } + return { isValid: ! problems.length, problems }; + }, + }; + + // validate style.css metadata + // Spec: https://developer.wordpress.org/themes/basics/main-stylesheet-style-css/ + const styleCssMetadata = [ + { attribute: 'Theme Name', required: true }, + { + attribute: 'Theme URI', + validators: [ + { + validate: validators.validateUri, + type: 'warning', + }, + ], + }, + { + attribute: 'Author URI', + validators: [ + { + validate: validators.validateUri, + type: 'warning', + }, + ], + }, + { attribute: 'Description', required: true }, + { + attribute: 'Version', + required: true, + validators: [ + { + validate: ( attr, value ) => + validators.validateVersion( attr, value, [ 3 ] ), + type: 'error', + }, + ], + }, + { + attribute: 'Requires at least', + required: true, + validators: [ + { + validate: ( attr, value ) => + validators.validateVersion( attr, value, [ 2 ] ), + type: 'error', + }, + ], + }, + { + attribute: 'Tested up to', + required: true, + validators: [ + { + validate: ( attr, value ) => + validators.validateVersion( attr, value, [ 2, 3 ] ), + type: 'error', + }, + { + validate: ( attr, value ) => + validators.validateVersionGte( + attr, + value, + themeRequires + ), + type: 'error', + }, + ], + }, + { + attribute: 'Requires PHP', + required: true, + validators: [ + { + validate: ( attr, value ) => + validators.validateVersion( attr, value, [ 2 ] ), + type: 'error', + }, + ], + }, + { attribute: 'License', required: true }, + { + attribute: 'License URI', + required: true, + validators: [ + { + validate: validators.validateUri, + type: 'warning', + }, + ], + }, + { + attribute: 'Text Domain', + required: true, + validators: [ + { + validate: validators.validateThemeSlug, + type: 'error', + }, + ], + }, + ]; + + styleCssMetadata.forEach( ( { attribute, required, validators } ) => { + const attributeValue = getThemeMetadata( styleCss, attribute ); + if ( ! attributeValue ) { + problems.push( + createProblem( { + type: required ? 'error' : 'warning', + file: styleCssPath, + data: { + message: `missing ${ chalkStr.green( + attribute + ) } header metadata`, + }, + } ) + ); + } else if ( validators ) { + validators.forEach( ( { validate, type } ) => { + const { isValid, problems: validationProblems } = validate( + attribute, + attributeValue + ); + if ( ! isValid ) { + problems = problems.concat( + validationProblems.map( ( problem ) => + createProblem( { + type: type, + file: styleCssPath, + data: problem, + } ) + ) + ); + } + } ); + } + } ); + + const validations = await Promise.all( [ + glob( `${ themeSlug }/styles/*.json` ).then( ( paths ) => ( { + schemaType: 'theme', + paths: [ `${ themeSlug }/theme.json`, ...paths ], + } ) ), + glob( `${ themeSlug }/assets/fonts/*.json` ).then( ( paths ) => ( { + schemaType: 'font-collection', + paths, + } ) ), + ] ); + + for ( const { schemaType, paths } of validations ) { + for ( const file of paths ) { + try { + const data = await readJson( file ); + const schemaUri = hasThemeJsonSupport + ? `https://schemas.wp.org/wp/${ wpVersion }/${ schemaType }.json` + : data.$schema; + + if ( data.$schema !== schemaUri ) { + problems.push( + createProblem( { + type: 'warning', + file, + // prettier-ignore + data: { + actual: data.$schema, + expected: schemaUri, + message: `the ${ chalkStr.whiteBright( '$schema' ) } version does not match style.css ${ chalkStr.green( "'Requires at least'" ) } version`, + }, + } ) + ); + } + + const schema = await loadSchema( schemaUri ); + const validate = + await ajv[ schema.$schema ].compileAsync( schema ); + if ( ! validate( data ) ) { + problems.push( + createProblem( { + type: 'warning', + file, + data: validate.errors, + metadata: { schema: schemaUri }, + } ) + ); + } + } catch ( error ) { + problems.push( + createProblem( { type: 'error', file, data: error } ) + ); + } + } + } + + progress.increment(); + } + + if ( problems.length ) { + let output = ''; + switch ( format ) { + case 'json': + output = JSON.stringify( problems ); + break; + case 'dir': + output = util.inspect( problems, { + depth: null, + maxArrayLength: null, + colors: isColorized, + } ); + break; + case 'table': + default: { + output = problemsToTable( problems, { tableWidth } ); + } + } + await new Promise( ( resolve, reject ) => + process.stdout.write( output, ( error ) => + error ? reject( error ) : resolve() + ) + ); + } + + const hasError = problems.some( ( { type } ) => type === 'error' ); + if ( hasError ) { + if ( process.stdout.isTTY && process.stderr.isTTY ) { + console.error( chalk.red( '\n\nValidation failed.' ) ); + } + process.exit( 1 ); + } + + if ( process.stdout.isTTY ) { + if ( problems.length ) { + console.log( + chalk.yellow( '\n\nValidation passed with warnings.' ) + ); + } else { + console.log( chalk.green( '\n\nValidation passed.' ) ); + } + } +} + +/** + * @typedef {Object} Problem + * @prop {'warning'|'error'} type Type of problem + * @prop {string} theme Name of the theme + * @prop {string} file File path, relative to the theme, where the problem exists + * @prop {Object} metadata Additional metadata to include in logging + * @prop {Object[]} data Array of validation problems + */ + +/** + * Creates a problem object. + * @param {Object} options Options for creating a problem + * @param {'warning'|'error'} options.type Type of problem + * @param {string?} options.theme Name of the theme + * @param {string} options.file File path where the problem exists + * @prop {Object} metadata Additional metadata to include in logging + * @prop {Object[]} data Array of validation problems + * @return {Problem} Problem object + */ +function createProblem( options ) { + const { + type, + metadata, + data: problemData, + theme: themeOverride, + file: themeFile, + } = options; + const separatorIndex = themeFile.indexOf( '/' ); + const theme = themeOverride + ? themeOverride.charAt( 0 ).toUpperCase() + themeOverride.slice( 1 ) + : themeFile.charAt( 0 ).toUpperCase() + + themeFile.slice( 1, separatorIndex ); + const file = themeFile.slice( separatorIndex + 1 ); + const data = Array.isArray( problemData ) ? problemData : [ problemData ]; + return { type, theme, file, metadata, data }; +} + +/** + * Similar to Object.entries, but includes non-enumerable properties and + * traverses the prototype chain. + * + * @param {Object} obj Any object + * @return {Array<[string, any]>} An array of key-value pairs + */ +function objectOwnPropertiesEntries( obj ) { + const visited = new Set(); + const propertyNames = new Set(); + + let currentObj = obj; + while ( currentObj !== null ) { + if ( visited.has( currentObj ) ) { + break; + } + visited.add( currentObj ); + + for ( const name of Object.getOwnPropertyNames( currentObj ) ) { + propertyNames.add( name ); + } + + currentObj = Object.getPrototypeOf( currentObj ); + } + + return [ ...propertyNames ].map( ( key ) => [ key, obj[ key ] ] ); +} + +/** + * Converts an object into a borderless table format. + * + * Example: + * objectToTable( { + * keyOne: 'value1', + * keyTwo: 'value2', + * keyThree: 3, + * fn: () => {}, + * obj: {}, + * } ) + * // Returns: + * // 'key one : value1\n' + + * // 'key two : value2\n' + + * // 'key three : 3' + * + * @param {Object} obj Object to convert into a table + * @param {Object} [extraOptions] Extra options for the table + * + * @return {string} Table string + */ +function objectToTable( obj = {}, extraOptions ) { + const data = objectOwnPropertiesEntries( obj ) + .filter( + ( [ key, value ] ) => + typeof value !== 'function' && ! key.startsWith( '_' ) + ) + .map( ( [ key, value ] ) => [ + key + .split( /(?=[A-Z0-9])/g ) + .map( ( part, i ) => + i === 0 + ? part.charAt( 0 ).toUpperCase() + part.slice( 1 ) + : part.charAt( 0 ).toLowerCase() + part.slice( 1 ) + ) + .join( ' ' ), + typeof value === 'object' + ? util.inspect( value, { colors: chalk.level > 0 } ) + : value, + ] ); + + const options = { + columns: [ { paddingLeft: 0 }, { paddingRight: 0 } ], + border: getBorderCharacters( 'void' ), // No border, modified below. + drawHorizontalLine: () => false, + }; + options.border.bodyJoin = ':'; + + // Hack for alignment with other tables. + if ( extraOptions?.columns?.[ 0 ]?.minWidth ) { + options.columns[ 0 ].width = Math.max( + extraOptions.columns[ 0 ].minWidth, + ...data.map( ( [ key ] ) => key.length ) + ); + } + + return table( data, options ).slice( 0, -1 ); // Remove trailing newline. +} + +/** + * Generates a table in the following format. + * + * ╔═══════════════════════════════════════════════════╤═══════════════════════╗ + * ║ WARNING │ Warning key 0 : value ║ + * ║ Theme : Example │ Warning key 1 : value ║ + * ║ File : style.css │ ║ + * ╟───────────────────────────────────────────────────┼───────────────────────╢ + * ║ ERROR │ Error 0 key 0 : value ║ + * ║ Theme : Example │ Error 0 key 1 : value ║ + * ║ File : theme.json │ Error 0 key 2 : value ║ + * ║ Schema : https://schemas.wp.org/wp/X.Y/theme.json ├───────────────────────╢ + * ║ │ Error 1 key 0 : value ║ + * ║ │ Error 1 key 1 : value ║ + * ║ │ Error 1 key 2 : value ║ + * ╚═══════════════════════════════════════════════════╧═══════════════════════╝ + * + * It has a very basic dynamic column width calculation where the first column + * expands to the content and the second column uses the remaining width of the + * terminal. Each column has a minimum width of 20 characters. + * + * @param {Problem[]} problems List of problems to format + * + * @return {string} Table string + */ +function problemsToTable( problems, options ) { + const tableWidth = options.tableWidth || process.stdout.columns || 120; + const paddingAndBorderWidth = '║ │ ║'.length; + const columnMinWidth = 20; + + const userConfig = { + columns: [ + { width: columnMinWidth }, + { width: tableWidth - columnMinWidth - paddingAndBorderWidth }, + ], + spanningCells: [], + }; + const tableData = []; + + for ( const { type, theme, file, data, metadata } of problems ) { + const metadataTable = metadata ? objectToTable( metadata ) : ''; + const titleTable = objectToTable( + { theme, file }, + { columns: [ { minWidth: metadataTable.indexOf( ':' ) - 1 } ] } + ); + + const rows = data.map( ( m ) => [ '', objectToTable( m ) ] ); + rows[ 0 ][ 0 ] = [ + chalk[ type === 'warning' ? 'yellow' : 'red' ].bold( + type.toUpperCase() + ), + chalk.whiteBright( titleTable ), + metadataTable, + ] + .filter( Boolean ) + .join( '\n' ); + + tableData.push( ...rows ); + + userConfig.spanningCells.push( { + row: tableData.length - rows.length, + col: 0, + rowSpan: rows.length, + } ); + + userConfig.columns[ 0 ].width = Math.max( + userConfig.columns[ 0 ].width, + ...rows[ 0 ][ 0 ].split( '\n' ).map( ( s ) => s.length ) + ); + userConfig.columns[ 1 ].width = Math.max( + columnMinWidth, + tableWidth - userConfig.columns[ 0 ].width - paddingAndBorderWidth + ); + } + + return table( tableData, userConfig ).slice( 0, -1 ); // Remove trailing newline. +} + +function startProgress( length ) { + if ( ! process.stdout.isTTY ) { + return { increment() {} }; + } + + let current = 0; + function render() { + const [ progress, percentage ] = progressbar.filledBar( + length, + current + ); + console.log( + '\nProgress:', + [ progress, Math.round( percentage * 100 ) / 100 ], + `${ current }/${ length }\n` + ); + } + + render(); + return { + increment() { + current++; + process.stdout.moveCursor?.( 0, -3 ); + render(); + }, + }; +} From 9e76c05fbe4852584d61e244c6a768b34a3c1fe2 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 15:44:43 -0700 Subject: [PATCH 03/20] Remove unused function --- theme-utils.mjs | 69 ------------------------------------------------- 1 file changed, 69 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index de30e50e..09142741 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -121,75 +121,6 @@ export function getThemeMetadata( styleCss, attribute ) { ?.trim(); } -/* - Execute a command locally. -*/ -export async function executeCommand( command, logResponse ) { - const timeout = 2 * 60 * 1000; // 2 min - - return new Promise( ( resolove, reject ) => { - let child; - let response = ''; - let errResponse = ''; - - if ( isWin ) { - child = spawn( 'cmd.exe', [ '/s', '/c', '"' + command + '"' ], { - windowsVerbatimArguments: true, - stdio: [ process.stdin, 'pipe', 'pipe' ], - detached: true, - } ); - } else { - /* - * Determines the shell to execute the command. - * - Prioritizes using the user's default shell unless it's fish, a known problematic shell. - * - In this case, falls back to `/bin/bash` for better syntax compatibility. - */ - let shellPath = process.env.SHELL || '/bin/bash'; - if ( - shellPath.includes( 'fish' ) && - fs.existsSync( '/bin/bash' ) - ) { - shellPath = '/bin/bash'; - } - - child = spawn( shellPath, [ '-c', command ], { - stdio: [ process.stdin, 'pipe', 'pipe' ], - detached: true, - } ); - } - - var timer = setTimeout( () => { - try { - process.kill( -child.pid, 'SIGKILL' ); - } catch ( e ) { - console.log( 'Cannot kill process' ); - } - }, timeout ); - - child.stdout.on( 'data', ( data ) => { - response += data; - if ( logResponse ) { - console.log( data.toString() ); - } - } ); - - child.stderr.on( 'data', ( data ) => { - errResponse += data; - if ( logResponse ) { - console.log( data.toString() ); - } - } ); - - child.on( 'exit', ( code ) => { - clearTimeout( timer ); - if ( code !== 0 ) { - reject( errResponse.trim() ); - } - resolove( response.trim() ); - } ); - } ); -} - async function escapePatterns( themes ) { // all patterns php const patterns = await Promise.all( From 521c8fbee5e53569ad95dd989e9321658a117767 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 15:51:06 -0700 Subject: [PATCH 04/20] Replace echo esc with _e version --- theme-utils.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index 09142741..6b1564a6 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -248,10 +248,10 @@ async function escapePatterns( themes ) { const trimmedText = text && text.trim(); if ( ! themeSlug || ! trimmedText || trimmedText.startsWith( ``; + return `${ spaceChar }`; } function escapeImagePath( src ) { From 19895577a3347234e7c0c564d213e410f4734f88 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:01:35 -0700 Subject: [PATCH 05/20] Remove unused code --- theme-utils.mjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index 6b1564a6..add158c9 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -1,4 +1,3 @@ -import { spawn } from 'child_process'; import fs from 'fs'; import util from 'util'; import process from 'process'; @@ -12,8 +11,6 @@ import AjvDraft04 from 'ajv-draft-04'; import glob from 'fast-glob'; import chalk, { Chalk } from 'chalk'; -const isWin = process.platform === 'win32'; - const commands = { 'escape-patterns': { helpText: 'Escapes block patterns for pattern files.', From ee5013ae9f5ee6bfdbb18e6f8fce06fba505ede1 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:02:17 -0700 Subject: [PATCH 06/20] Add additionalArgs --- theme-utils.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/theme-utils.mjs b/theme-utils.mjs index add158c9..3f8d5abb 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -14,6 +14,7 @@ import chalk, { Chalk } from 'chalk'; const commands = { 'escape-patterns': { helpText: 'Escapes block patterns for pattern files.', + additionalArgs: '', run: ( args ) => escapePatterns( args[ 1 ].split( /[ ,]+/ ) ), }, 'validate-theme': { From e7197f1a6c8419038f9ae8daf6a7a17e3276650d Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:42:50 -0700 Subject: [PATCH 07/20] Refactor escapePatterns to accept a list of domains --- theme-utils.mjs | 114 ++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index 3f8d5abb..23e7b5ea 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -1,7 +1,6 @@ import fs from 'fs'; import util from 'util'; import process from 'process'; -import inquirer from 'inquirer'; import { RewritingStream } from 'parse5-html-rewriting-stream'; import { table, getBorderCharacters } from 'table'; import progressbar from 'string-progressbar'; @@ -13,9 +12,20 @@ import chalk, { Chalk } from 'chalk'; const commands = { 'escape-patterns': { - helpText: 'Escapes block patterns for pattern files.', - additionalArgs: '', - run: ( args ) => escapePatterns( args[ 1 ].split( /[ ,]+/ ) ), + helpText: [ + 'Escapes block patterns for pattern files.', + '--domains=DOMAINS', + wrapIndent( 'Array of domains mapping to themes' ), + ].join( '\n\n' ), + additionalArgs: '[--domains=DOMAINS] ', + run: ( args ) => { + const [ options, rest ] = parseFlags( args ); + if ( typeof options.domains === 'string' ) { + options.domains = options.domains.split( /[ ,]+/ ); + } + const themes = rest[ 0 ].split( /[ ,]+/ ); + escapePatterns( themes, options ); + }, }, 'validate-theme': { helpText: [ @@ -34,17 +44,8 @@ const commands = { additionalArgs: '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', run: async ( args ) => { - args.shift(); - const options = {}; - while ( args[ 0 ].startsWith( '--' ) ) { - const flag = args.shift().slice( 2 ); - const [ key, value ] = flag.split( '=' ); - const camelCaseKey = key.replace( /-([a-z])/g, ( [ , c ] ) => - c.toUpperCase() - ); - options[ camelCaseKey ] = value ?? true; - } - const themes = args[ 0 ].split( /[ ,]+/ ); + const [ options, rest ] = parseFlags( args ); + const themes = rest[ 0 ].split( /[ ,]+/ ); await validateThemes( themes, options ); }, }, @@ -66,6 +67,21 @@ const commands = { await commands[ command ].run( args ); } )(); +function parseFlags( originalArgs ) { + const args = [ ...originalArgs ]; + args.shift(); + const options = {}; + while ( args[ 0 ].startsWith( '--' ) ) { + const flag = args.shift().slice( 2 ); + const [ key, value ] = flag.split( '=' ); + const camelCaseKey = key.replace( /-([a-z])/g, ( [ , c ] ) => + c.toUpperCase() + ); + options[ camelCaseKey ] = value ?? true; + } + return [ options, args ]; +} + function wrapIndent( text, indent = ' ', @@ -119,56 +135,29 @@ export function getThemeMetadata( styleCss, attribute ) { ?.trim(); } -async function escapePatterns( themes ) { - // all patterns php - const patterns = await Promise.all( - themes.map( ( themeSlug ) => glob( `${ themeSlug }/patterns/*.php` ) ) - ).then( ( globs ) => globs.flat() ); - - // arrange patterns by theme - const themePatterns = patterns.reduce( ( acc, file ) => { - console.log( file ); - const themeSlug = file.split( '/' ).shift(); - return { - ...acc, - [ themeSlug ]: ( acc[ themeSlug ] || [] ).concat( file ), - }; - }, {} ); - - Object.entries( themePatterns ).forEach( - async ( [ themeSlug, patterns ] ) => { - console.log( getPatternTable( themeSlug, patterns ) ); - - const prompt = await inquirer.prompt( [ - { - type: 'input', - message: 'Verify the theme slug', - name: 'themeSlug', - default: themeSlug, - }, - ] ); - - if ( ! prompt.themeSlug ) { - return; - } +async function escapePatterns( themes, options ) { + for ( const themeSlug of themes ) { + const domain = options?.domains?.[ i ] ?? themeSlug; + const patterns = await glob( `${ themeSlug }/patterns/*.php` ); - patterns.forEach( ( file ) => { - const rewriter = getReWriter( prompt.themeSlug ); - const tmpFile = `${ file }-tmp`; - const readStream = fs.createReadStream( file, { - encoding: 'UTF-8', - } ); - const writeStream = fs.createWriteStream( tmpFile, { - encoding: 'UTF-8', - } ); - writeStream.on( 'finish', () => { - fs.renameSync( tmpFile, file ); - } ); + console.log( getPatternTable( themeSlug, patterns ) ); - readStream.pipe( rewriter ).pipe( writeStream ); + patterns.forEach( ( file ) => { + const rewriter = getReWriter( domain ); + const tmpFile = `${ file }-tmp`; + const readStream = fs.createReadStream( file, { + encoding: 'UTF-8', } ); - } - ); + const writeStream = fs.createWriteStream( tmpFile, { + encoding: 'UTF-8', + } ); + writeStream.on( 'finish', () => { + fs.renameSync( tmpFile, file ); + } ); + + readStream.pipe( rewriter ).pipe( writeStream ); + } ); + } // Helper functions function getReWriter( themeSlug ) { @@ -278,6 +267,7 @@ async function escapePatterns( themes ) { ); } } + /** * Validates theme files against their respective JSON schemas. * From 81d1dfb40b1228b0fb5a51bb2301fa013137eae2 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:43:08 -0700 Subject: [PATCH 08/20] Remove inquirer dependency --- package-lock.json | 423 ---------------------------------------------- package.json | 1 - 2 files changed, 424 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5cb34e1e..b40ce3f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "ajv-draft-04": "^1.0.0", "chalk": "^5.3.0", "fast-glob": "^3.3.2", - "inquirer": "^11.0.2", "parse5-html-rewriting-stream": "^7.0.0", "semver": "^7.6.3", "string-progressbar": "^1.0.4", @@ -124,225 +123,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/@inquirer/checkbox": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", - "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/confirm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", - "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", - "dev": true, - "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@inquirer/editor": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", - "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0", - "external-editor": "^3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/expand": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", - "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", - "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", - "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/number": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", - "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/password": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", - "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0", - "ansi-escapes": "^4.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/prompts": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", - "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", - "dev": true, - "dependencies": { - "@inquirer/checkbox": "^3.0.1", - "@inquirer/confirm": "^4.0.1", - "@inquirer/editor": "^3.0.1", - "@inquirer/expand": "^3.0.1", - "@inquirer/input": "^3.0.1", - "@inquirer/number": "^2.0.1", - "@inquirer/password": "^3.0.1", - "@inquirer/rawlist": "^3.0.1", - "@inquirer/search": "^2.0.1", - "@inquirer/select": "^3.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/rawlist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", - "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/type": "^2.0.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/search": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", - "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/select": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", - "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", - "dev": true, - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -388,24 +168,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "22.5.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", - "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", - "dev": true, - "dependencies": { - "undici-types": "~6.19.2" - } - }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -420,12 +182,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true - }, "node_modules/@wordpress/stylelint-config": { "version": "22.5.0", "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-22.5.0.tgz", @@ -474,33 +230,6 @@ } } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -644,21 +373,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -837,20 +551,6 @@ "node": ">=0.8.0" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1129,18 +829,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -1234,25 +922,6 @@ "dev": true, "license": "ISC" }, - "node_modules/inquirer": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.0.2.tgz", - "integrity": "sha512-pnbn3nL+JFrTw/pLhzyE/IQ3+gA3n5JxTAZQDjB6qu4gbjOaiTnpZbxT6HY2DDCT7bzDjTTsd3snRP+B6N//Pg==", - "dev": true, - "dependencies": { - "@inquirer/core": "^9.2.1", - "@inquirer/prompts": "^6.0.1", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "ansi-escapes": "^4.3.2", - "mute-stream": "^1.0.0", - "run-async": "^3.0.0", - "rxjs": "^7.8.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -1561,15 +1230,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1625,15 +1285,6 @@ "wrappy": "1" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -2103,15 +1754,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2136,21 +1778,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -2480,18 +2107,6 @@ "node": ">=10.0.0" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2515,12 +2130,6 @@ "node": ">=8" } }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true - }, "node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -2534,12 +2143,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2578,20 +2181,6 @@ "which": "bin/which" } }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2639,18 +2228,6 @@ "engines": { "node": ">=10" } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index 8e21b9c3..6f9cf1c3 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "ajv-draft-04": "^1.0.0", "chalk": "^5.3.0", "fast-glob": "^3.3.2", - "inquirer": "^11.0.2", "parse5-html-rewriting-stream": "^7.0.0", "semver": "^7.6.3", "string-progressbar": "^1.0.4", From 91766cfcc1d469e6de93284eb54d88ead44efd86 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:50:09 -0700 Subject: [PATCH 09/20] Add textDomain to table --- theme-utils.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index 23e7b5ea..1f79f846 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -137,13 +137,13 @@ export function getThemeMetadata( styleCss, attribute ) { async function escapePatterns( themes, options ) { for ( const themeSlug of themes ) { - const domain = options?.domains?.[ i ] ?? themeSlug; + const textDomain = options?.domains?.[ i ] ?? themeSlug; const patterns = await glob( `${ themeSlug }/patterns/*.php` ); - console.log( getPatternTable( themeSlug, patterns ) ); + console.log( getPatternTable( themeSlug, textDomain, patterns ) ); patterns.forEach( ( file ) => { - const rewriter = getReWriter( domain ); + const rewriter = getReWriter( textDomain ); const tmpFile = `${ file }-tmp`; const readStream = fs.createReadStream( file, { encoding: 'UTF-8', @@ -160,11 +160,11 @@ async function escapePatterns( themes, options ) { } // Helper functions - function getReWriter( themeSlug ) { + function getReWriter( textDomain ) { const rewriter = new RewritingStream(); rewriter.on( 'text', ( _, raw ) => { - rewriter.emitRaw( escapeText( raw, themeSlug ) ); + rewriter.emitRaw( escapeText( raw, textDomain ) ); } ); rewriter.on( 'startTag', ( startTag, rawHtml ) => { @@ -176,7 +176,7 @@ async function escapePatterns( themes, options ) { if ( attr.name === 'src' ) { attr.value = escapeImagePath( attr.value ); } else if ( attr.name === 'alt' ) { - attr.value = escapeText( attr.value, themeSlug, true ); + attr.value = escapeText( attr.value, textDomain, true ); } } ); } @@ -191,7 +191,7 @@ async function escapePatterns( themes, options ) { } // escape the strings in block config (blocks that are represented as comments) // ex: - const block = escapeBlockAttrs( comment.text, themeSlug ); + const block = escapeBlockAttrs( comment.text, textDomain ); rewriter.emitComment( { ...comment, text: block } ); } ); @@ -250,14 +250,14 @@ async function escapePatterns( themes, options ) { return `/${ resultSrc }`; } - function getPatternTable( themeSlug, patterns ) { + function getPatternTable( themeSlug, textDomain, patterns ) { const tableConfig = { columnDefault: { width: 40, }, header: { alignment: 'center', - content: `THEME: ${ themeSlug }\n\n Following patterns may get updated with escaped strings and/or image paths`, + content: `THEME: ${ themeSlug }\n\nTEXT DOMAIN: ${ textDomain }Following patterns may get updated with escaped strings and/or image paths`, }, }; From 837c08b3dd5c7df46b9df3a9e74eee6e8711cafe Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:50:27 -0700 Subject: [PATCH 10/20] Add docs for scripts in CONTRIBUTING.md --- CONTRIBUTING.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b02b968d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,139 @@ +# Contributing + +## Tools + +### Translation + +```sh-session +$ node theme-utils.mjs help escape-patterns + +escape-patterns [--domains=DOMAINS] + +Escapes block patterns for pattern files. + +--domains=DOMAINS + + Array of domains mapping to themes + +``` + +### Validation + +```sh-session +$ node theme-utils.mjs help validate-theme + +validate-theme [--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] + +Validates a theme against the WordPress theme requirements. + +--format=FORMAT + + Output format. Possible values: *table*, json, dir. + +--color=WHEN + + Colorize the output for table or dir formats. The automatic mode only enables colors if + an interactive terminal is detected. Possible values: *auto*, always, never. + +--table-width=COLUMNS + + Explicitly set the width of the table format instead of determining it automatically. + Will default to 120 if omitted and width cannot be determined automatically. + +``` + +Here's an example of the output. + +image + +This is how you run validation on a single theme: + +```sh-session +$ node theme-utils.mjs validate-theme grammer + +Progress: [ '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■', 100 ] 1/1 + +╔═════════════════════════════════════════════════════════════╤══════════════════════════╗ +║ ERROR │ Message : missing 'Requi ║ +║ Theme : Grammer │ res at least' header met ║ +║ File : style.css │ adata ║ +╟─────────────────────────────────────────────────────────────┼──────────────────────────╢ +║ WARNING │ Actual : undefined ║ +║ Theme : Grammer │ Expected : 5.9 or greate ║ +║ File : style.css │ r ║ +║ │ Message : the 'Requires ║ +║ │ at least' version does ║ +║ │ not support theme.json ║ +╟─────────────────────────────────────────────────────────────┼──────────────────────────╢ +║ WARNING │ Instance path : /setting ║ +║ Theme : Grammer │ s/spacing/spacingScale ║ +║ File : theme.json │ Schema path : #/defini ║ +║ Schema : https://schemas.wp.org/trunk/theme.json │ tions/settingsSpacingPro ║ +║ │ perties/properties/spaci ║ +║ │ ng/properties/spacingSca ║ +║ │ le/additionalProperties ║ +║ │ Keyword : addition ║ +║ │ alProperties ║ +║ │ Params : { additi ║ +║ │ onalProperty: 'theme' } ║ +║ │ Message : must NOT ║ +║ │ have additional properti ║ +║ │ es ║ +╟─────────────────────────────────────────────────────────────┼──────────────────────────╢ +║ WARNING │ Instance path : ║ +║ Theme : Grammer │ Schema path : #/requir ║ +║ File : assets/fonts/font-collection.json │ ed ║ +║ Schema : https://schemas.wp.org/wp/6.5/font-collection.json │ Keyword : required ║ +║ │ Params : { missin ║ +║ │ gProperty: 'slug' } ║ +║ │ Message : must hav ║ +║ │ e required property 'slu ║ +║ │ g' ║ +║ ├──────────────────────────╢ +║ │ Instance path : ║ +║ │ Schema path : #/requir ║ +║ │ ed ║ +║ │ Keyword : required ║ +║ │ Params : { missin ║ +║ │ gProperty: 'name' } ║ +║ │ Message : must hav ║ +║ │ e required property 'nam ║ +║ │ e' ║ +╚═════════════════════════════════════════════════════════════╧══════════════════════════╝ + +Validation passed with warnings. +``` + +image + +It works with pagers. This example shows how to match the table width to your terminal width inside a pager. + +```sh-session +$ # You may need to wait a while for longer lists of themes. +$ node theme-utils.mjs validate-theme --color=always --table-width=$(( $(tput cols) )) atheme,adventurer,grammer,skatepark | less -R +``` + +The added `--format=json` option is super helpful when combined with [`jq`](https://jqlang.github.io/jq/download/) to drill down into the data. + +This, for example, is the breakdown of all the current themes and a count of the types of problems that they have: + +```sh-session +$ # Scroll to see the long command → +$ node theme-utils.mjs validate-theme --format=json $(find . -name 'theme.json' | awk -F/ '{print $2}' | uniq | sort | paste -s -d, -) | jq '.[].data[].message' | sort | uniq -c | sort -bgr +5815 "must NOT have additional properties" + 696 "must be object" + 551 "must be string" + 328 "the $schema version does not match style.css 'Requires at least' version" + 153 "must match exactly one schema in oneOf" + 73 "must be equal to constant" + 71 "the 'Requires at least' version does not support theme.json" + 71 "must be equal to one of the allowed values" + 47 "Missing $schema URI: undefined" + 6 "property name must be valid" + 5 "must be number" + 2 "must be >= 1" + 2 "missing 'Requires at least' header metadata" + 1 "must have required property 'version'" + 1 "must have required property 'slug'" + 1 "must have required property 'name'" +``` From a2d2c34b7d53ed2ee93170a4b085cadec81e8ee3 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:57:37 -0700 Subject: [PATCH 11/20] Fix escapePatterns --- theme-utils.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index 1f79f846..b9a550b4 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -136,7 +136,7 @@ export function getThemeMetadata( styleCss, attribute ) { } async function escapePatterns( themes, options ) { - for ( const themeSlug of themes ) { + for ( const [ i, themeSlug ] of themes.entries() ) { const textDomain = options?.domains?.[ i ] ?? themeSlug; const patterns = await glob( `${ themeSlug }/patterns/*.php` ); From 015096aef20321f6ce03fafedbd264d2206d922e Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 16:58:14 -0700 Subject: [PATCH 12/20] Fix parseFlags if not args remain after flags --- theme-utils.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index b9a550b4..1e6ac9f0 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -71,7 +71,7 @@ function parseFlags( originalArgs ) { const args = [ ...originalArgs ]; args.shift(); const options = {}; - while ( args[ 0 ].startsWith( '--' ) ) { + while ( args?.[ 0 ]?.startsWith( '--' ) ) { const flag = args.shift().slice( 2 ); const [ key, value ] = flag.split( '=' ); const camelCaseKey = key.replace( /-([a-z])/g, ( [ , c ] ) => From e1cda939306f8fef11ab0ae0687d38dbeeb874c2 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 22:51:01 -0700 Subject: [PATCH 13/20] Allow missing theme to represent current directory --- theme-utils.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index 1e6ac9f0..fb0d4ce7 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -23,7 +23,7 @@ const commands = { if ( typeof options.domains === 'string' ) { options.domains = options.domains.split( /[ ,]+/ ); } - const themes = rest[ 0 ].split( /[ ,]+/ ); + const themes = rest?.[ 0 ]?.split( /[ ,]+/ ) ?? [ '.' ]; escapePatterns( themes, options ); }, }, @@ -45,7 +45,7 @@ const commands = { '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', run: async ( args ) => { const [ options, rest ] = parseFlags( args ); - const themes = rest[ 0 ].split( /[ ,]+/ ); + const themes = rest?.[ 0 ]?.split( /[ ,]+/ ) ?? [ '.' ]; await validateThemes( themes, options ); }, }, From 1e7b004f114924e8219011b97758bb2ab1adcf83 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 22:56:07 -0700 Subject: [PATCH 14/20] Fix version formats --- theme-utils.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index fb0d4ce7..c8c22a88 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -530,7 +530,7 @@ async function validateThemes( themes, { format, color, tableWidth } ) { validators: [ { validate: ( attr, value ) => - validators.validateVersion( attr, value, [ 3 ] ), + validators.validateVersion( attr, value, [ 2, 3 ] ), type: 'error', }, ], @@ -552,7 +552,7 @@ async function validateThemes( themes, { format, color, tableWidth } ) { validators: [ { validate: ( attr, value ) => - validators.validateVersion( attr, value, [ 2, 3 ] ), + validators.validateVersion( attr, value, [ 2 ] ), type: 'error', }, { From 202b5323717cae2cc444f83ec394145d1f917c78 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 22:56:20 -0700 Subject: [PATCH 15/20] Add npm scripts for themes --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f9cf1c3..03d8625d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "lint:css": "stylelint **/*.css -i .gitignore", "lint:css:fix": "stylelint **/*.css -i .gitignore --fix", "lint:php": "composer run-script lint", - "lint:php:fix": "composer run-script format" + "lint:php:fix": "composer run-script format", + "lint:theme": "node theme-utils.mjs validate-theme", + "lint:patterns:fix": "node theme-utils.mjs escape-patterns --domains=twentytwentyfive" } } From c1d6c9c74c4b77a4141c2cc39652ae3ddfad26e8 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Tue, 17 Sep 2024 23:05:44 -0700 Subject: [PATCH 16/20] Clean up docs --- CONTRIBUTING.md | 102 ++---------------------------------------------- 1 file changed, 3 insertions(+), 99 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b02b968d..1ed10986 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Tools -### Translation +### Escape pattern strings ```sh-session $ node theme-utils.mjs help escape-patterns @@ -13,11 +13,11 @@ Escapes block patterns for pattern files. --domains=DOMAINS - Array of domains mapping to themes + Array of text domains mapping to themes ``` -### Validation +### Theme validation ```sh-session $ node theme-utils.mjs help validate-theme @@ -41,99 +41,3 @@ Validates a theme against the WordPress theme requirements. Will default to 120 if omitted and width cannot be determined automatically. ``` - -Here's an example of the output. - -image - -This is how you run validation on a single theme: - -```sh-session -$ node theme-utils.mjs validate-theme grammer - -Progress: [ '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■', 100 ] 1/1 - -╔═════════════════════════════════════════════════════════════╤══════════════════════════╗ -║ ERROR │ Message : missing 'Requi ║ -║ Theme : Grammer │ res at least' header met ║ -║ File : style.css │ adata ║ -╟─────────────────────────────────────────────────────────────┼──────────────────────────╢ -║ WARNING │ Actual : undefined ║ -║ Theme : Grammer │ Expected : 5.9 or greate ║ -║ File : style.css │ r ║ -║ │ Message : the 'Requires ║ -║ │ at least' version does ║ -║ │ not support theme.json ║ -╟─────────────────────────────────────────────────────────────┼──────────────────────────╢ -║ WARNING │ Instance path : /setting ║ -║ Theme : Grammer │ s/spacing/spacingScale ║ -║ File : theme.json │ Schema path : #/defini ║ -║ Schema : https://schemas.wp.org/trunk/theme.json │ tions/settingsSpacingPro ║ -║ │ perties/properties/spaci ║ -║ │ ng/properties/spacingSca ║ -║ │ le/additionalProperties ║ -║ │ Keyword : addition ║ -║ │ alProperties ║ -║ │ Params : { additi ║ -║ │ onalProperty: 'theme' } ║ -║ │ Message : must NOT ║ -║ │ have additional properti ║ -║ │ es ║ -╟─────────────────────────────────────────────────────────────┼──────────────────────────╢ -║ WARNING │ Instance path : ║ -║ Theme : Grammer │ Schema path : #/requir ║ -║ File : assets/fonts/font-collection.json │ ed ║ -║ Schema : https://schemas.wp.org/wp/6.5/font-collection.json │ Keyword : required ║ -║ │ Params : { missin ║ -║ │ gProperty: 'slug' } ║ -║ │ Message : must hav ║ -║ │ e required property 'slu ║ -║ │ g' ║ -║ ├──────────────────────────╢ -║ │ Instance path : ║ -║ │ Schema path : #/requir ║ -║ │ ed ║ -║ │ Keyword : required ║ -║ │ Params : { missin ║ -║ │ gProperty: 'name' } ║ -║ │ Message : must hav ║ -║ │ e required property 'nam ║ -║ │ e' ║ -╚═════════════════════════════════════════════════════════════╧══════════════════════════╝ - -Validation passed with warnings. -``` - -image - -It works with pagers. This example shows how to match the table width to your terminal width inside a pager. - -```sh-session -$ # You may need to wait a while for longer lists of themes. -$ node theme-utils.mjs validate-theme --color=always --table-width=$(( $(tput cols) )) atheme,adventurer,grammer,skatepark | less -R -``` - -The added `--format=json` option is super helpful when combined with [`jq`](https://jqlang.github.io/jq/download/) to drill down into the data. - -This, for example, is the breakdown of all the current themes and a count of the types of problems that they have: - -```sh-session -$ # Scroll to see the long command → -$ node theme-utils.mjs validate-theme --format=json $(find . -name 'theme.json' | awk -F/ '{print $2}' | uniq | sort | paste -s -d, -) | jq '.[].data[].message' | sort | uniq -c | sort -bgr -5815 "must NOT have additional properties" - 696 "must be object" - 551 "must be string" - 328 "the $schema version does not match style.css 'Requires at least' version" - 153 "must match exactly one schema in oneOf" - 73 "must be equal to constant" - 71 "the 'Requires at least' version does not support theme.json" - 71 "must be equal to one of the allowed values" - 47 "Missing $schema URI: undefined" - 6 "property name must be valid" - 5 "must be number" - 2 "must be >= 1" - 2 "missing 'Requires at least' header metadata" - 1 "must have required property 'version'" - 1 "must have required property 'slug'" - 1 "must have required property 'name'" -``` From f5565d713c8b67391b5bbe308484ade3194e2b87 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 18 Sep 2024 10:24:56 -0700 Subject: [PATCH 17/20] Simplify docs --- CONTRIBUTING.md | 43 ------------------------------------------- README.md | 3 +++ 2 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 1ed10986..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributing - -## Tools - -### Escape pattern strings - -```sh-session -$ node theme-utils.mjs help escape-patterns - -escape-patterns [--domains=DOMAINS] - -Escapes block patterns for pattern files. - ---domains=DOMAINS - - Array of text domains mapping to themes - -``` - -### Theme validation - -```sh-session -$ node theme-utils.mjs help validate-theme - -validate-theme [--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] - -Validates a theme against the WordPress theme requirements. - ---format=FORMAT - - Output format. Possible values: *table*, json, dir. - ---color=WHEN - - Colorize the output for table or dir formats. The automatic mode only enables colors if - an interactive terminal is detected. Possible values: *auto*, always, never. - ---table-width=COLUMNS - - Explicitly set the width of the table format instead of determining it automatically. - Will default to 120 if omitted and width cannot be determined automatically. - -``` diff --git a/README.md b/README.md index 36723305..107e7edb 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,9 @@ You can then use the following commands to test the code: - **`npm run lint:css:fix`**: Lints and attempts to autofix any issues in the CSS files. - **`npm run lint:php`**: Checks PHP files for syntax and standards errors according to [WordPress coding standards](https://developer.wordpress.org/coding-standards/). - **`npm run lint:php:fix`**: Attempts to automatically fix PHP errors. +- **`lint:theme`**: Checks the theme.json and style.css metadata for issues. +- **`lint:patterns:fix`**: Attempts to automatically wrap text in patterns for translation. Please check the updated files after running for adding additional context that may be needed or text that the tool wasn't able to automatically wrap. + ## Resources From 2424bc25e09f1aa353547f440f3b94a6d8b05ebd Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 18 Sep 2024 12:03:25 -0700 Subject: [PATCH 18/20] Filter aria-label --- theme-utils.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/theme-utils.mjs b/theme-utils.mjs index c8c22a88..eb5aa493 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -181,6 +181,17 @@ async function escapePatterns( themes, options ) { } ); } + const ariaLabel = startTag.attrs.find( + ( attr ) => attr.name === 'aria-label' + ); + if ( ariaLabel ) { + ariaLabel.value = escapeText( + ariaLabel.value, + textDomain, + true + ); + } + rewriter.emitStartTag( startTag ); } ); @@ -206,6 +217,7 @@ async function escapePatterns( themes, options ) { { name: 'placeholder', isAttr: true }, { name: 'buttonText' }, { name: 'content' }, + { name: 'ariaLabel', isAttr: true }, ]; const start = block.indexOf( '{' ); const end = block.lastIndexOf( '}' ); From 7156e07987fd2dd3607bc97c98dc5f9249f5c844 Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Wed, 18 Sep 2024 14:03:13 -0700 Subject: [PATCH 19/20] Update escape-patterns to handle individual files --- theme-utils.mjs | 85 +++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/theme-utils.mjs b/theme-utils.mjs index eb5aa493..bc2a1839 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -14,22 +14,31 @@ const commands = { 'escape-patterns': { helpText: [ 'Escapes block patterns for pattern files.', - '--domains=DOMAINS', - wrapIndent( 'Array of domains mapping to themes' ), + '', + wrapIndent( + 'Individual pattern files to escape or root directories of themes with patterns to escape.' + ), + '--text-domain=TEXT_DOMAIN', + wrapIndent( + 'Text domain to use for translations. If omitted and theme directories are passed, this will default to the names of the directories.' + ), + 'EXAMPLE', + wrapIndent( 'Escape patterns for only modified files in git.' ), + wrapIndent( + "node theme-utils.mjs escape-patterns --text-domain=$(basename $PWD) $(git diff --name-only HEAD -- './patterns/*.php')" + ), ].join( '\n\n' ), - additionalArgs: '[--domains=DOMAINS] ', + additionalArgs: '[--text-domain=TEXT_DOMAIN] ', run: ( args ) => { - const [ options, rest ] = parseFlags( args ); - if ( typeof options.domains === 'string' ) { - options.domains = options.domains.split( /[ ,]+/ ); - } - const themes = rest?.[ 0 ]?.split( /[ ,]+/ ) ?? [ '.' ]; - escapePatterns( themes, options ); + const [ options, files ] = parseFlags( args ); + escapePatterns( files, options ); }, }, 'validate-theme': { helpText: [ 'Validates a theme against the WordPress theme requirements.', + '', + wrapIndent( 'Root directories for themes.' ), '--format=FORMAT', wrapIndent( 'Output format. Possible values: *table*, json, dir.' ), '--color=WHEN', @@ -42,11 +51,13 @@ const commands = { ), ].join( '\n\n' ), additionalArgs: - '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', + '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', run: async ( args ) => { - const [ options, rest ] = parseFlags( args ); - const themes = rest?.[ 0 ]?.split( /[ ,]+/ ) ?? [ '.' ]; - await validateThemes( themes, options ); + const [ options, themes ] = parseFlags( args ); + const dirs = themes?.flatMap( ( maybeThemeArray ) => + maybeThemeArray?.split( /[ ,]+/ ) + ); + await validateThemes( dirs, options ); }, }, help: { @@ -135,12 +146,16 @@ export function getThemeMetadata( styleCss, attribute ) { ?.trim(); } -async function escapePatterns( themes, options ) { - for ( const [ i, themeSlug ] of themes.entries() ) { - const textDomain = options?.domains?.[ i ] ?? themeSlug; - const patterns = await glob( `${ themeSlug }/patterns/*.php` ); - - console.log( getPatternTable( themeSlug, textDomain, patterns ) ); +async function escapePatterns( patternsAndThemes, options ) { + for ( const themeOrPattern of patternsAndThemes ) { + const isTheme = fs.statSync( themeOrPattern ).isDirectory(); + const themeSlug = isTheme + ? themeOrPattern + : themeOrPattern.split( '/', 1 )[ 0 ]; + const textDomain = options?.textDomain ?? themeSlug; + const patterns = isTheme + ? await glob( `${ themeSlug }/patterns/*.php` ) + : [ themeOrPattern ]; patterns.forEach( ( file ) => { const rewriter = getReWriter( textDomain ); @@ -209,7 +224,7 @@ async function escapePatterns( themes, options ) { return rewriter; } - function escapeBlockAttrs( block, themeSlug ) { + function escapeBlockAttrs( block, textDomain ) { // Set isAttr to true if it is an attribute in the result HTML // If set to true, it generates esc_attr_, otherwise it generates esc_html_ const allowedAttrs = [ @@ -232,7 +247,7 @@ async function escapePatterns( themes, options ) { if ( ! configJson[ attr.name ] ) return; configJson[ attr.name ] = escapeText( configJson[ attr.name ], - themeSlug, + textDomain, attr.isAttr ); } ); @@ -243,14 +258,19 @@ async function escapePatterns( themes, options ) { } } - function escapeText( text, themeSlug, isAttr = false ) { + function escapeText( text, textDomain, isAttr = false ) { const trimmedText = text && text.trim(); - if ( ! themeSlug || ! trimmedText || trimmedText.startsWith( ``; + return `${ spaceChar }`; } function escapeImagePath( src ) { @@ -261,23 +281,6 @@ async function escapePatterns( themes, options ) { const resultSrc = parts.slice( parts.indexOf( assetsDir ) ).join( '/' ); return `/${ resultSrc }`; } - - function getPatternTable( themeSlug, textDomain, patterns ) { - const tableConfig = { - columnDefault: { - width: 40, - }, - header: { - alignment: 'center', - content: `THEME: ${ themeSlug }\n\nTEXT DOMAIN: ${ textDomain }Following patterns may get updated with escaped strings and/or image paths`, - }, - }; - - return table( - patterns.map( ( p ) => [ p ] ), - tableConfig - ); - } } /** From 55a99686958c30d58a3e221455681982a150d7aa Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 19 Sep 2024 07:54:53 -0700 Subject: [PATCH 20/20] Fix npm scripts --- package.json | 2 +- theme-utils.mjs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 03d8625d..8840bd14 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ "lint:php": "composer run-script lint", "lint:php:fix": "composer run-script format", "lint:theme": "node theme-utils.mjs validate-theme", - "lint:patterns:fix": "node theme-utils.mjs escape-patterns --domains=twentytwentyfive" + "lint:patterns:fix": "node theme-utils.mjs escape-patterns --text-domain=twentytwentyfive" } } diff --git a/theme-utils.mjs b/theme-utils.mjs index bc2a1839..e9d3e7fd 100644 --- a/theme-utils.mjs +++ b/theme-utils.mjs @@ -30,8 +30,11 @@ const commands = { ].join( '\n\n' ), additionalArgs: '[--text-domain=TEXT_DOMAIN] ', run: ( args ) => { - const [ options, files ] = parseFlags( args ); - escapePatterns( files, options ); + const [ options, filesOrThemes ] = parseFlags( args ); + if ( ! filesOrThemes.length ) { + filesOrThemes.push( '.' ); + } + escapePatterns( filesOrThemes, options ); }, }, 'validate-theme': { @@ -54,10 +57,10 @@ const commands = { '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', run: async ( args ) => { const [ options, themes ] = parseFlags( args ); - const dirs = themes?.flatMap( ( maybeThemeArray ) => - maybeThemeArray?.split( /[ ,]+/ ) - ); - await validateThemes( dirs, options ); + if ( ! themes.length ) { + themes.push( '.' ); + } + await validateThemes( themes, options ); }, }, help: {