tailwind-hyperclay 0.1.0 → 0.1.2

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 (4) hide show
  1. package/PLAN.md +86 -0
  2. package/index.js +39 -2
  3. package/package.json +6 -2
  4. package/test/test.js +49 -10
package/PLAN.md ADDED
@@ -0,0 +1,86 @@
1
+ # Plan: Integrate tailwind-hyperclay into ./hyperclay
2
+
3
+ ## Overview
4
+ When a user saves an HTML file that references `https://hyperclay.com/tailwindcss/{siteName}.css`, automatically generate the CSS file using Tailwind v4.
5
+
6
+ ## Steps
7
+
8
+ ### 1. Install dependency
9
+ ```bash
10
+ cd ./hyperclay
11
+ npm install tailwind-hyperclay
12
+ ```
13
+
14
+ ### 2. Add static route for generated CSS
15
+ In `hey.js`, add static serving for tailwind CSS files:
16
+ ```js
17
+ '/tailwindcss' → public-assets/tailwindcss
18
+ ```
19
+
20
+ ### 3. Create the directory
21
+ ```bash
22
+ mkdir -p ./hyperclay/public-assets/tailwindcss
23
+ ```
24
+
25
+ ### 4. Hook into saveHTML
26
+ In `server-lib/state-actions.js`, after HTML is saved (~line 703):
27
+
28
+ ```js
29
+ import { compileTailwind, hasTailwindLink } from 'tailwind-hyperclay';
30
+
31
+ // After successful save...
32
+ if (hasTailwindLink(formattedHtml, node.name)) {
33
+ const css = await compileTailwind(formattedHtml);
34
+ await dx('public-assets/tailwindcss').createFileOverwrite(`${node.name}.css`, css);
35
+ }
36
+ ```
37
+
38
+ ### 5. Hook into uploadSyncFile
39
+ In `server-lib/sync-actions.js`, after file is written (~line 311):
40
+
41
+ ```js
42
+ import { compileTailwind, hasTailwindLink } from 'tailwind-hyperclay';
43
+
44
+ // After file write...
45
+ if (hasTailwindLink(content, siteName)) {
46
+ const css = await compileTailwind(content);
47
+ await dx('public-assets/tailwindcss').createFileOverwrite(`${siteName}.css`, css);
48
+ }
49
+ ```
50
+
51
+ ### 6. Hook into restoreBackup
52
+ In `server-lib/state-actions.js`, after backup is restored (~line 909):
53
+
54
+ ```js
55
+ // After restore...
56
+ if (hasTailwindLink(html, siteName)) {
57
+ const css = await compileTailwind(html);
58
+ await dx('public-assets/tailwindcss').createFileOverwrite(`${siteName}.css`, css);
59
+ }
60
+ ```
61
+
62
+ ### 7. Hook into copySite
63
+ In `server-lib/state-actions.js`, modify the copy logic (~line 399):
64
+
65
+ ```js
66
+ import { compileTailwind, hasAnyTailwindLink, replaceTailwindLink } from 'tailwind-hyperclay';
67
+
68
+ // Before writing the file, update the tailwind link to use new name
69
+ let contentToWrite = siteContent;
70
+ if (hasAnyTailwindLink(siteContent)) {
71
+ contentToWrite = replaceTailwindLink(siteContent, newSiteName);
72
+ const css = await compileTailwind(contentToWrite);
73
+ await dx('public-assets/tailwindcss').createFileOverwrite(`${newSiteName}.css`, css);
74
+ }
75
+
76
+ await dx('sites').createFileOverwrite(`${newSiteName}.html`, contentToWrite);
77
+ ```
78
+
79
+ ## Files to modify
80
+ 1. `hyperclay/package.json` - add dependency
81
+ 2. `hyperclay/hey.js` - add static route (~line 185)
82
+ 3. `hyperclay/server-lib/state-actions.js` - hooks at:
83
+ - `saveHTML` (~line 703)
84
+ - `copySite` (~line 399)
85
+ - `restoreBackup` (~line 909)
86
+ 4. `hyperclay/server-lib/sync-actions.js` - add post-sync hook (~line 311)
package/index.js CHANGED
@@ -80,7 +80,44 @@ export function extractCandidates(html) {
80
80
  return Array.from(candidates);
81
81
  }
82
82
 
