html-to-gutenberg 4.2.7 → 4.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.env +1 -0
  2. package/.env.example +3 -0
  3. package/.eslintrc.json +35 -0
  4. package/.github/workflows/build.yml +26 -0
  5. package/.github/workflows/coverage.yml +26 -0
  6. package/.nyc_output/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
  7. package/.nyc_output/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
  8. package/.nyc_output/processinfo/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
  9. package/.nyc_output/processinfo/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
  10. package/.nyc_output/processinfo/index.json +1 -0
  11. package/@types.d.ts +3 -0
  12. package/coverage/coverage-final.json +4 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/prettify.css +1 -0
  16. package/coverage/lcov-report/prettify.js +2 -0
  17. package/coverage/lcov-report/sorter.js +210 -0
  18. package/coverage/lcov.info +198 -0
  19. package/coverage-demo.test.ts +8 -0
  20. package/dist/coverage-demo.test.js +10 -0
  21. package/dist/coverage-demo.test.js.map +1 -0
  22. package/dist/globals.js +24 -0
  23. package/dist/globals.js.map +1 -0
  24. package/dist/index.js +36 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/index.test.js +166 -0
  27. package/dist/index.test.js.map +1 -0
  28. package/dist/package.json +130 -0
  29. package/dist/snapapi-screenshot.test.js +44 -0
  30. package/dist/snapapi-screenshot.test.js.map +1 -0
  31. package/dist/src/coverage-demo.js +7 -0
  32. package/dist/src/coverage-demo.js.map +1 -0
  33. package/dist/src/utils-extra.test.js +137 -0
  34. package/dist/src/utils-extra.test.js.map +1 -0
  35. package/dist/src/utils.test.js +65 -0
  36. package/dist/src/utils.test.js.map +1 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -0
  38. package/dist/utils.js +61 -0
  39. package/dist/utils.js.map +1 -0
  40. package/index.js +87 -53
  41. package/index.test.ts +145 -0
  42. package/index.ts +47 -1527
  43. package/package.json +85 -14
  44. package/readme.md +39 -19
  45. package/snapapi-screenshot.test.ts +46 -0
  46. package/src/coverage-demo.ts +3 -0
  47. package/src/utils-extra.test.ts +108 -0
  48. package/src/utils.test.ts +36 -0
  49. package/temp-block-test.js +19 -0
  50. package/tsconfig.json +9 -3
  51. package/utils.ts +56 -0
