├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── assets ├── css-obfuscation.png ├── html-obfuscation.png ├── js-obfuscation.png ├── json-obfuscation.png ├── postcss-obfuscator.png └── react-obfuscation.png ├── index.js ├── package-lock.json ├── package.json ├── style.css ├── test ├── demos │ └── simple │ │ ├── postcss-obfuscate.js │ │ ├── postcss.config.js │ │ ├── src │ │ ├── css │ │ │ ├── app.css │ │ │ └── input.css │ │ └── html │ │ │ ├── 404.html │ │ │ ├── app.html │ │ │ └── index.htm │ │ └── tailwind.config.js ├── test.js └── unit │ └── getClassNames.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | test/**/build 13 | test/**/out 14 | test/**/css-obfuscator 15 | test/**/src/css/output.css 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # personalFiles & localFiles 29 | /MYFILES -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | /assets 3 | /MYFILES -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Najib Rachid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | IntroMotivationFeaturesInstallationUsageConfigurationFAQReleasesContributingLicenseCheck Also 3 | 4 | 5 | # :space_invader: PostCSS Obfuscator 6 | 7 | - :date:**13-05-2023** :pushpin:**Beta Version 1.6.0** 8 | - :computer:NajibRachid :purple_circle:ANMOON :office: XHUB 9 | 10 | PostCSS plugin that helps you protect your CSS code by obfuscating class names and ids. with advanced customizable configuration. 11 | 12 | This plugin provides obfuscation capabilities to your CSS files by replacing class and id selectors with prefixed, simplified or randomly generated strings. This can help to protect your CSS code from reverse engineering and unauthorized copying, while also reducing the file size of your CSS files. plugin offers advanced customizable configuration. 13 | 14 |
15 | postcss obfuscator cli 16 | json Obfuscation 17 | css Obfuscation 18 | html Obfuscation 19 | react Obfuscation 20 | js Obfuscation 21 |
22 | 23 | --- 24 | 25 | ## Motivation 26 | 27 | - Protecting intellectual property, licensing & distribution: make it hard for others from stealing your design or using it without your permission. 28 | - Protect against Web scraping, data mining or any malicious activities like stealing data or content: make it hard extracting data from websites automatically using software tools, which use class names & ids. 29 | - Minfiy Your code even more: obsfucation/uglify can slightly reduce file size and improve its performance. 30 | 31 | ## Features 32 | 33 | - [x] No 3rd parties, dependencies. just vanilla Nodejs code. 34 | - [x] Quicker then you think. 35 | - [x] Postcss plugin, hence its intended to work with any build tool or task runner. 36 | - [x] Advanced Customizable configuration (Control is yours). 37 | - [x] Supports all files: .html, .js, .jsx, .vue, .php, ... you name it. 38 | - [x] Supports a wide range of CSS frameworks (Tailwidcss, Bootstrap, Bulma, ...). 39 | 40 | ## Installation 41 | 42 | ```sh 43 | # npm 44 | npm install postcss-obfuscator --save-dev 45 | ``` 46 | 47 | ```sh 48 | # yarn 49 | yarn add postcss-obfuscator --dev 50 | ``` 51 | 52 | ## Usage 53 | 54 | First, you need to add postcss-obfuscator to your PostCSS configuration. For example, if you're using postcss-cli, you can add it to your `postcss.config.js` file: 55 | 56 | ```js 57 | //postcss.config.js / postcss.config.cjs 58 | module.exports = { 59 | // other plugins 60 | plugins: [ 61 | require("postcss-obfuscator")({ 62 | /* options */ 63 | }), 64 | ], 65 | }; 66 | ``` 67 | 68 | ## Configuartion 69 | 70 | The plugin has several options that you can configure to customize its behavior. 71 | **Here's the default donfiguration:** 72 | 73 | ```js 74 | const defaultOptions = { 75 | enable: true, // Enable plugin 76 | length: 5, // Random name length. 77 | classMethod: "random", // 'random', 'simple', 'none' obfuscation method for classes. 78 | classPrefix: "", // ClassName prefix. 79 | classSuffix: "", // ClassName suffix. 80 | classIgnore: [], // Class to ignore from obfuscation. 81 | ids: false, // Obfuscate #IdNames. 82 | idMethod: "random", // 'random', 'simple', 'none' obfuscation method for ids . 83 | idPrefix: "", // idName Prefix. 84 | idSuffix: "", // idName suffix. 85 | idIgnore: [], // Ids to ignore from obfuscation. 86 | indicatorStart: null, // Identify ids & classes by the preceding string. 87 | indicatorEnd: null, // Identify ids & classes by the following string. 88 | jsonsPath: "css-obfuscator", // Path and file name where to save obfuscation data. 89 | srcPath: "src", // Source of your files. 90 | desPath: "out", // Destination for obfuscated html/js/.. files.Be careful using the same directory as your src(you will lose your original files). 91 | extensions: [".html"], // Extesnion of files you want osbfucated ['.html', '.php', '.js', '.svelte']. 92 | htmlExcludes: [], // Files and paths to exclude from html obfuscation replacement. 93 | cssExcludes: [], // Files and paths to exclude from css obfuscation. 94 | fresh: false, // Create new obfuscation data list or use already existed one (to keep production cache or prevent data scrapping). 95 | multi: false, // Generate obsfucated data file for each css file. 96 | differMulti: false, // Generate different Random names for each file. 97 | formatJson: false, // Format obfuscation data JSON file. 98 | showConfig: false, // Show config on terminal when runinng. 99 | keepData: true, // Keep or delete Data after obfuscation is finished? 100 | preRun: () => Promise.resolve(), // do something before the plugin runs. 101 | callBack: function () {}, // Callback function to call after obfuscation is done. 102 | }; 103 | ``` 104 | 105 | - **`enable:`** Enable plugin, **default is true.** 106 | - **`length:`** Random name length for both ids and classes, **default is 5.** 107 | - **`classMethod:`** Obfuscation method for classes, options are: (`random`, `simple`, `none`). `simple` will remove pronounced vowels and digits, `none` will keep original name in case you want to just use prefixes or suffixes. **default is 'random'.** 108 | - **`classPrefix:`** Prefix for class names, **default is nothig.** 109 | - **`classSuffix:`** Suffix for class names, **default is nothig.** 110 | - **`classIgnore:`** Array of classes to ignore from obfuscation. **default is none.** 111 | - **`ids:`** Enable id Obfuscation, **default is false.** 112 | - **`idMethod:`** Obfuscation method for ids, options are also: (`random`, `simple`, `none`), __default is 'random'._ 113 | - **`idPrefix:`** Prefix for id names, **default is nothig.** 114 | - **`idSuffix:`** Suffix for id names, **default is nothig.** 115 | - **`idIgnore:`** Array of ids to ignore from obfuscation. **default is none.** 116 | - **`indicator:`** Indicator used to replace names. **default is none.** 117 | - **`indicatorStart:`** Identify ids & classes by the preceding string. **default is none.** 118 | - **`indicatorEnd:`** Identify ids & classes by the following string. **default is none.** 119 | - **`jsonsPath:`** Path and file name where to save obfuscation data **default is: css-obfuscator.** 120 | - **`srcPath:`** Path for your source files, **default is: src.** 121 | - **`desPath:`** Destination path for obfuscated html/js/.. files. Be careful using the same directory as your src(you will lose your original files). **default is: out**. 122 | - **`extensions:`** Extesnions Array of files you want osbfucated ['.html', '.php', '.js', '.svelte'], **default is '.html'.** 123 | - **`htmlExcludes:`** Files and paths to exclude from html obfuscation replacement, **default is none.** 124 | - **`cssExcludes:`** Files and paths to exclude from css obfuscation, **default is none.** 125 | - **`fresh:`** Create new obfuscation data list or use already existed one (to keep production cache or prevent data scrapping). **default is false.** 126 | - **`multi:`** Generate obsfucated data file for each css file, **default is false.** 127 | - **`differMulti:`** Generate different Random names for each file, **default is false.** 128 | - **`formatJson:`** Format obfuscation data JSON file, **default is false.** 129 | - **`showConfig:`** Show config on terminal when runinng, **default is false.** 130 | - **`keepData:`** Keep or delete data after obfuscation is finished? **default is true.** 131 | - **`preRun:`** Do something before the plugin runs. **default is a promise that immediately resolves.** 132 | - **`callBack:`** Callback function to call after obfuscation is done. **default is an empty function.** 133 | 134 | ## npm scripts example 135 | 136 | Then npm scripts can be something like this: 137 | 138 | ```json 139 | "postcss": "postcss src/**/*.css --dir build", 140 | "postcss:watch": "postcss src/**/*.css --dir build --watch" 141 | "obfuscate": "node postcss-obfuscate", // for custome script. 142 | ``` 143 | 144 | ## FAQ 145 | 146 | ### How it works basically? 147 | 148 | 1. Loop over all CSS files. 149 | 2. Uses built-in function(regex) to find classes and ids. 150 | 3. Saves Ids & classes in a JSON file key representing original Names. then generate random names as values. 151 | 4. Creates a new folder from the source folder. 152 | 5. Loops throw files and replace those keys with values from JSON files. 153 | 154 | ### Caveats? 155 | 156 | - Only CSS is supported so call the extension After your code was converted to CSS (Example: SCSS to CSS). It's generally better to call it the last. 157 | - One of the best practices is to avoid naming your ids & classes reserved words like HTML element names or attributes. (same for JS & CSS). 158 | - It uses a built-in function to find ids & CSS classes. so it may not work perfectly with advanced CSS selectors. 159 | - I advise `keepData` option as default, and using a different build directory: Using the same directory will replace your files and you may lose original classes and ids names. you will get a warning for that. 160 | - Postcss doesn't support nested directories by default. this is If you intend to work with the plugin's multi-option. 161 | 162 | ### Destination folder? 163 | 164 | It's better to keep your source files as they are for easy development. Consider specifying another folder for the build, if you choose your build directory to be the same as the source directory you will be replaced and you will lose your original files. 165 | 166 | ### Support for CSS framworks? 167 | 168 | It's designed to work with CSS, hence its supports any framework you can think of: 169 | - Tailwindcss. 170 | - Bootstrap. 171 | - Bulma. 172 | - ... . 173 | ### Use indicators? 174 | 175 | As mentioned this plugin uses Regex to replace all appearances of classes & ids on files with extensions you specify (be it html, cs, js, ...). 176 | Generally, if your class names are unique and avoid reserved keywords, then you got nothing to worry about, otherwise, we got you covered just use the `indicatorStart` & `indicatorEnd` options. 177 | 178 | For example `class`, `h1`, and `import` are examples of reserved keywords in (HTML, CSS, JS, PHP). 179 | so if you insist on breaking naming conventions and using them as id or class names: 180 | Then you can use either or both indicator options like this: 181 | 182 | ```js 183 | indicatorStart: "@", 184 | indicatorEnd: "#", 185 | ``` 186 | 187 | ```html 188 | 191 |
MyClass
192 | ``` 193 | 194 | ### Build Static and make production ready? 195 | 196 | Postcss usually is run automatically on `dev` and `build`: so it's preferred to create a customer postcss script with the postcss-obfuscator 197 | plugin, a script to run only when you want to obfuscate and make the project ready for production. 198 | 199 | ```js 200 | //postcss.config.js / postcss.config.cjs 201 | const postcss = require("postcss"); 202 | const obfuscator = require("postcss-obfuscator"); 203 | // other plugins 204 | postcss([ 205 | obfuscator({ 206 | /* options */ 207 | }), 208 | ]) 209 | .process(css) 210 | .then((result) => { 211 | console.log("Task Completed!", result); 212 | }) 213 | .catch((error) => { 214 | console.error(error); 215 | }); 216 | ``` 217 | 218 | But it can also be done like this: 219 | 220 | 1. The `enable` option: to enable it only in specific mode and to make sure CSS works fine in dev mode for debugging: 221 | 222 | ```js 223 | // process.env.NODE_ENV = "development" //development //obfuscation //production 224 | const isObfscMode = process.env.NODE_ENV === "obfuscation"; 225 | 226 | //enable: isObfscMode, 227 | ``` 228 | 229 | 2. The `callBack` option, a Callback function to call after obfuscation is done. that way once obfuscation is done you can config and prepare your project for production: 230 | 231 | ```js 232 | callBack: function () { 233 | process.env.NODE_ENV = "production"; // to make sure postcss-obfuscator doesn't re-run. 234 | }, 235 | ``` 236 | 237 | So basically you use `callBack` option to set the env mode back to `production` thus obfuscation will not run, and then config your app source folder to use `out` folder instead of `src` for production. 238 | 239 | ### Run plugin after some tasks are finished? 240 | 241 | You can use the `preRun` event hook option to delay the plugin until a certain task is finished or to perform some operation before the plugin runs. This option provides greater control over the timing of the plugin's execution and allows you to ensure that any necessary pre-processing or post-processing is completed before or after the plugin runs. 242 | ```js 243 | preRun: () => new Promise(resolve => setTimeout(resolve, 10000)), // delay for 10s. 244 | ``` 245 | ```js 246 | preRun: () => { 247 | return new Promise(resolve => { 248 | // some pre-tasks 249 | resolve(); 250 | }); 251 | } 252 | ``` 253 | ### How To Use With? 254 | Are you struggling to use it with your tooling & stack environment? please create an Issue. We will be happy to help. 255 | 256 | #### HTML/CSS: Issue :link: | Replit :link: 257 | #### PHP+Tailwindcss: Issue :link: | Replit :link: 258 | #### ASTRO+Tailwindcss: Issue :link: | Replit :link: 259 | #### Vite+React+Tailwindcss: Issue :link: | Replit :link: 260 | #### Laravel-Mix (outside Laravel) + Tailwindcss: Issue :link: | Replit :link: 261 | #### Gulp-postcss + Tailwindcss: Issue :link: | Replit :link: 262 | 263 | ## Releases 264 | 265 | ```txt 266 | - Initial Version 1.0.0 : 18/02/2023 267 | - Project Setup. 268 | - Theory & prove of concept. 269 | - Initial Version 1.0.3 : 19/02/2023 270 | - Essential default confugration options (length, jsonPath, placeholder). 271 | - Settled on a no dependcies solution. built own parser. 272 | - Developing class finder function. 273 | - Initial Version 1.0.7 : 20/02/2023 274 | - Set confugration options. 275 | - Introduce Id obfuscation. 276 | - Add prefixers option. 277 | - Add suffixes option. 278 | - Initial Version 1.0.9 : 21/02/2023 279 | - Add srcPath option. 280 | - Add desPath option. 281 | - Initial Version 1.1.2 : 21/02/2023 282 | - Improving class finder method regex. 283 | - Add showConfig Option. 284 | - Add formatJson Option. 285 | - Alpha Version 1.1.5 : 22/02/2023 286 | - Introducing the Multi option. 287 | - Refactor & improve code performance. 288 | - Introduce CLI UI. 289 | - Alpha Version 1.1.8 : 23/02/2023 290 | - Introducing the fresh option. 291 | - Introducing the keepSame option. 292 | - Adding Logger & logs. 293 | - Alpha Version 1.2.2 : 24/02/2023 294 | - Introducing the callback option. 295 | - Adding proccess stats/data log. 296 | - Use a copy method solution. 297 | - Improve Replace Regex (exclude HTML attributes). 298 | - Alpha Version 1.2.6 : 24/02/2023 299 | - Adding Indicator option. 300 | - deprecate keepSame option. 301 | - Introduce the differMulti option. 302 | - Make keepData true as default. 303 | - Alpha Version 1.3.2 : 25/02/2023 304 | - Add extensions options. 305 | - fix differMulti option Bug(class repeation). 306 | - Deprecate placeholder option. 307 | - Introducing ExcludeCss option. 308 | - Introducing ExcludeHTML option. 309 | - Fix Edge case (Css file count). 310 | - Alpha Version 1.3.4 : 26/02/2023 311 | - Fix Files Count (count diffrent extensions). 312 | - Improve simplify function (add random letter if <1). 313 | - Alpha Version 1.3.8 : 27/02/2023 314 | - Add idIgnore/classIgnore Option, 315 | - Add classMethod option (random/simplify/none). 316 | - Fix issue (delete data wrong path). 317 | - Fix bug (Find multiple Ids). 318 | - Beta Version 1.4.0 : 28/02/2023 319 | - Improve Exclude Css Option allow Paths. 320 | - Improve Exclude HTML Option allow Paths. 321 | - Beta Version 1.4.1 : 01/03/2023 322 | - Fix Error copying directory: Invalid regular expression: /(?PostCSS-prepend | PostCSS-mobile-first 382 | 383 | **keywords:** _postcss, plugin, obfuscation, css, css classes, class renamer, postcss-rename-selectors, class prefixer, Postcss obfuscator, PostCSS obfuscation plugin, CSS obfuscation, Class name scrambling, CSS security, Obfuscate CSS code, Protect CSS code, Prevent CSS reverse-engineering, tailwindcss, tailwindcss classes list, tailwindcss classes array json, bootstrap, bootstrap classes array json, Scramble HTML classes, CSS anti-theft protection, code privacy, CSS code obfuscator, CSS class name encryption, anti web scraping, Anti-scraping tools, Anti-scraping technology, Web scraping prevention, Web crawling protection._ 384 | -------------------------------------------------------------------------------- /assets/css-obfuscation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/assets/css-obfuscation.png -------------------------------------------------------------------------------- /assets/html-obfuscation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/assets/html-obfuscation.png -------------------------------------------------------------------------------- /assets/js-obfuscation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/assets/js-obfuscation.png -------------------------------------------------------------------------------- /assets/json-obfuscation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/assets/json-obfuscation.png -------------------------------------------------------------------------------- /assets/postcss-obfuscator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/assets/postcss-obfuscator.png -------------------------------------------------------------------------------- /assets/react-obfuscation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/assets/react-obfuscation.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { 2 | getRandomName, 3 | simplifyString, 4 | writeJsonToFile, 5 | copyDirectory, 6 | replaceJsonKeysInFiles, 7 | getFileCount, 8 | getClassNames, 9 | getIdNames, 10 | logger, 11 | getRelativePath, 12 | isFileOrInDirectory, 13 | escapeClassName, 14 | octalizeClassName, 15 | } = require("./utils"); 16 | const path = require("path"); 17 | 18 | const pluginName = "PostCSS Obfuscator"; 19 | const pluginVersion = "V 1.6.0 Beta"; 20 | const pluginWebSite = "https://github.com/n4j1Br4ch1D/postcss-obfuscator"; 21 | const pluginHead = ` __ ${pluginName} __ 22 | (oo) ${pluginVersion} (xx) 23 | //||\\\\ ${pluginWebSite} //||\\\\ 24 | ======================================================================> `; 25 | const defaultOptions = { 26 | enable: true, // Enable plugin 27 | length: 5, // Random name length. 28 | classMethod: "random", // 'random', 'simple', 'none' obfuscation method for classes. 29 | classPrefix: "", // ClassName prefix. 30 | classSuffix: "", // ClassName suffix. 31 | classIgnore: [], // Class to ignore from obfuscation. 32 | ids: false, // Obfuscate #IdNames. 33 | idMethod: "random", // 'random', 'simple', 'none' obfuscation method for ids . 34 | idPrefix: "", // idName Prefix. 35 | idSuffix: "", // idName suffix. 36 | idIgnore: [], // Ids to ignore from obfuscation. 37 | indicatorStart: null, // Identify ids & classes by the preceding string. 38 | indicatorEnd: null, // Identify ids & classes by the following string. 39 | jsonsPath: "css-obfuscator", // Path and file name where to save obfuscation data. 40 | srcPath: "src", // Source of your files. 41 | desPath: "out", // Destination for obfuscated html/js/.. files. Be careful using the same directory as your src(you will lose your original files). 42 | extensions: ['.html'], // Extesnion of files you want osbfucated ['.html', '.php', '.js', '.svelte']. 43 | htmlExcludes: [], // Files and paths to exclude from html obfuscation replacement. 44 | cssExcludes: [], // Files and paths to exclude from css obfuscation. 45 | fresh: false, // Create new obfuscation data list or use already existed one (to keep production cache or prevent data scrapping). 46 | multi: false, // Generate obsfucated data file for each css file. 47 | differMulti: false, // Generate different Random names for each file. 48 | formatJson: false, // Format obfuscation data JSON file. 49 | showConfig: false, // Show config on terminal when runinng. 50 | keepData: true, // Keep or delete Data after obfuscation is finished? 51 | preRun: () => Promise.resolve(), // do something before the plugin runs. 52 | callBack: function () {}, // Callback function to call after obfuscation is done. 53 | }; 54 | let data = {}; 55 | let jsonData = {}; 56 | let singleFileData = {}; 57 | let processedFiles = new Set(); 58 | let idList = new Set(); 59 | let cssNo = 0; 60 | let classesNo = 0; 61 | let idsNo = 0; 62 | const envMode = process.env.NODE_ENV; 63 | 64 | module.exports = (options = {}) => { 65 | // Get Final Option By Merging the default and user-defined options 66 | const { 67 | enable, 68 | length, 69 | classMethod, 70 | classPrefix, 71 | classSuffix, 72 | classIgnore, 73 | ids, 74 | idMethod, 75 | idPrefix, 76 | idSuffix, 77 | idIgnore, 78 | indicatorStart, 79 | indicatorEnd, 80 | jsonsPath, 81 | srcPath, 82 | desPath, 83 | extensions, 84 | htmlExcludes, 85 | cssExcludes, 86 | fresh, 87 | multi, 88 | differMulti, 89 | formatJson, 90 | showConfig, 91 | keepData, 92 | preRun, 93 | callBack, 94 | } = { ...defaultOptions, ...options }; 95 | return { 96 | postcssPlugin: pluginName, 97 | Once: async (root, { result }) => { 98 | // Add the file path to the set of processed files 99 | if (!enable) { 100 | return; 101 | } else { 102 | await preRun(); 103 | if (processedFiles.size == 0) { 104 | console.log("\x1b[48;2;103;113;210m%s\x1b[0m", pluginHead); 105 | if (envMode === "dev" || envMode === "development") { 106 | logger( 107 | "warn", 108 | pluginName, 109 | "Warning:", 110 | "You are Running in Dev Mode!" 111 | ); 112 | } 113 | if (srcPath === desPath) { 114 | logger( 115 | "warn", 116 | pluginName, 117 | "Warning:", 118 | "Are You Sure You wanna Replace this file This my cause you loose your surce data please specify antother folder" 119 | ); 120 | } 121 | cssFilesNo = getFileCount(srcPath, [".css"], cssExcludes); 122 | if (showConfig) { 123 | console.info("\x1b[34m", "Plug", "\x1b[36m", "Config:", { 124 | ...defaultOptions, 125 | ...options, 126 | }); 127 | } 128 | logger("info", pluginName, "PreRun:", "PreRun event hook finished."); 129 | } 130 | let cssFile = getRelativePath(result.opts.from); 131 | if (isFileOrInDirectory(cssExcludes, cssFile)) { 132 | logger("info", pluginName, "Ignoring:", cssFile); 133 | return; 134 | } 135 | cssNo++; 136 | logger("info", pluginName, "processing:", cssFile); 137 | if (envMode === "dev" || envMode === "development") { 138 | root.prepend({ 139 | text: ` __ 140 | (oo) 141 | //||\\\\ 142 | ${pluginName} 143 | ${pluginVersion} 144 | ${pluginWebSite} 145 | ** this only appears on Dev Mode ** 146 | `, 147 | }); 148 | } 149 | singleFileData = {}; 150 | if (multi) { 151 | data = singleFileData; 152 | if (differMulti) { 153 | data = singleFileData; 154 | } else { 155 | data = jsonData; 156 | } 157 | } else { 158 | data = jsonData; 159 | } 160 | root.walkRules((rule) => { 161 | rule.selectors = rule.selectors.map((selector) => { 162 | // get List of all classNames in the selector 163 | const classList = getClassNames(selector); 164 | classesNo += classList.size; 165 | classList.forEach((className) => { 166 | // Generate new className 167 | let oldClassName = "." + className; 168 | let newClassName; 169 | if (classIgnore.includes(className) || classMethod == "none") { 170 | newClassName = className; 171 | } else if (classMethod == "simple") { 172 | newClassName = simplifyString(className); 173 | } else { 174 | newClassName = getRandomName(length); 175 | } 176 | newClassName = `.${classPrefix}${newClassName}${classSuffix}`; 177 | validCssClassName = '.'+escapeClassName(oldClassName.slice(1)); 178 | //cond 179 | octalValidCssClassName = '.'+octalizeClassName(oldClassName.slice(1)); 180 | // If ClassName already exist replace with its value else generate new : the should have same name. 181 | console.log("validCssClassName:", validCssClassName); 182 | if (jsonData.hasOwnProperty(oldClassName)) { 183 | selector = selector.replace( 184 | validCssClassName, 185 | jsonData[oldClassName] 186 | ); 187 | //cond 188 | selector = selector.replace( 189 | octalValidCssClassName, 190 | jsonData[oldClassName] 191 | ); 192 | } else { 193 | selector = selector.replace(validCssClassName, newClassName); 194 | //cond 195 | selector = selector.replace(octalValidCssClassName, newClassName); 196 | jsonData[oldClassName] = newClassName; 197 | } 198 | singleFileData[oldClassName] = newClassName; 199 | }); 200 | if (ids) { 201 | idList = getIdNames(selector); 202 | idList.forEach((idName) => { 203 | idsNo++; 204 | // Get only idName not other elements or pseudo-element & remove spaces. 205 | let oldIdName = idName; 206 | // Generate new idName 207 | let newIdName; 208 | if (idIgnore.includes(idName) || idMethod == "none") { 209 | newIdName = idName.splice(1); 210 | } else if (idMethod == "simple") { 211 | newIdName = simplifyString(idName); 212 | } else { 213 | newIdName = getRandomName(length); 214 | } 215 | newIdName = `#${idPrefix}${newIdName}${idSuffix}`; 216 | 217 | if (jsonData.hasOwnProperty(oldIdName)) { 218 | selector = selector.replace(oldIdName, jsonData[oldIdName]); 219 | } else { 220 | selector = selector.replace(oldIdName, newIdName); 221 | jsonData[oldIdName] = newIdName; 222 | } 223 | singleFileData[oldIdName] = newIdName; 224 | }); 225 | } 226 | return selector; 227 | }); 228 | }); 229 | jsonData = { ...jsonData, ...singleFileData }; 230 | const fileName = path.basename(root.source.input.file, ".css"); 231 | // If mult & keep same get and replace. 232 | const newjsonsPath = `${jsonsPath}/${multi ? fileName : "main"}.json`; 233 | writeJsonToFile(data, newjsonsPath, formatJson, fresh, !multi & fresh); 234 | if (cssNo == cssFilesNo) { 235 | copyDirectory(srcPath, desPath, true) 236 | .then(() => { 237 | logger( 238 | "info", 239 | pluginName, 240 | "Copying:", 241 | `${srcPath} to ${desPath} finished!` 242 | ); 243 | replaceJsonKeysInFiles( 244 | desPath, 245 | extensions, 246 | htmlExcludes, 247 | jsonsPath, 248 | indicatorStart, 249 | indicatorEnd, 250 | keepData 251 | ); 252 | logger( 253 | "info", 254 | pluginName, 255 | "Replacing:", 256 | `All files have been updated!` 257 | ); 258 | logger( 259 | "success", 260 | pluginName, 261 | "Processed:", 262 | `${cssFilesNo}/${getFileCount( 263 | srcPath, 264 | [".css"], 265 | [] 266 | )} CSS| ${getFileCount( 267 | srcPath, 268 | extensions, 269 | htmlExcludes 270 | )}/${getFileCount(srcPath, extensions, [])} Files| ${ 271 | classesNo - classIgnore.length 272 | }/${classesNo} Class| ${idsNo - idIgnore.length}/${idsNo} Id` 273 | ); 274 | callBack(); 275 | console.info( 276 | "\x1b[38;2;99;102;241m%s\x1b[0m", 277 | "==========================================================================>", 278 | "\x1b[0m" 279 | ); 280 | }) 281 | .catch((error) => { 282 | logger( 283 | "error", 284 | pluginName, 285 | "Error copying directory:", 286 | error.message 287 | ); 288 | }); 289 | } 290 | } 291 | processedFiles.add(jsonsPath); 292 | }, 293 | }; 294 | }; 295 | 296 | module.exports.postcss = true; 297 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-obfuscator", 3 | "version": "1.6.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@nodelib/fs.scandir": { 8 | "version": "2.1.5", 9 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 10 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 11 | "dev": true, 12 | "requires": { 13 | "@nodelib/fs.stat": "2.0.5", 14 | "run-parallel": "^1.1.9" 15 | } 16 | }, 17 | "@nodelib/fs.stat": { 18 | "version": "2.0.5", 19 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 20 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 21 | "dev": true 22 | }, 23 | "@nodelib/fs.walk": { 24 | "version": "1.2.8", 25 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 26 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 27 | "dev": true, 28 | "requires": { 29 | "@nodelib/fs.scandir": "2.1.5", 30 | "fastq": "^1.6.0" 31 | } 32 | }, 33 | "acorn": { 34 | "version": "7.4.1", 35 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 36 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 37 | "dev": true 38 | }, 39 | "acorn-node": { 40 | "version": "1.8.2", 41 | "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", 42 | "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", 43 | "dev": true, 44 | "requires": { 45 | "acorn": "^7.0.0", 46 | "acorn-walk": "^7.0.0", 47 | "xtend": "^4.0.2" 48 | } 49 | }, 50 | "acorn-walk": { 51 | "version": "7.2.0", 52 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", 53 | "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", 54 | "dev": true 55 | }, 56 | "ansi-regex": { 57 | "version": "5.0.1", 58 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 59 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 60 | "dev": true 61 | }, 62 | "ansi-styles": { 63 | "version": "4.3.0", 64 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 65 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 66 | "dev": true, 67 | "requires": { 68 | "color-convert": "^2.0.1" 69 | } 70 | }, 71 | "anymatch": { 72 | "version": "3.1.3", 73 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 74 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 75 | "dev": true, 76 | "requires": { 77 | "normalize-path": "^3.0.0", 78 | "picomatch": "^2.0.4" 79 | } 80 | }, 81 | "arg": { 82 | "version": "5.0.2", 83 | "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", 84 | "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", 85 | "dev": true 86 | }, 87 | "binary-extensions": { 88 | "version": "2.2.0", 89 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 90 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 91 | "dev": true 92 | }, 93 | "braces": { 94 | "version": "3.0.2", 95 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 96 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 97 | "dev": true, 98 | "requires": { 99 | "fill-range": "^7.0.1" 100 | } 101 | }, 102 | "camelcase-css": { 103 | "version": "2.0.1", 104 | "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", 105 | "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", 106 | "dev": true 107 | }, 108 | "chokidar": { 109 | "version": "3.5.3", 110 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 111 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 112 | "dev": true, 113 | "requires": { 114 | "anymatch": "~3.1.2", 115 | "braces": "~3.0.2", 116 | "fsevents": "~2.3.2", 117 | "glob-parent": "~5.1.2", 118 | "is-binary-path": "~2.1.0", 119 | "is-glob": "~4.0.1", 120 | "normalize-path": "~3.0.0", 121 | "readdirp": "~3.6.0" 122 | }, 123 | "dependencies": { 124 | "glob-parent": { 125 | "version": "5.1.2", 126 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 127 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 128 | "dev": true, 129 | "requires": { 130 | "is-glob": "^4.0.1" 131 | } 132 | } 133 | } 134 | }, 135 | "cliui": { 136 | "version": "8.0.1", 137 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 138 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 139 | "dev": true, 140 | "requires": { 141 | "string-width": "^4.2.0", 142 | "strip-ansi": "^6.0.1", 143 | "wrap-ansi": "^7.0.0" 144 | } 145 | }, 146 | "color-convert": { 147 | "version": "2.0.1", 148 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 149 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 150 | "dev": true, 151 | "requires": { 152 | "color-name": "~1.1.4" 153 | } 154 | }, 155 | "color-name": { 156 | "version": "1.1.4", 157 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 158 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 159 | "dev": true 160 | }, 161 | "css-selector-parser": { 162 | "version": "3.0.4", 163 | "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.4.tgz", 164 | "integrity": "sha512-pnmS1dbKsz6KA4EW4BznyPL2xxkNDRg62hcD0v8g6DEw2W7hxOln5M953jsp9hmw5Dg57S6o/A8GOn37mbAgcQ==", 165 | "dev": true 166 | }, 167 | "cssesc": { 168 | "version": "3.0.0", 169 | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 170 | "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 171 | "dev": true 172 | }, 173 | "defined": { 174 | "version": "1.0.1", 175 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", 176 | "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", 177 | "dev": true 178 | }, 179 | "dependency-graph": { 180 | "version": "0.11.0", 181 | "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", 182 | "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", 183 | "dev": true 184 | }, 185 | "detective": { 186 | "version": "5.2.1", 187 | "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", 188 | "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", 189 | "dev": true, 190 | "requires": { 191 | "acorn-node": "^1.8.2", 192 | "defined": "^1.0.0", 193 | "minimist": "^1.2.6" 194 | } 195 | }, 196 | "didyoumean": { 197 | "version": "1.2.2", 198 | "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", 199 | "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", 200 | "dev": true 201 | }, 202 | "dir-glob": { 203 | "version": "3.0.1", 204 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 205 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 206 | "dev": true, 207 | "requires": { 208 | "path-type": "^4.0.0" 209 | } 210 | }, 211 | "dlv": { 212 | "version": "1.1.3", 213 | "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 214 | "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", 215 | "dev": true 216 | }, 217 | "emoji-regex": { 218 | "version": "8.0.0", 219 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 220 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 221 | "dev": true 222 | }, 223 | "escalade": { 224 | "version": "3.1.1", 225 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 226 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 227 | "dev": true 228 | }, 229 | "fast-glob": { 230 | "version": "3.2.12", 231 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", 232 | "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", 233 | "dev": true, 234 | "requires": { 235 | "@nodelib/fs.stat": "^2.0.2", 236 | "@nodelib/fs.walk": "^1.2.3", 237 | "glob-parent": "^5.1.2", 238 | "merge2": "^1.3.0", 239 | "micromatch": "^4.0.4" 240 | }, 241 | "dependencies": { 242 | "glob-parent": { 243 | "version": "5.1.2", 244 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 245 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 246 | "dev": true, 247 | "requires": { 248 | "is-glob": "^4.0.1" 249 | } 250 | } 251 | } 252 | }, 253 | "fastq": { 254 | "version": "1.15.0", 255 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 256 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 257 | "dev": true, 258 | "requires": { 259 | "reusify": "^1.0.4" 260 | } 261 | }, 262 | "fill-range": { 263 | "version": "7.0.1", 264 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 265 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 266 | "dev": true, 267 | "requires": { 268 | "to-regex-range": "^5.0.1" 269 | } 270 | }, 271 | "fs-extra": { 272 | "version": "11.1.1", 273 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", 274 | "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", 275 | "dev": true, 276 | "requires": { 277 | "graceful-fs": "^4.2.0", 278 | "jsonfile": "^6.0.1", 279 | "universalify": "^2.0.0" 280 | } 281 | }, 282 | "fsevents": { 283 | "version": "2.3.2", 284 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 285 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 286 | "dev": true, 287 | "optional": true 288 | }, 289 | "function-bind": { 290 | "version": "1.1.1", 291 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 292 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 293 | "dev": true 294 | }, 295 | "get-caller-file": { 296 | "version": "2.0.5", 297 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 298 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 299 | "dev": true 300 | }, 301 | "get-stdin": { 302 | "version": "9.0.0", 303 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", 304 | "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", 305 | "dev": true 306 | }, 307 | "glob-parent": { 308 | "version": "6.0.2", 309 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 310 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 311 | "dev": true, 312 | "requires": { 313 | "is-glob": "^4.0.3" 314 | } 315 | }, 316 | "globby": { 317 | "version": "13.1.4", 318 | "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", 319 | "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", 320 | "dev": true, 321 | "requires": { 322 | "dir-glob": "^3.0.1", 323 | "fast-glob": "^3.2.11", 324 | "ignore": "^5.2.0", 325 | "merge2": "^1.4.1", 326 | "slash": "^4.0.0" 327 | }, 328 | "dependencies": { 329 | "slash": { 330 | "version": "4.0.0", 331 | "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", 332 | "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", 333 | "dev": true 334 | } 335 | } 336 | }, 337 | "graceful-fs": { 338 | "version": "4.2.11", 339 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 340 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 341 | "dev": true 342 | }, 343 | "has": { 344 | "version": "1.0.3", 345 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 346 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 347 | "dev": true, 348 | "requires": { 349 | "function-bind": "^1.1.1" 350 | } 351 | }, 352 | "ignore": { 353 | "version": "5.2.4", 354 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", 355 | "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", 356 | "dev": true 357 | }, 358 | "is-binary-path": { 359 | "version": "2.1.0", 360 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 361 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 362 | "dev": true, 363 | "requires": { 364 | "binary-extensions": "^2.0.0" 365 | } 366 | }, 367 | "is-core-module": { 368 | "version": "2.11.0", 369 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 370 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 371 | "dev": true, 372 | "requires": { 373 | "has": "^1.0.3" 374 | } 375 | }, 376 | "is-extglob": { 377 | "version": "2.1.1", 378 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 379 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 380 | "dev": true 381 | }, 382 | "is-fullwidth-code-point": { 383 | "version": "3.0.0", 384 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 385 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 386 | "dev": true 387 | }, 388 | "is-glob": { 389 | "version": "4.0.3", 390 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 391 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 392 | "dev": true, 393 | "requires": { 394 | "is-extglob": "^2.1.1" 395 | } 396 | }, 397 | "is-number": { 398 | "version": "7.0.0", 399 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 400 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 401 | "dev": true 402 | }, 403 | "jsonfile": { 404 | "version": "6.1.0", 405 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 406 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 407 | "dev": true, 408 | "requires": { 409 | "graceful-fs": "^4.1.6", 410 | "universalify": "^2.0.0" 411 | } 412 | }, 413 | "lilconfig": { 414 | "version": "2.1.0", 415 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", 416 | "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", 417 | "dev": true 418 | }, 419 | "merge2": { 420 | "version": "1.4.1", 421 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 422 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 423 | "dev": true 424 | }, 425 | "micromatch": { 426 | "version": "4.0.5", 427 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 428 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 429 | "dev": true, 430 | "requires": { 431 | "braces": "^3.0.2", 432 | "picomatch": "^2.3.1" 433 | } 434 | }, 435 | "minimist": { 436 | "version": "1.2.8", 437 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 438 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 439 | "dev": true 440 | }, 441 | "nanoid": { 442 | "version": "3.3.7", 443 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 444 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 445 | "dev": true 446 | }, 447 | "normalize-path": { 448 | "version": "3.0.0", 449 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 450 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 451 | "dev": true 452 | }, 453 | "object-hash": { 454 | "version": "3.0.0", 455 | "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", 456 | "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", 457 | "dev": true 458 | }, 459 | "path-parse": { 460 | "version": "1.0.7", 461 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 462 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 463 | "dev": true 464 | }, 465 | "path-type": { 466 | "version": "4.0.0", 467 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 468 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 469 | "dev": true 470 | }, 471 | "picocolors": { 472 | "version": "1.0.0", 473 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 474 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 475 | "dev": true 476 | }, 477 | "picomatch": { 478 | "version": "2.3.1", 479 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 480 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 481 | "dev": true 482 | }, 483 | "pify": { 484 | "version": "2.3.0", 485 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 486 | "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 487 | "dev": true 488 | }, 489 | "postcss": { 490 | "version": "8.4.35", 491 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", 492 | "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", 493 | "dev": true, 494 | "requires": { 495 | "nanoid": "^3.3.7", 496 | "picocolors": "^1.0.0", 497 | "source-map-js": "^1.0.2" 498 | } 499 | }, 500 | "postcss-cli": { 501 | "version": "10.1.0", 502 | "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-10.1.0.tgz", 503 | "integrity": "sha512-Zu7PLORkE9YwNdvOeOVKPmWghprOtjFQU3srMUGbdz3pHJiFh7yZ4geiZFMkjMfB0mtTFR3h8RemR62rPkbOPA==", 504 | "dev": true, 505 | "requires": { 506 | "chokidar": "^3.3.0", 507 | "dependency-graph": "^0.11.0", 508 | "fs-extra": "^11.0.0", 509 | "get-stdin": "^9.0.0", 510 | "globby": "^13.0.0", 511 | "picocolors": "^1.0.0", 512 | "postcss-load-config": "^4.0.0", 513 | "postcss-reporter": "^7.0.0", 514 | "pretty-hrtime": "^1.0.3", 515 | "read-cache": "^1.0.0", 516 | "slash": "^5.0.0", 517 | "yargs": "^17.0.0" 518 | }, 519 | "dependencies": { 520 | "postcss-load-config": { 521 | "version": "4.0.1", 522 | "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", 523 | "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", 524 | "dev": true, 525 | "requires": { 526 | "lilconfig": "^2.0.5", 527 | "yaml": "^2.1.1" 528 | } 529 | }, 530 | "yaml": { 531 | "version": "2.2.2", 532 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", 533 | "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", 534 | "dev": true 535 | } 536 | } 537 | }, 538 | "postcss-import": { 539 | "version": "14.1.0", 540 | "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", 541 | "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", 542 | "dev": true, 543 | "requires": { 544 | "postcss-value-parser": "^4.0.0", 545 | "read-cache": "^1.0.0", 546 | "resolve": "^1.1.7" 547 | } 548 | }, 549 | "postcss-js": { 550 | "version": "4.0.1", 551 | "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", 552 | "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", 553 | "dev": true, 554 | "requires": { 555 | "camelcase-css": "^2.0.1" 556 | } 557 | }, 558 | "postcss-load-config": { 559 | "version": "3.1.4", 560 | "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", 561 | "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", 562 | "dev": true, 563 | "requires": { 564 | "lilconfig": "^2.0.5", 565 | "yaml": "^1.10.2" 566 | } 567 | }, 568 | "postcss-nested": { 569 | "version": "6.0.0", 570 | "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", 571 | "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", 572 | "dev": true, 573 | "requires": { 574 | "postcss-selector-parser": "^6.0.10" 575 | } 576 | }, 577 | "postcss-reporter": { 578 | "version": "7.0.5", 579 | "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz", 580 | "integrity": "sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==", 581 | "dev": true, 582 | "requires": { 583 | "picocolors": "^1.0.0", 584 | "thenby": "^1.3.4" 585 | } 586 | }, 587 | "postcss-selector-parser": { 588 | "version": "6.0.11", 589 | "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", 590 | "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", 591 | "dev": true, 592 | "requires": { 593 | "cssesc": "^3.0.0", 594 | "util-deprecate": "^1.0.2" 595 | } 596 | }, 597 | "postcss-value-parser": { 598 | "version": "4.2.0", 599 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 600 | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 601 | "dev": true 602 | }, 603 | "pretty-hrtime": { 604 | "version": "1.0.3", 605 | "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", 606 | "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", 607 | "dev": true 608 | }, 609 | "queue-microtask": { 610 | "version": "1.2.3", 611 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 612 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 613 | "dev": true 614 | }, 615 | "quick-lru": { 616 | "version": "5.1.1", 617 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", 618 | "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", 619 | "dev": true 620 | }, 621 | "read-cache": { 622 | "version": "1.0.0", 623 | "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", 624 | "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 625 | "dev": true, 626 | "requires": { 627 | "pify": "^2.3.0" 628 | } 629 | }, 630 | "readdirp": { 631 | "version": "3.6.0", 632 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 633 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 634 | "dev": true, 635 | "requires": { 636 | "picomatch": "^2.2.1" 637 | } 638 | }, 639 | "require-directory": { 640 | "version": "2.1.1", 641 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 642 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 643 | "dev": true 644 | }, 645 | "resolve": { 646 | "version": "1.22.1", 647 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 648 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 649 | "dev": true, 650 | "requires": { 651 | "is-core-module": "^2.9.0", 652 | "path-parse": "^1.0.7", 653 | "supports-preserve-symlinks-flag": "^1.0.0" 654 | } 655 | }, 656 | "reusify": { 657 | "version": "1.0.4", 658 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 659 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 660 | "dev": true 661 | }, 662 | "run-parallel": { 663 | "version": "1.2.0", 664 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 665 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 666 | "dev": true, 667 | "requires": { 668 | "queue-microtask": "^1.2.2" 669 | } 670 | }, 671 | "slash": { 672 | "version": "5.0.1", 673 | "resolved": "https://registry.npmjs.org/slash/-/slash-5.0.1.tgz", 674 | "integrity": "sha512-ywNzUOiXwetmLvTUiCBZpLi+vxqN3i+zDqjs2HHfUSV3wN4UJxVVKWrS1JZDeiJIeBFNgB5pmioC2g0IUTL+rQ==", 675 | "dev": true 676 | }, 677 | "source-map-js": { 678 | "version": "1.0.2", 679 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 680 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 681 | "dev": true 682 | }, 683 | "string-width": { 684 | "version": "4.2.3", 685 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 686 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 687 | "dev": true, 688 | "requires": { 689 | "emoji-regex": "^8.0.0", 690 | "is-fullwidth-code-point": "^3.0.0", 691 | "strip-ansi": "^6.0.1" 692 | } 693 | }, 694 | "strip-ansi": { 695 | "version": "6.0.1", 696 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 697 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 698 | "dev": true, 699 | "requires": { 700 | "ansi-regex": "^5.0.1" 701 | } 702 | }, 703 | "supports-preserve-symlinks-flag": { 704 | "version": "1.0.0", 705 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 706 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 707 | "dev": true 708 | }, 709 | "tailwindcss": { 710 | "version": "3.2.7", 711 | "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz", 712 | "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==", 713 | "dev": true, 714 | "requires": { 715 | "arg": "^5.0.2", 716 | "chokidar": "^3.5.3", 717 | "color-name": "^1.1.4", 718 | "detective": "^5.2.1", 719 | "didyoumean": "^1.2.2", 720 | "dlv": "^1.1.3", 721 | "fast-glob": "^3.2.12", 722 | "glob-parent": "^6.0.2", 723 | "is-glob": "^4.0.3", 724 | "lilconfig": "^2.0.6", 725 | "micromatch": "^4.0.5", 726 | "normalize-path": "^3.0.0", 727 | "object-hash": "^3.0.0", 728 | "picocolors": "^1.0.0", 729 | "postcss": "^8.0.9", 730 | "postcss-import": "^14.1.0", 731 | "postcss-js": "^4.0.0", 732 | "postcss-load-config": "^3.1.4", 733 | "postcss-nested": "6.0.0", 734 | "postcss-selector-parser": "^6.0.11", 735 | "postcss-value-parser": "^4.2.0", 736 | "quick-lru": "^5.1.1", 737 | "resolve": "^1.22.1" 738 | } 739 | }, 740 | "thenby": { 741 | "version": "1.3.4", 742 | "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", 743 | "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", 744 | "dev": true 745 | }, 746 | "to-regex-range": { 747 | "version": "5.0.1", 748 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 749 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 750 | "dev": true, 751 | "requires": { 752 | "is-number": "^7.0.0" 753 | } 754 | }, 755 | "universalify": { 756 | "version": "2.0.0", 757 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 758 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", 759 | "dev": true 760 | }, 761 | "util-deprecate": { 762 | "version": "1.0.2", 763 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 764 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 765 | "dev": true 766 | }, 767 | "wrap-ansi": { 768 | "version": "7.0.0", 769 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 770 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 771 | "dev": true, 772 | "requires": { 773 | "ansi-styles": "^4.0.0", 774 | "string-width": "^4.1.0", 775 | "strip-ansi": "^6.0.0" 776 | } 777 | }, 778 | "xtend": { 779 | "version": "4.0.2", 780 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 781 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 782 | "dev": true 783 | }, 784 | "y18n": { 785 | "version": "5.0.8", 786 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 787 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 788 | "dev": true 789 | }, 790 | "yaml": { 791 | "version": "1.10.2", 792 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 793 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 794 | "dev": true 795 | }, 796 | "yargs": { 797 | "version": "17.7.2", 798 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 799 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 800 | "dev": true, 801 | "requires": { 802 | "cliui": "^8.0.1", 803 | "escalade": "^3.1.1", 804 | "get-caller-file": "^2.0.5", 805 | "require-directory": "^2.1.1", 806 | "string-width": "^4.2.3", 807 | "y18n": "^5.0.5", 808 | "yargs-parser": "^21.1.1" 809 | } 810 | }, 811 | "yargs-parser": { 812 | "version": "21.1.1", 813 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 814 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 815 | "dev": true 816 | } 817 | } 818 | } 819 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-obfuscator", 3 | "version": "1.6.1", 4 | "description": "PostCSS plugin that helps you protect your CSS code by obfuscating class names and ids. with customizable configuration.", 5 | "main": "index.js", 6 | "scripts": { 7 | "tailwindcss": "cd test/demo && npx tailwindcss -i ./src/css/input.css -o ./src/css/output.css --watch", 8 | "postcss": "postcss test/demo/src/**/*.css --dir test/demo/out/css", 9 | "postcss:watch": "postcss test/demo/src/**/*.css --dir test/demo/build --watch", 10 | "obfuscate": "node test/demo/postcss-obfuscate", 11 | "test": "node test/test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/n4j1Br4ch1D/postcss-obfuscator.git" 16 | }, 17 | "keywords": [ 18 | "postcss", 19 | "plugin", 20 | "obfuscation", 21 | "css", 22 | "css classes", 23 | "class renamer", 24 | "postcss-rename-selectors", 25 | "class prefixer", 26 | "Postcss obfuscator", 27 | "PostCSS obfuscation plugin", 28 | "CSS obfuscation", 29 | "Class name scrambling", 30 | "CSS security", 31 | "Obfuscate CSS code", 32 | "Protect CSS code", 33 | "Prevent CSS reverse-engineering", 34 | "tailwindcss", 35 | "tailwindcss classes list", 36 | "tailwindcss classes array json", 37 | "bootstrap", 38 | "bootstrap classes array json", 39 | "Scramble HTML classes", 40 | "CSS anti-theft protection", 41 | "code privacy", 42 | "CSS code obfuscator", 43 | "CSS class name encryption", 44 | "anti web scraping", 45 | "Anti-scraping tools", 46 | "Anti-scraping technology", 47 | "Web scraping prevention", 48 | "Web crawling protection" 49 | ], 50 | "author": "Najib Rachid", 51 | "license": "MIT", 52 | "bugs": { 53 | "url": "https://github.com/n4j1Br4ch1D/postcss-obfuscator/issues" 54 | }, 55 | "homepage": "https://github.com/n4j1Br4ch1D/postcss-obfuscator#readme", 56 | "devDependencies": { 57 | "css-selector-parser": "^3.0.4", 58 | "postcss": "^8.4.35", 59 | "postcss-cli": "^10.1.0", 60 | "tailwindcss": "^3.2.7" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n4j1Br4ch1D/postcss-obfuscator/fac57af5ff8df042e4c03ab0011468990d8e686b/style.css -------------------------------------------------------------------------------- /test/demos/simple/postcss-obfuscate.js: -------------------------------------------------------------------------------- 1 | const postcss = require("postcss"); 2 | const obfuscator = require("../../index"); 3 | 4 | postcss([obfuscator({ 5 | /* options */ 6 | })]) 7 | .process(css) 8 | .then((result) => { 9 | console.log("Task Completed!", result); 10 | }) 11 | .catch((error) => { 12 | console.error(error); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /test/demos/simple/postcss.config.js: -------------------------------------------------------------------------------- 1 | const obfuscator = require('../../index'); 2 | 3 | // Check if in development mode 4 | const isDevMode = process.env.NODE_ENV === 'development'; //production 5 | 6 | // config 7 | const options = { 8 | enable: isDevMode, // Run on Dev Env 9 | length: 5, // Random name length 10 | classMethod: 'random', // 'random', 'simple', 'none' obfuscation method for classes 11 | classPrefix: "c-", // ClassName prefix 12 | classSuffix: "-c", // ClassName suffix 13 | classIgnore: ['red'], // Class to ignore from obfuscation 14 | ids: true, // Obfuscate #IdNames 15 | idMethod: 'random', // 'random', 'simple', 'none' obfuscation method for ids 16 | idPrefix: "i-", // idName Prefix 17 | idSuffix: "-i", // idName suffix 18 | idIgnore: [], // Ids to ignore from obfuscation 19 | indicatorStart: null, // Identify ids & classes by the preceding string. 20 | indicatorEnd: null, // Identify ids & classes by the following string. 21 | jsonsPath: "test\\demo\\css-obfuscator", // Path and file name where to save obfuscation data. 22 | srcPath: "test\\demo\\src", // Source of your files. 23 | desPath: "test\\demo\\out", // Destination for obfuscated html/js/.. files. Be careful using the same directory as your src(you will lose your original files). 24 | extensions: ['.html', '.htm'], // Extesnion of files you want osbfucated ['.html', '.php', '.js', '.svelte'] 25 | htmlExcludes: ['404.html'], // Files and paths to exclude from html obfuscation replacement 26 | cssExcludes: ['test\\demo\\src\\css\\exclude.css', 'test\\demo\\src\\css\\top\\'], // Files and paths to exclude from css obfuscation 27 | fresh: false, // Create new obfuscation data list or use already existed one (to keep production cache or prevent data scrapping). 28 | multi: false, // Generate obsfucated data file for each css file. 29 | differMulti: false, // Generate different Random names for each file. 30 | formatJson: true, // Format obfuscation data JSON file. 31 | showConfig: false, // Show config on terminal when runinng 32 | keepData: true, // Keep or delete Data after obfuscation is finished? 33 | // preRun: () => Promise.resolve(), // do something before the plugin runs. 34 | // callBack: function () {console.log("Call Me back! ;)");} // Callback function to call after obfuscation is done. 35 | }; 36 | 37 | module.exports = { 38 | plugins: [ 39 | obfuscator(options), 40 | ], 41 | } 42 | -------------------------------------------------------------------------------- /test/demos/simple/src/css/app.css: -------------------------------------------------------------------------------- 1 | .num1a{ 2 | color:red; 3 | } 4 | #myid > main #another.lily{ 5 | 6 | } 7 | #about-me{ 8 | 9 | } -------------------------------------------------------------------------------- /test/demos/simple/src/css/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /test/demos/simple/src/html/404.html: -------------------------------------------------------------------------------- 1 |
hhhh
-------------------------------------------------------------------------------- /test/demos/simple/src/html/app.html: -------------------------------------------------------------------------------- 1 |
hhhh
2 |
hhhh
3 | 4 | 5 | … 6 | 7 | 8 |
9 | Hire me 10 |