83
+ function parseTailwindUrl(urlString) {
84
+ try {
85
+ const url = new URL(urlString);
86
+ if (url.host === 'hyperclay.com' &&
87
+ url.pathname.startsWith('/tailwindcss/') &&
88
+ url.pathname.endsWith('.css')) {
89
+ return url.pathname.slice(13, -4); // extract app name between /tailwindcss/ and .css
90
+ }
91
+ } catch {}
92
+ return null;
93
+ }
94
+
95
+ function extractHrefs(html) {
96
+ const hrefs = [];
97
+ const regex = /href\s*=\s*["']([^"']+)["']/gi;
98
+ let match;
99
+ while ((match = regex.exec(html)) !== null) {
100
+ hrefs.push(match[1]);
101
+ }
102
+ return hrefs;
103
+ }
104
+
83
105
  export function hasTailwindLink(html, appName) {
84
- const pattern = new RegExp(`https://hyperclay\\.com/tailwindcss/${appName}\\.css`);
85
- return pattern.test(html);
106
+ return extractHrefs(html).some(url => parseTailwindUrl(url) === appName);
107
+ }
108
+
109
+ export function hasAnyTailwindLink(html) {
110
+ return extractHrefs(html).some(url => parseTailwindUrl(url) !== null);
111
+ }
112
+
113
+ export function replaceTailwindLink(html, newName) {
114
+ return html.replace(
115
+ /(href\s*=\s*["'])([^"']+)(["'])/gi,
116
+ (match, prefix, url, suffix) => {
117
+ if (parseTailwindUrl(url) !== null) {
118
+ return `${prefix}https://hyperclay.com/tailwindcss/${newName}.css${suffix}`;
119
+ }
120
+ return match;
121
+ }
122
+ );
86
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwind-hyperclay",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "On-save Tailwind CSS generator for Hyperclay",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,7 +11,11 @@
11
11
  "test": "node test/test.js",
12
12
  "release": "./scripts/release.sh"
13
13
  },
14
- "keywords": ["tailwind", "hyperclay", "css"],
14
+ "keywords": [
15
+ "tailwind",
16
+ "hyperclay",
17
+ "css"
18
+ ],
15
19
  "author": "",
16
20
  "license": "MIT",
17
21
  "dependencies": {
package/test/test.js CHANGED
@@ -1,4 +1,4 @@
1
- import { compileTailwind, hasTailwindLink } from '../index.js';
1
+ import { compileTailwind, hasTailwindLink, hasAnyTailwindLink, replaceTailwindLink } from '../index.js';
2
2
 
3
3
  const testHTML = `
4
4
  <!DOCTYPE html>
@@ -15,20 +15,59 @@ const testHTML = `
15
15
  </html>
16
16
  `;
17
17
 
18
+ const testHTMLWithQueryParams = `
19
+ <!DOCTYPE html>
20
+ <html>
21
+ <head>
22
+ <link href="https://hyperclay.com/tailwindcss/myApp.css?v=123&t=456" rel="stylesheet">
23
+ </head>
24
+ <body></body>
25
+ </html>
26
+ `;
27
+
28
+ const testHTMLWithFragment = `
29
+ <!DOCTYPE html>
30
+ <html>
31
+ <head>
32
+ <link href="https://hyperclay.com/tailwindcss/myApp.css#section" rel="stylesheet">
33
+ </head>
34
+ <body></body>
35
+ </html>
36
+ `;
37
+
38
+ function test(name, actual, expected) {
39
+ const pass = actual === expected;
40
+ console.log(` ${pass ? '✓' : '✗'} ${name}: ${actual} (expected ${expected})`);
41
+ if (!pass) process.exitCode = 1;
42
+ }
43
+
18
44
  async function runTests() {
19
45
  console.log('Testing hasTailwindLink...');
20
- console.log(' myApp:', hasTailwindLink(testHTML, 'myApp')); // true
21
- console.log(' other:', hasTailwindLink(testHTML, 'other')); // false
46
+ test('myApp found', hasTailwindLink(testHTML, 'myApp'), true);
47
+ test('other not found', hasTailwindLink(testHTML, 'other'), false);
48
+ test('myApp with query params', hasTailwindLink(testHTMLWithQueryParams, 'myApp'), true);
49
+ test('myApp with fragment', hasTailwindLink(testHTMLWithFragment, 'myApp'), true);
50
+
51
+ console.log('\nTesting hasAnyTailwindLink...');
52
+ test('finds link', hasAnyTailwindLink(testHTML), true);
53
+ test('finds link with query params', hasAnyTailwindLink(testHTMLWithQueryParams), true);
54
+ test('no link', hasAnyTailwindLink('<html><body></body></html>'), false);
55
+
56
+ console.log('\nTesting replaceTailwindLink...');
57
+ const replaced = replaceTailwindLink(testHTML, 'newApp');
58
+ test('replaces app name', replaced.includes('https://hyperclay.com/tailwindcss/newApp.css'), true);
59
+ test('removes old app name', !replaced.includes('myApp'), true);
60
+
61
+ const replacedWithParams = replaceTailwindLink(testHTMLWithQueryParams, 'newApp');
62
+ test('replaces and strips query params', replacedWithParams.includes('https://hyperclay.com/tailwindcss/newApp.css"'), true);
63
+ test('query params removed', !replacedWithParams.includes('?v=123'), true);
22
64
 
23
65
  console.log('\nTesting compileTailwind...');
24
66
  const css = await compileTailwind(testHTML);
25
- console.log(' Generated CSS length:', css.length);
26
- console.log(' Contains .prose:', css.includes('.prose'));
27
- console.log(' Contains .form-input:', css.includes('.form-input'));
28
- console.log(' Contains .text-2xl:', css.includes('.text-2xl'));
29
-
30
- console.log('\nSample output (first 500 chars):');
31
- console.log(css.slice(0, 500));
67
+ test('generates CSS', css.length > 0, true);
68
+ test('contains .prose', css.includes('.prose'), true);
69
+ test('contains .form-input', css.includes('.form-input'), true);
70
+ test('contains .text-2xl', css.includes('.text-2xl'), true);
32
71
  }
33
72
 
34
73
  runTests().catch(console.error);