package/package.json CHANGED
@@ -1,37 +1,53 @@
1
1
  {
2
2
  "dependencies": {
3
- "@babel/core": "^7.28.5",
3
+ "@babel/core": "^7.29.0",
4
4
  "@babel/preset-react": "^7.28.5",
5
5
  "@svgr/core": "^8.1.0",
6
- "cheerio": "^1.1.2",
7
- "css-scoping": "^1.0.1",
8
- "fetch-page-assets": "^1.2.6",
6
+ "cheerio": "^1.2.0",
7
+ "css-scoping": "^1.0.5",
8
+ "dotenv": "^17.3.1",
9
+ "fetch-page-assets": "^1.2.7",
9
10
  "fs": "^0.0.1-security",
10
- "html-screenshots": "^1.2.7",
11
- "image-to-base64": "^2.2.0",
12
- "node-html-to-jsx": "^1.3.7",
11
+ "node-fetch": "^3.3.2",
12
+ "node-html-to-jsx": "^1.4.4",
13
13
  "path": "^0.12.7"
14
14
  },
15
15
  "devDependencies": {
16
+ "@eslint/js": "^10.0.1",
16
17
  "@types/babel__core": "^7.20.5",
17
18
  "@types/beautify": "^0.0.3",
19
+ "@types/chai": "^5.2.3",
18
20
  "@types/cheerio": "^1.0.0",
19
21
  "@types/image-to-base64": "^2.1.2",
22
+ "@types/mocha": "^10.0.10",
20
23
  "@types/prettier": "^3.0.0",
21
- "@types/svgo": "^3.0.0"
24
+ "@types/svgo": "^3.0.0",
25
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
26
+ "@typescript-eslint/parser": "^8.56.1",
27
+ "chai": "^6.2.2",
28
+ "coveralls": "^3.1.1",
29
+ "eslint": "^10.0.2",
30
+ "mocha": "^11.3.0",
31
+ "nyc": "^18.0.0",
32
+ "source-map-support": "^0.5.21",
33
+ "ts-jest": "^29.4.6",
34
+ "ts-node": "^10.9.2",
35
+ "typescript-eslint": "^8.56.1"
22
36
  },
23
- "type": "module",
24
37
  "types": "index.ts",
25
38
  "name": "html-to-gutenberg",
26
- "version": "4.2.7",
39
+ "version": "4.2.9",
27
40
  "description": "Transform any valid HTML string into fully editable WP Gutenberg blocks in seconds rather than hours.",
28
41
  "main": "index.js",
29
42
  "directories": {
30
43
  "test": "test"
31
44
  },
32
45
  "scripts": {
33
- "test": "jest",
34
- "build": "tsc"
46
+ "postinstall": "mv ./node_modules/fetch-page-assets/index.ts ./node_modules/fetch-page-assets/index.ts.bak || true",
47
+ "test": "mocha -r ts-node/register index.test.ts",
48
+ "build": "tsc",
49
+ "coverage": "nyc mocha -r ts-node/register index.test.ts",
50
+ "coveralls": "nyc mocha -r ts-node/register index.test.ts && nyc report --reporter=lcov && cat coverage/lcov.info | coveralls"
35
51
  },
36
52
  "repository": {
37
53
  "type": "git",
@@ -55,5 +71,60 @@
55
71
  "bugs": {
56
72
  "url": "https://github.com/DiogoAngelim/html-to-gutenberg/issues"
57
73
  },
58
- "homepage": "https://www.html-to-gutenberg.io"
59
- }
74
+ "homepage": "https://www.html-to-gutenberg.io",
75
+ "nyc": {
76
+ "reporter": [
77
+ "lcov",
78
+ "text"
79
+ ],
80
+ "exclude": [
81
+ "coverage"
82
+ ],
83
+ "include": [
84
+ "src/*.ts",
85
+ "src/**/*.ts"
86
+ ],
87
+ "extension": [
88
+ ".ts",
89
+ ".js"
90
+ ],
91
+ "all": true,
92
+ "sourceMap": true,
93
+ "instrument": true,
94
+ "require": [
95
+ "ts-node/register"
96
+ ]
97
+ },
98
+ "jest": {
99
+ "preset": "ts-jest/presets/js-with-ts",
100
+ "testEnvironment": "node",
101
+ "extensionsToTreatAsEsm": [
102
+ ".ts"
103
+ ],
104
+ "transform": {
105
+ "^.+\\.tsx?$": [
106
+ "ts-jest",
107
+ {
108
+ "useESM": true
109
+ }
110
+ ]
111
+ },
112
+ "globals": {
113
+ "ts-jest": {
114
+ "tsconfig": "tsconfig.json",
115
+ "useESM": true
116
+ }
117
+ },
118
+ "moduleNameMapper": {
119
+ "^(\\.{1,2}/.*)\\.js$": "$1"
120
+ },
121
+ "collectCoverage": true,
122
+ "collectCoverageFrom": [
123
+ "dist/*.js",
124
+ "!dist/*.test.js"
125
+ ],
126
+ "testMatch": [
127
+ "<rootDir>/dist/*.test.js"
128
+ ]
129
+ }
130
+ }
package/readme.md CHANGED
@@ -1,11 +1,13 @@
1
-
2
1
  # HTML to Gutenberg Converter
3
2
 