11 | I am a full-stack engineer ... 12 |

13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/demos/simple/src/html/index.htm: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

6 | Hello world! 7 |

-------------------------------------------------------------------------------- /test/demos/simple/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{htm, html,js}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | const postcss = require("postcss"); 3 | const obfuscator = require("../../index"); 4 | const { 5 | getRandomName, 6 | simplifyString, 7 | writeJsonToFile, 8 | copyDirectory, 9 | replaceJsonKeysInFiles, 10 | getFileCount, 11 | getClassNames, 12 | getIdNames, 13 | logger, 14 | getRelativePath, 15 | isFileOrInDirectory, 16 | } = require("./utils"); 17 | // test default options. 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/unit/getClassNames.js: -------------------------------------------------------------------------------- 1 | const { getClassNames } = require("../../utils"); 2 | 3 | 4 | 5 | function assertClasses(actualSelector, expectedClasses) { 6 | let foundClasses = getClassNames(actualSelector); 7 | if (foundClasses.size !== expectedClasses.length) { 8 | console.log(expectedClasses, foundClasses); 9 | throw new Error( 10 | `Assertion failed: expected ${expectedClasses.length} but got ${foundClasses.size}` 11 | ); 12 | } 13 | const allClassesMatch = expectedClasses.every((expectedClass) => 14 | foundClasses.has(expectedClass) 15 | ); 16 | if (!allClassesMatch) { 17 | console.log(expectedClasses, foundClasses); 18 | throw new Error( 19 | `Assertion failed: expected classes and found classes do not match.` 20 | ); 21 | } 22 | } 23 | 24 | const matchList = { 25 | // Simple Selectors 26 | "h2 #first.second": ["second"], 27 | "#id": [], 28 | ".first.second": ["first", "second"], 29 | ".first .second": ["first", "second"], 30 | ".first:hover": ["first"], 31 | ".first::before": ["first"], 32 | "article": [], // semantic tags. 33 | "unknowntag": [], 34 | 35 | // Advanced Selectors 36 | ".first > .second#sdsa": ["first", "second"], 37 | ".btn-slctn:selection": ["btn-slctn"], //psudo elements 38 | ".btn-slctn::selection": ["btn-slctn"], 39 | ".btn-moz:-moz-focusring": ["btn-moz"], 40 | ".btn-moz::-moz-focusring": ["btn-moz"], 41 | 'img[src$=".png"]': [], 42 | "div:not(.highlight)": ["highlight"], 43 | "div:not(.highlight, .example) .test :hover": [ 44 | "highlight", 45 | "example", 46 | "test", 47 | ], 48 | "@keyframes spin": [], // Keyframes 49 | "from": [], 50 | "to": [], 51 | "0%, 100%": [], 52 | "50%": [], 53 | "& .childclass": ["childclass"], // Native CSS & Nasting 54 | "&:hover": [], 55 | '.two[class*="test"]': ['two'], // "contains" selector 56 | 57 | // Bootsrap Selectors 58 | ".form-range:disabled::-moz-range-thumb": ["form-range"], 59 | ".was-validated :valid ~ .valid-feedback,.was-validated :valid ~ .valid-tooltip,.is-valid ~ .valid-feedback,.is-valid ~ .valid-tooltip": 60 | ["was-validated", "valid-feedback", "valid-tooltip", "is-valid"], 61 | ".was-validated .form-control:valid, .form-control.is-valid": [ 62 | "was-validated", 63 | "form-control", 64 | "is-valid", 65 | ], 66 | '.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"]': 67 | ["was-validated", "form-select", "is-valid"], 68 | ".was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked": 69 | ["was-validated", "form-check-input", "is-valid"], 70 | ".was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label": 71 | ["was-validated", "form-check-input", "form-check-label", "is-valid"], 72 | ".btn-check:focus + .btn-light, .btn-light:focus": ["btn-check", "btn-light"], 73 | ".btn-check:checked + .btn-light, .btn-check:active + .btn-light, .btn-light:active, .btn-light.active, .show > .btn-light.dropdown-toggle": 74 | ["btn-check", "btn-light", "active", "show", "dropdown-toggle"], 75 | ".btn-check:checked + .btn-light:focus, .btn-check:active + .btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show > .btn-light.dropdown-toggle:focus": 76 | ["btn-check", "btn-light", "active", "show", "dropdown-toggle"], 77 | ".dropdown-toggle:empty::after": ["dropdown-toggle"], 78 | ".dropdown-menu-dark .dropdown-item.disabled, .dropdown-menu-dark .dropdown-item:disabled": 79 | ["dropdown-menu-dark", "dropdown-item", "disabled"], 80 | ".btn-group > .btn:not(:last-child):not(.dropdown-toggle),.btn-group > .btn-group:not(:last-child) > .btn": 81 | ["btn-group", "dropdown-toggle", "btn"], 82 | ".btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), .btn-group-vertical > .btn-group:not(:last-child) > .btn": 83 | ["btn-group-vertical", "dropdown-toggle", "dropdown-toggle", "btn"], 84 | ".navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus": 85 | ["navbar-dark", "navbar-nav", "nav-link"], 86 | ".card-group > .card + .card": ["card-group", "card"], 87 | ".card-group > .card:not(:last-child)": ["card-group", "card"], 88 | ".card-group > .card:not(:first-child) .card-img-bottom,.card-group > .card:not(:first-child) .card-footer": 89 | ["card-group", "card", "card-img-bottom", "card-footer"], 90 | ".accordion-button:not(.collapsed)::after": ["accordion-button", "collapsed"], 91 | ".list-group-horizontal-xxl > .list-group-item:last-child": [ 92 | "list-group-horizontal-xxl", 93 | "list-group-item", 94 | ], 95 | ".list-group-horizontal-xxl > .list-group-item + .list-group-item": [ 96 | "list-group-horizontal-xxl", 97 | "list-group-item", 98 | ], 99 | ".list-group-horizontal-xxl > .list-group-item + .list-group-item.active": [ 100 | "list-group-horizontal-xxl", 101 | "list-group-item", 102 | "active", 103 | ], 104 | ".list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus": 105 | ["list-group-item-primary", "list-group-item-action"], 106 | ".bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before": 107 | ["bs-tooltip-start", "tooltip-arrow", "bs-tooltip-auto"], 108 | ".bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before": 109 | ["bs-popover-start", "popover-arrow", "bs-popover-auto"], 110 | 111 | // TailwindCss Selectors 112 | ".xl\\:hover\\:translate-x-0:hover": ["xl:hover:translate-x-0"], //hover 113 | ".space-x-3 > :not([hidden]) ~ :not([hidden])": ["space-x-3"], 114 | ".focus\\:ring-gray-100:focus": ["focus:ring-gray-100"], 115 | ".dark .dark\\:border-gray-700": ["dark", "dark:border-gray-700"], 116 | ".dark .dark\\:hover\\:bg-gray-600:hover": ["dark", "dark:hover:bg-gray-600"], //dark 117 | ".md\\:text-3xl": ["md:text-3xl"], 118 | ".dark .dark\\:bg-gray-800\\/50": ["dark", "dark:bg-gray-800/50"], 119 | ".py-0\\.5": ["py-0.5"], 120 | ".w-\\[1\\.5rem\\]": ['w-[1.5rem]'], 121 | ".\\[\\&_a\\]\\:will-change-\\[padding-left\\2c _color\\] a": [ 122 | "[&_a]:will-change-[padding-left,_color]", 123 | ], 124 | ".\\[\\&_a\\]\\:\\[transition\\:padding-left_cubic-bezier\\(\\.15\\2c 1\\.6\\2c \\.75\\2c 1\\)_300ms\\2c color_ease_300ms\\] a": 125 | [ 126 | "[&_a]:[transition:padding-left_cubic-bezier(.15,1.6,.75,1)_300ms,color_ease_300ms]", 127 | ], 128 | //
lol
129 | //
lol
130 | //
lol
131 | //
lol
132 | //
133 | ".-translate-x-1\\/2": ["-translate-x-1/2"], //negative 134 | ".\\32xl\\:grid-cols-3": ["2xl:grid-cols-3"], //octal 135 | ".\\3exl\\:p-8": [">xl:p-8"], 136 | ".pt-\\[100px\\]": ["pt-[100px]"], //custome 137 | ".bg-\\[\\#F6F8F9\\]": ["bg-[#F6F8F9]"], 138 | ".data-\\[status\\=\\'active\\'\\]\\:bg-green-100[data-status='active']": [ 139 | "data-[status='active']:bg-green-100", 140 | ], 141 | ".data-\\[size\\=large\\]\\:p-8[data-size=large]": ["data-[size=large]:p-8"], 142 | ".data-\\[href\\=\\'active\\'\\]\\:bg-green-100[data-href='active']": [ 143 | "data-[href='active']:bg-green-100", 144 | ], 145 | ".data-\\[size\\=large\\]\\:p-8[data-size=large]": ["data-[size=large]:p-8"], 146 | ".group[data-status='active'] .group-data-\\[status\\=\\'active\\'\\]\\:text-green-700": 147 | ["group", "group-data-[status='active']:text-green-700"], 148 | ".group.is-published .group-\\[\\.is-published\\]\\:block": [ 149 | "group", 150 | "is-published", 151 | "group-[.is-published]:block", 152 | ], 153 | ".peer.is-dirty:required ~ .peer-\\[\\.is-dirty\\]\\:peer-required\\:block": [ 154 | "peer", 155 | "is-dirty", 156 | "peer-[.is-dirty]:peer-required:block", 157 | ], 158 | ":nth-of-type(3) .group .group-\\[\\:nth-of-type\\(3\\)_\\&\\]\\:block": [ 159 | "group", 160 | "group-[:nth-of-type(3)_&]:block", 161 | ], 162 | ".\\[\\@supports\\(display\\:grid\\)\\]\\:grid": [ 163 | "[@supports(display:grid)]:grid", 164 | ], 165 | ".\\[\\@media\\(any-hover\\:hover\\)\\{\\&\\:hover\\}\\]\\:opacity-100:hover": 166 | ["[@media(any-hover:hover){&:hover}]:opacity-100"], 167 | ".\\[\\&\\:hover\\]\\:bg-white\\/50:hover": ["[&:hover]:bg-white/50"], 168 | ".\\[\\@supports\\(backdrop-filter\\:blur\\(0\\)\\)\\]\\:backdrop-blur": [ 169 | "[@supports(backdrop-filter:blur(0))]:backdrop-blur", 170 | ], 171 | ".\\[\\@supports\\(backdrop-filter\\:blur\\(0\\)\\)\\]\\:bg-white\\/50": [ 172 | "[@supports(backdrop-filter:blur(0))]:bg-white/50", 173 | ], 174 | ".\\*\\:pt-4 > *": ["*:pt-4"], //global 175 | }; 176 | 177 | Object.entries(matchList).forEach(([selector, classes]) => 178 | assertClasses(selector, classes) 179 | ); 180 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const readline = require("readline"); 4 | const {createParser} = require('css-selector-parser'); 5 | 6 | const pluginName = "PostCSS Obfuscator"; 7 | 8 | function getRandomName(length) { 9 | // Generate a random string of characters with the specified length 10 | const randomString = Math.random() 11 | .toString(36) 12 | .substring(2, length - 1 + 2); 13 | // Combine the random string with a prefix to make it a valid class name (starts with a letter, contains only letters, digits, hyphens, and underscores) 14 | const randomLetter = String.fromCharCode(Math.floor(Math.random() * 26) + 97); // 97 is the ASCII code for lowercase 'a' 15 | return `${randomLetter}${randomString}`; 16 | } 17 | 18 | function simplifyString(str) { 19 | tempStr = str.replace(/[aeiouw\d_-]/gi, ""); 20 | return tempStr.length < 1 21 | ? String.fromCharCode(Math.floor(Math.random() * 26) + 97) + tempStr 22 | : tempStr; 23 | } 24 | 25 | function writeJsonToFile( 26 | data, 27 | filePath, 28 | format = true, 29 | fresh = false, 30 | startOver = false 31 | ) { 32 | // If startOver is true, remove the directory path 33 | const dirPath = filePath.substring(0, filePath.lastIndexOf("/")); 34 | if (startOver) { 35 | if (fs.existsSync(dirPath)) { 36 | fs.rmSync(dirPath, { recursive: true }); 37 | logger("info", pluginName, "Directory removed:", dirPath); 38 | } 39 | } 40 | 41 | // Create the directory path if it doesn't exist 42 | if (!fs.existsSync(dirPath)) { 43 | fs.mkdirSync(dirPath, { recursive: true }); 44 | logger("info", pluginName, "Directory created:", dirPath); 45 | } 46 | 47 | // Check if the file exists 48 | if (!fs.existsSync(filePath)) { 49 | // Create the file with an empty object as the default content 50 | fs.writeFileSync(filePath, "{}"); 51 | logger("info", pluginName, "File created:", filePath); 52 | } 53 | 54 | // Read the existing JSON data from the file 55 | let jsonData = fs.readFileSync(filePath); 56 | 57 | // Parse the existing JSON data 58 | let parsedData = JSON.parse(jsonData); 59 | 60 | // Merge the new data with the existing data 61 | const mergedData = fresh ? data : { ...data, ...parsedData }; 62 | 63 | // Write the merged data to the file 64 | const outputData = format 65 | ? JSON.stringify(mergedData, null, 2) 66 | : JSON.stringify(mergedData); 67 | fs.writeFileSync(filePath, outputData); 68 | logger("info", pluginName, "Data written to file:", filePath); 69 | } 70 | 71 | function replaceJsonKeysInFiles( 72 | filesDir, 73 | htmlExtensions, 74 | htmlExclude, 75 | jsonDataPath, 76 | indicatorStart, 77 | indicatorEnd, 78 | keepData 79 | ) { 80 | // Read and merge the JSON data 81 | const jsonData = {}; 82 | fs.readdirSync(jsonDataPath).forEach((file) => { 83 | const filePath = path.join(jsonDataPath, file); 84 | const fileData = JSON.parse(fs.readFileSync(filePath, "utf-8")); 85 | Object.assign(jsonData, fileData); 86 | }); 87 | 88 | // Read and process the files 89 | const replaceJsonKeysInFile = (filePath) => { 90 | const fileExt = path.extname(filePath).toLowerCase(); 91 | if (fs.statSync(filePath).isDirectory()) { 92 | // Recursively process all files in subdirectories 93 | fs.readdirSync(filePath).forEach((subFilePath) => { 94 | replaceJsonKeysInFile(path.join(filePath, subFilePath)); 95 | }); 96 | } else if ( 97 | htmlExtensions.includes(fileExt) && 98 | !htmlExclude.includes(path.basename(filePath)) 99 | ) { 100 | // Replace JSON keys in the file 101 | let fileContent = fs.readFileSync(filePath, "utf-8"); 102 | Object.keys(jsonData).forEach((key) => { 103 | let keyUse = escapeRegExp(key.slice(1).replace(/\\/g, "")); 104 | let regex; 105 | regex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`])`, 'g'); // match exact wording & avoid ` ' "" 106 | fileContent = fileContent.replace(regex, `$1` + jsonData[key].slice(1).replace(/\\/g, "")); // capture preceding space 107 | if (indicatorStart || indicatorEnd) { 108 | regex = new RegExp(`([\\s"'\\\`]|^)(${indicatorStart ?? ''}${keyUse})(?=$|[\\s"'\\\`])`, 'g'); 109 | fileContent = fileContent.replace(regex, `$1` + jsonData[key].slice(1).replace(/\\/g, "")); 110 | regex = new RegExp(`([\\s"'\\\`]|^)(${keyUse}${indicatorEnd ?? ''})(?=$|[\\s"'\\\`])`, 'g'); 111 | fileContent = fileContent.replace(regex, `$1` + jsonData[key].slice(1).replace(/\\/g, "")); 112 | regex = new RegExp(`([\\s"'\\\`]|^)(${indicatorStart ?? ''}${keyUse}${indicatorEnd ?? ''})(?=$|[\\s"'\\\`])`, 'g'); 113 | fileContent = fileContent.replace(regex, `$1` + jsonData[key].slice(1).replace(/\\/g, "")); 114 | } 115 | }); 116 | fs.writeFileSync(filePath, fileContent); 117 | } 118 | if (!keepData) { 119 | if (fs.existsSync(jsonDataPath)) { 120 | fs.rmSync(jsonDataPath, { recursive: true }); 121 | logger("info", pluginName, "Data removed:", jsonDataPath); 122 | } 123 | } 124 | }; 125 | 126 | // Process all files in the directory 127 | replaceJsonKeysInFile(filesDir); 128 | } 129 | 130 | function copyDirectory(source, destination, copyHiddenFiles = false) { 131 | return new Promise((resolve, reject) => { 132 | // Create the destination directory if it doesn't exist 133 | if (!fs.existsSync(destination)) { 134 | fs.mkdirSync(destination); 135 | } 136 | 137 | // Get a list of all the files and directories in the source directory 138 | const files = fs.readdirSync(source); 139 | 140 | // Loop through the files and directories 141 | for (const file of files) { 142 | // Check if hidden file should be copied 143 | if (!copyHiddenFiles && file.startsWith(".")) { 144 | continue; 145 | } 146 | const sourcePath = path.join(source, file); 147 | const destPath = path.join(destination, file); 148 | 149 | // Check if the current item is a directory 150 | if (fs.statSync(sourcePath).isDirectory()) { 151 | // Recursively copy the directory 152 | copyDirectory(sourcePath, destPath); 153 | } else { 154 | // Copy the file 155 | fs.copyFileSync(sourcePath, destPath); 156 | } 157 | } 158 | 159 | // All files and directories have been copied 160 | resolve(); 161 | }); 162 | } 163 | 164 | function getFileCount(directoryPath, extensions, excludePathsOrFiles = []) { 165 | let count = 0; 166 | const files = fs.readdirSync(directoryPath); 167 | files.forEach((file) => { 168 | const filePath = path.join(directoryPath, file); 169 | const isExcluded = excludePathsOrFiles.some((excludePathOrFile) => { 170 | return ( 171 | excludePathOrFile === file || 172 | excludePathOrFile === filePath || 173 | excludePathOrFile === path.basename(filePath) 174 | ); 175 | }); 176 | 177 | if (fs.statSync(filePath).isDirectory()) { 178 | count += getFileCount(filePath, extensions, excludePathsOrFiles); 179 | } else if ( 180 | extensions.some((extension) => file.endsWith(extension)) && 181 | !isExcluded 182 | ) { 183 | count++; 184 | } 185 | }); 186 | return count; 187 | } 188 | 189 | function extractClassNames(obj) { 190 | const classNames = new Set(); 191 | function traverse(node) { 192 | if (node.type === "ClassName") { 193 | classNames.add(node.name); 194 | } 195 | for (const key of Object.keys(node)) { 196 | const value = node[key]; 197 | if (typeof value === "object" && value !== null) { 198 | if (Array.isArray(value)) { 199 | value.forEach(traverse); 200 | } else { 201 | traverse(value); 202 | } 203 | } 204 | } 205 | } 206 | 207 | traverse(obj); 208 | return classNames; 209 | } 210 | 211 | function escapeClassName(className) { 212 | // CSS escapes for some special characters 213 | const escapes = { 214 | '!': '\\!', 215 | '"': '\\"', 216 | '#': '\\#', 217 | '$': '\\$', 218 | '%': '\\%', 219 | '&': '\\&', 220 | '\'': '\\\'', 221 | '(': '\\(', 222 | ')': '\\)', 223 | '*': '\\*', 224 | '+': '\\+', 225 | ',': '\\,', 226 | '.': '\\.', 227 | '/': '\\/', 228 | ':': '\\:', 229 | ';': '\\;', 230 | '<': '\\<', 231 | '=': '\\=', 232 | '>': '\\>', 233 | '?': '\\?', 234 | '@': '\\@', 235 | '[': '\\[', 236 | '\\': '\\\\', 237 | ']': '\\]', 238 | '^': '\\^', 239 | '`': '\\`', 240 | '{': '\\{', 241 | '|': '\\|', 242 | '}': '\\}', 243 | '~': '\\~', 244 | ' ': '\\ ', 245 | }; 246 | 247 | // Special handling for class names starting with a digit 248 | if (/^\d/.test(className)) { 249 | // Convert the first digit to its hexadecimal escape code 250 | const firstCharCode = className.charCodeAt(0).toString(16); 251 | const rest = className.slice(1); 252 | 253 | // Use the hexadecimal escape for the first character, followed by the rest of the class name 254 | // Note: A trailing space is added after the escape sequence to ensure separation 255 | return `\\${firstCharCode}${rest.split('').map(char => escapes[char] || char).join('')}`; 256 | } 257 | // Replace each special character with its escaped version for the rest of the class name 258 | return className.split('').map(char => escapes[char] || char).join(''); 259 | } 260 | 261 | function octalizeClassName(className) { 262 | // Escape the first character if it's a digit or a special character 263 | let firstCharEscaped = ''; 264 | if (/[\d>]/.test(className.charAt(0))) { 265 | const firstChar = className.charCodeAt(0).toString(16).toLowerCase(); 266 | firstCharEscaped = `\\${firstChar}`; 267 | } 268 | 269 | // Escape other special characters in the rest of the className 270 | const restEscaped = className.slice(firstCharEscaped ? 1 : 0) 271 | .split('') 272 | .map(char => { 273 | if (/[!\"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/.test(char)) { 274 | // Directly escape special characters 275 | return `\\${char}`; 276 | } 277 | return char; 278 | }) 279 | .join(''); 280 | 281 | return firstCharEscaped + restEscaped; 282 | } 283 | 284 | function getClassNames(selectorStr) { 285 | // https://github.com/mdevils/css-selector-parser/issues/40 286 | // Avoid keyframe selectors: @keyframes, from, to, 0%, 100%. 50% 287 | const keyframeOrAtRuleRegex = /^(?:@|\d+|from|to)\b/; 288 | if (keyframeOrAtRuleRegex.test(selectorStr)) { 289 | return new Set(); // Return an empty set for ignored cases 290 | } 291 | 292 | // https://github.com/mdevils/css-selector-parser/issues/41 293 | // Remove '&' used for nesting in CSS, if present 294 | selectorStr = selectorStr.replace(/(^|\s+)&/g, ''); 295 | 296 | const parse = createParser({syntax: 'progressive'}); 297 | const ast = parse(selectorStr); 298 | return extractClassNames(ast); 299 | } 300 | 301 | function getIdNames(selectorStr) { 302 | let ids = selectorStr.replace(".#", " ").replace(".", " ").trim().split(" "); 303 | ids = ids.filter((id) => id.charAt(0) == "#"); 304 | return ids; 305 | } 306 | 307 | function logger(type, issuer, task, data) { 308 | const mainColor = "\x1b[38;2;99;102;241m%s\x1b[0m"; 309 | switch (type) { 310 | case "info": 311 | console.info(mainColor, issuer, "\x1b[36m", task, data, "\x1b[0m"); 312 | break; 313 | case "warn": 314 | console.warn(mainColor, issuer, "\x1b[33m", task, data, "\x1b[0m"); 315 | break; 316 | case "error": 317 | console.error(mainColor, issuer, "\x1b[31m", task, data, "\x1b[0m"); 318 | break; 319 | case "success": 320 | console.log(mainColor, issuer, "\x1b[32m", task, data, "\x1b[0m"); 321 | break; 322 | default: 323 | console.log("'\x1b[0m'", issuer, task, data, "\x1b[0m"); 324 | break; 325 | } 326 | } 327 | 328 | function getRelativePath(absolutePath) { 329 | const currentDirectory = process.cwd(); 330 | const relativePath = path.relative(currentDirectory, absolutePath); 331 | return relativePath; 332 | } 333 | 334 | function isFileOrInDirectory(paths, filePath) { 335 | const resolvedFilePath = filePath.replace(/\\/g, "/"); // Replace backslashes with forward slashes 336 | 337 | for (const currentPath of paths) { 338 | const resolvedCurrentPath = currentPath.replace(/\\/g, "/"); // Replace backslashes with forward slashes 339 | 340 | if (resolvedCurrentPath === resolvedFilePath) { 341 | // The path is one of the items in the array, so it's a file or directory 342 | return true; 343 | } 344 | 345 | if ( 346 | resolvedCurrentPath.endsWith("/") && 347 | resolvedFilePath.startsWith(resolvedCurrentPath) 348 | ) { 349 | // The current path is a directory, so check whether the file is inside it 350 | const relativeFilePath = resolvedFilePath.substr( 351 | resolvedCurrentPath.length 352 | ); 353 | 354 | if (!relativeFilePath.startsWith("/") && relativeFilePath !== "") { 355 | // The file is inside the directory 356 | return true; 357 | } 358 | } 359 | } 360 | 361 | return false; 362 | } 363 | 364 | function escapeRegExp(string) { 365 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string 366 | } 367 | 368 | module.exports = { 369 | getRandomName, 370 | simplifyString, 371 | writeJsonToFile, 372 | copyDirectory, 373 | replaceJsonKeysInFiles, 374 | getFileCount, 375 | getClassNames, 376 | getIdNames, 377 | logger, 378 | getRelativePath, 379 | isFileOrInDirectory, 380 | escapeClassName, 381 | octalizeClassName, 382 | }; 383 | --------------------------------------------------------------------------------