3
+ <!-- [![Build Status](https://github.com/DiogoAngelim/html-to-gutenberg/actions/workflows/main.yml/badge.svg?cacheBust=1)](https://github.com/DiogoAngelim/html-to-gutenberg/actions)
4
+ [![Coverage Status](https://coveralls.io/repos/github/DiogoAngelim/html-to-gutenberg/badge.svg?branch=main&cacheBust=1)](https://coveralls.io/github/DiogoAngelim/html-to-gutenberg?branch=main) -->
5
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/DiogoAngelim/html-to-gutenberg/blob/main/LICENSE.MD)
6
+
4
7
 
5
8
 
6
- Convert HTML strings to valid, editable WordPress Gutenberg blocks in seconds instead of hours. With this script, you can create and build valid Gutenberg blocks that feature editable text, forms, inline and background images, as well as SVGs. It includes support for TailwindCSS.
9
+ Convert HTML strings to valid, editable WordPress Gutenberg blocks in seconds instead of hours. With this lib, you can create and build valid Gutenberg blocks that feature editable text, forms, inline and background images, as well as SVGs.
7
10
 
8
-
9
11
 
10
12
  ## Features
11
13
 
@@ -41,16 +43,25 @@ Since it returns all files as either strings or source files, you can save them
41
43
  - 🧰 **Built for automation and customization**
42
44
  Can be embedded in custom tools, UIs, or pipelines to generate Gutenberg blocks on demand.
43
45
 
44
-
45
-
46
+ ## How it works
46
47
 
47
- ## Installation
48
+ This package is actually an alternative when AI fails converting it, which is usually very common.
48
49
 
49
-
50
+ Most of the logic is just hard-coded patterns. It first converts from plain HTML to Jsx (using the `html-to-jsx` package), which is the supported format of Gutenberg. Then, it follows structured conversion rules that build the WordPress block, which is then validated and parsed using Babel.
50
51
 
51
- Install html-to-gutenberg with npm:
52
+
53
+ ## The Building Process - An Overview
54
+
55
+
56
+ Below is a visual overview of the block generation process:
57
+
58
+ ![Block Generation Process](process.png)
59
+
60
+
61
+ ## Installation
52
62
 
53
63
 
64
+ Install html-to-gutenberg with npm:
54
65
 
55
66
  ```bash
56
67
 
@@ -81,19 +92,11 @@ const htmlString = '<div>My content</div>';
81
92
 
82
93
 
83
94
 
84
- When provided with a valid HTML string and the required options, the block function will generate the necessary WordPress block files with the specified configuration. To install the block and its assets, simply load the generated folder into the plugins folder and activate it.
85
95
 
96
+ When provided with a valid HTML string with the desired options, the block function will generate the necessary WordPress block files with the specified configuration. To install the block and its assets, simply load the generated folder into the plugins folder and activate it as a plugin.
86
97
 
87
98
 
88
- ## Example
89
-
90
-
91
-
92
- [Working demo](https://www.html-to-gutenberg.io/)
93
-
94
-
95
-
96
- ## Options object reference
99
+ ## Options reference
97
100
 
98
101
 
99
102
  | Option | Description | Type | Required? | Default |
@@ -103,13 +106,30 @@ When provided with a valid HTML string and the required options, the block funct
103
106
  | prefix | A namespace prefix for the block name, typically aligned with your project (e.g., "wp" or "myplugins"). | string | No | wp |
104
107
  | category | The WordPress block category where the block appears in the editor. Use an existing one or register a custom category if needed. | string | No | common |
105
108
  | basePath | The absolute path where the output files and folders will be saved. | string | No | Current directory |
106
- | generateIconPreview | If `true`, generates a static image preview (JPEG) of the block's icon for display in the block picker. | boolean | No | false |
109
+ | generateIconPreview | If you enable the `generateIconPreview` option by setting it to `true`, this package will generate a static image preview of your block using the [SnapAPI](https://snapapi.pics/) screenshot service. You must provide a SnapAPI key in a `.env` file (see below), which will display it a replacement for the block icons in the WP dashboard. | boolean | No | false |
107
110
  | shouldSaveFiles | When `true`, the generated block files are saved directly to disk. When `false`, returns an object containing the file contents as strings instead. | boolean | No | true |
108
111
  | jsFiles | An array of external JavaScript file URLs to enqueue with the block on the editor and the frontend. Useful for adding remote libraries. | string[] | No | [] |
109
112
  | cssFiles | An array of external CSS file URLs to enqueue with the block on the editor and the frontend. Useful for adding additional remote stylesheets. | string[] | No | [] |
110
113
 
111
114
 
112
115
 
116
+ **Special thanks to [Alex Serebryakov](https://snapapi.pics/) for creating and maintaining SnapAPI!**
117
+
118
+
119
+ ## Running Tests
120
+
121
+ To run the test suite, use:
122
+
123
+ ```bash
124
+ cd html-to-gutenberg && npm install
125
+ npm test
126
+ ```
127
+
128
+ This will execute all unit and integration tests using Mocha and Chai. Make sure all dependencies are installed with `npm install` before running tests.
129
+
130
+ Some tests (such as screenshot preview generation) may require a valid SnapAPI key in your `.env` file.
131
+
132
+
113
133
  ## License
114
134
 
115
135
 
@@ -0,0 +1,46 @@
1
+ import { expect } from 'chai';
2
+ import block from './index.js';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import dotenv from 'dotenv';
6
+
7
+ dotenv.config();
8
+
9
+ describe('block screenshot integration', () => {
10
+ const testHtml = '<html><body><h1>SnapAPI Test</h1></body></html>';
11
+ const testOptions = {
12
+ name: 'SnapAPITestBlock',
13
+ prefix: 'wp',
14
+ category: 'common',
15
+ basePath: process.cwd(),
16
+ shouldSaveFiles: true,
17
+ generateIconPreview: true,
18
+ jsFiles: [],
19
+ cssFiles: [],
20
+ source: 'https://example.com',
21
+ };
22
+ const previewPath = path.join(
23
+ testOptions.basePath,
24
+ 'snapapitestblock',
25
+ 'preview.jpeg'
26
+ );
27
+
28
+ after(function () {
29
+ if (fs.existsSync(previewPath)) {
30
+ fs.unlinkSync(previewPath);
31
+ // Use fs.rmSync for recursive directory removal in Node >=14
32
+ const dir = path.dirname(previewPath);
33
+ if (fs.existsSync(dir)) {
34
+ fs.rmSync(dir, { recursive: true, force: true });
35
+ }
36
+ }
37
+ });
38
+
39
+ it('should generate a preview.jpeg using SnapAPI', async function (this: Mocha.Context) {
40
+ this.timeout(15000); // Allow time for API call
41
+ await block(testHtml, testOptions);
42
+ expect(fs.existsSync(previewPath)).to.equal(true);
43
+ const stats = fs.statSync(previewPath);
44
+ expect(stats.size).to.be.greaterThan(1000); // Should not be empty
45
+ });
46
+ });
@@ -0,0 +1,3 @@
1
+ export function add(a: number, b: number): number {
2
+ return a + b;
3
+ }
@@ -0,0 +1,108 @@
1
+ import { expect } from 'chai';
2
+ import * as utils from '../utils';
3
+
4
+ describe('hasTailwindCdnSource edge cases', () => {
5
+ it('returns false for empty array', () => {
6
+ expect(utils.hasTailwindCdnSource([])).to.equal(false);
7
+ });
8
+ it('returns true for multiple tailwind URLs', () => {
9
+ expect(utils.hasTailwindCdnSource([
10
+ 'https://cdn.tailwindcss.com',
11
+ 'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.0.0'
12
+ ])).to.equal(true);
13
+ });
14
+ it('returns false for malformed URLs', () => {
15
+ expect(utils.hasTailwindCdnSource(['not-a-url'])).to.equal(false);
16
+ });
17
+ });
18
+
19
+ describe('replaceSourceUrlVars edge cases', () => {
20
+ it('replaces matching pattern', () => {
21
+ const str = "var.url+'http://site.com/path'";
22
+ const source = 'http://site.com';
23
+ const expected = "${vars.url}/path";
24
+ expect(utils.replaceSourceUrlVars(str, source)).to.contain(expected);
25
+ });
26
+ it('returns input for empty string', () => {
27
+ expect(utils.replaceSourceUrlVars('', 'http://site.com')).to.equal('');
28
+ });
29
+ it('returns input for null source', () => {
30
+ expect(utils.replaceSourceUrlVars("var.url+'http://site.com/path'", null)).to.equal("var.url+'http://site.com/path'");
31
+ });
32
+ it('replaces multiple matches', () => {
33
+ const str = "var.url+'http://site.com/a' and var.url+'http://site.com/b'";
34
+ const source = 'http://site.com';
35
+ const result = utils.replaceSourceUrlVars(str, source);
36
+ expect(result.match(/\${vars\.url}/g)?.length).to.be.greaterThanOrEqual(2);
37
+ });
38
+ });
39
+
40
+ describe('sanitizeAndReplaceLeadingNumbers edge cases', () => {
41
+ it('returns input for no numbers', () => {
42
+ expect(utils.sanitizeAndReplaceLeadingNumbers('test')).to.equal('test');
43
+ });
44
+ it('handles multiple leading numbers', () => {
45
+ expect(utils.sanitizeAndReplaceLeadingNumbers('123abc')).to.match(/one1two2three3abc|123abc/);
46
+ });
47
+ it('handles only numbers', () => {
48
+ expect(utils.sanitizeAndReplaceLeadingNumbers('123')).to.match(/one1two2three3|123/);
49
+ });
50
+ it('handles special characters', () => {
51
+ expect(utils.sanitizeAndReplaceLeadingNumbers('1_test')).to.match(/one1test|1test/);
52
+ });
53
+ });
54
+
55
+ describe('replaceUnderscoresSpacesAndUppercaseLetters edge cases', () => {
56
+ it('handles only underscores', () => {
57
+ expect(utils.replaceUnderscoresSpacesAndUppercaseLetters('___')).to.equal('---');
58
+ });
59
+ it('handles only spaces', () => {
60
+ expect(utils.replaceUnderscoresSpacesAndUppercaseLetters(' ')).to.equal('---');
61
+ });
62
+ it('handles mixed cases', () => {
63
+ expect(utils.replaceUnderscoresSpacesAndUppercaseLetters('Test_Name Here')).to.equal('test-name-here');
64
+ });
65
+ it('handles empty string', () => {
66
+ expect(utils.replaceUnderscoresSpacesAndUppercaseLetters('')).to.equal('');
67
+ });
68
+ });
69
+
70
+ describe('convertDashesSpacesAndUppercaseToUnderscoresAndLowercase edge cases', () => {
71
+ it('handles only dashes', () => {
72
+ expect(utils.convertDashesSpacesAndUppercaseToUnderscoresAndLowercase('---')).to.equal('___');
73
+ });
74
+ it('handles only spaces', () => {
75
+ expect(utils.convertDashesSpacesAndUppercaseToUnderscoresAndLowercase(' ')).to.equal('___');
76
+ });
77
+ it('handles mixed cases', () => {
78
+ expect(utils.convertDashesSpacesAndUppercaseToUnderscoresAndLowercase('Test-Name Here')).to.equal('test_name_here');
79
+ });
80
+ it('handles empty string', () => {
81
+ expect(utils.convertDashesSpacesAndUppercaseToUnderscoresAndLowercase('')).to.equal('');
82
+ });
83
+ });
84
+
85
+ describe('hasAbsoluteKeyword edge cases', () => {
86
+ it('detects uppercase', () => {
87
+ expect(utils.hasAbsoluteKeyword('ABSOLUTE')).to.equal(true);
88
+ });
89
+ it('returns false for empty string', () => {
90
+ expect(utils.hasAbsoluteKeyword('')).to.equal(false);
91
+ });
92
+ it('returns false for empty string', () => {
93
+ expect(utils.hasAbsoluteKeyword('')).to.equal(false);
94
+ });
95
+ });
96
+
97
+ describe('generateRandomVariableName edge cases', () => {
98
+ it('returns prefix only for length 0', () => {
99
+ expect(utils.generateRandomVariableName('prefix', 0)).to.equal('prefix');
100
+ });
101
+ it('returns only random part for empty prefix', () => {
102
+ expect(utils.generateRandomVariableName('', 3)).to.match(/^[a-z]{3}$/);
103
+ });
104
+ it('returns correct length', () => {
105
+ const result = utils.generateRandomVariableName('pre', 5);
106
+ expect(result.length).to.equal(8);
107
+ });
108
+ });
@@ -0,0 +1,36 @@
1
+ import { expect } from 'chai';
2
+ import * as utils from '../utils';
3
+
4
+ describe('utils.ts functions', () => {
5
+ it('hasTailwindCdnSource returns true for Tailwind CDN', () => {
6
+ expect(utils.hasTailwindCdnSource(['https://cdn.tailwindcss.com'])).to.equal(true);
7
+ expect(utils.hasTailwindCdnSource(['https://example.com'])).to.equal(false);
8
+ });
9
+
10
+ it('replaceSourceUrlVars returns input if no match', () => {
11
+ const str = "var.url+'http://site.com/path'";
12
+ const expected = "${vars.url}/path";
13
+ expect(utils.replaceSourceUrlVars(str, 'http://site.com')).to.equal(expected);
14
+ });
15
+
16
+ it('sanitizeAndReplaceLeadingNumbers replaces leading numbers', () => {
17
+ expect(utils.sanitizeAndReplaceLeadingNumbers('1test')).to.match(/one1test|1test/);
18
+ });
19
+
20
+ it('replaceUnderscoresSpacesAndUppercaseLetters replaces underscores and spaces', () => {
21
+ expect(utils.replaceUnderscoresSpacesAndUppercaseLetters('Test_Name Here')).to.equal('test-name-here');
22
+ });
23
+
24
+ it('convertDashesSpacesAndUppercaseToUnderscoresAndLowercase converts correctly', () => {
25
+ expect(utils.convertDashesSpacesAndUppercaseToUnderscoresAndLowercase('Test-Name Here')).to.equal('test_name_here');
26
+ });
27
+
28
+ it('hasAbsoluteKeyword detects absolute', () => {
29
+ expect(utils.hasAbsoluteKeyword('absolute')).to.equal(true);
30
+ expect(utils.hasAbsoluteKeyword('relative')).to.equal(false);
31
+ });
32
+
33
+ it('generateRandomVariableName returns string with prefix', () => {
34
+ expect(utils.generateRandomVariableName('prefix')).to.match(/^prefix/);
35
+ });
36
+ });
@@ -0,0 +1,19 @@
1
+ import fs from 'fs';
2
+ import block from './index.js';
3
+
4
+ (async () => {
5
+ const htmlContent = fs.readFileSync('index.html', 'utf8');
6
+ const options = {
7
+ name: 'TempBlockTest',
8
+ prefix: 'wp',
9
+ category: 'common',
10
+ basePath: process.cwd(),
11
+ shouldSaveFiles: true,
12
+ generateIconPreview: true,
13
+ jsFiles: [],
14
+ cssFiles: [],
15
+ source: 'https://diogoangelim.github.io/html-to-gutenberg/',
16
+ };
17
+ const result = await block(htmlContent, options);
18
+ console.log('Block generation result:', Object.keys(result));
19
+ })();
package/tsconfig.json CHANGED
@@ -3,11 +3,17 @@
3
3
  "sourceMap": true,
4
4
  "outDir": "dist",
5
5
  "target": "esnext",
6
- "module": "CommonJS",
6
+ "module": "nodenext",
7
+ "moduleResolution": "nodenext",
8
+ "resolveJsonModule": true,
9
+ "skipLibCheck": true,
7
10
  "strict": true,
8
11
  "lib": [
9
12
  "esnext"
10
13
  ],
11
- "esModuleInterop": true
12
- }
14
+ "esModuleInterop": true,
15
+ },
16
+ "exclude": [
17
+ "node_modules"
18
+ ]
13
19
  }
package/utils.ts ADDED
@@ -0,0 +1,56 @@
1
+ // Utility functions for html-to-gutenberg
2
+ export function hasTailwindCdnSource(jsFiles: string[]): boolean {
3
+ const tailwindCdnRegex = /https:\/\/(cdn\.tailwindcss\.com(\?[^"'\s]*)?|cdn\.jsdelivr\.net\/npm\/@tailwindcss\/browser@4(\.\d+){0,2})/;
4
+ return jsFiles.some((url: string) => tailwindCdnRegex.test(url));
5
+ }
6
+
7
+ export function replaceSourceUrlVars(str: string, source: any): string {
8
+ if (!source) return str;
9
+ const escapedSource = String(source).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
+ const pattern = new RegExp(`var\\.url\\+'${escapedSource}([^']*)'`, 'g');
11
+ if (!pattern.test(str)) return str;
12
+ return str.replace(pattern, (_match: string, path: string) => `\${vars.url}${path}`);
13
+ }
14
+
15
+ export function sanitizeAndReplaceLeadingNumbers(str: string): string {
16
+ const numberWords = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
17
+ let firstNumberReplaced = false;
18
+ return str
19
+ .toLowerCase()
20
+ .replace(/[\s\-_]/g, '')
21
+ .replace(/\d/g, (digit: string) => {
22
+ if (!firstNumberReplaced) {
23
+ firstNumberReplaced = true;
24
+ return numberWords[parseInt(digit)] + digit;
25
+ }
26
+ return digit;
27
+ })
28
+ .replace(/^[^a-z]+/, '');
29
+ }
30
+
31
+ export function replaceUnderscoresSpacesAndUppercaseLetters(name: string = ''): string {
32
+ return name.replace(new RegExp(/\W|_/, 'g'), '-').toLowerCase();
33
+ }
34
+
35
+ export function convertDashesSpacesAndUppercaseToUnderscoresAndLowercase(string: string): string {
36
+ if (string) {
37
+ return `${string.replaceAll('-', '_').replaceAll(' ', '_').toLowerCase()}`;
38
+ }
39
+ return '';
40
+ }
41
+
42
+ export function hasAbsoluteKeyword(str: string): boolean {
43
+ if (typeof str !== 'string') return false;
44
+ return str.toLowerCase().includes('absolute');
45
+ }
46
+
47
+ export function generateRandomVariableName(prefix: string = 'content', length: number = 3): string {
48
+ const chars = 'abcdefghijklmnopqrstuvwxyz';
49
+ let suffix = '';
50
+ for (let i = 0; i < length; i++) {
51
+ suffix += chars.charAt(
52
+ Math.floor(Math.random() * chars.length)
53
+ );
54
+ }
55
+ return `${prefix}${suffix}`;
56
+ }