opentwig 1.1.0 → 1.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 (56) hide show
  1. package/AGENTS.md +49 -200
  2. package/package.json +2 -3
  3. package/src/live-ui/editor.js +1 -0
  4. package/src/live-ui/index.html +94 -0
  5. package/src/live-ui/sidebar.js +2 -0
  6. package/src/live-ui/styles.css +578 -0
  7. package/src/utils/favicon.js +20 -0
  8. package/src/utils/loadConfig.js +3 -1
  9. package/src/utils/setupWatcher.js +2 -2
  10. package/src/utils/startLiveServer.js +11 -8
  11. package/theme/default/index.js +2 -0
  12. package/vitest.config.js +19 -10
  13. package/website/README.md +42 -0
  14. package/website/components.json +16 -0
  15. package/website/eslint.config.js +36 -0
  16. package/website/package-lock.json +4136 -0
  17. package/website/package.json +41 -0
  18. package/website/shadcn-svelte.md +118 -0
  19. package/website/src/app.d.ts +13 -0
  20. package/website/src/app.html +11 -0
  21. package/website/src/lib/assets/favicon.svg +4 -0
  22. package/website/src/lib/components/ui/badge/badge.svelte +50 -0
  23. package/website/src/lib/components/ui/badge/index.ts +2 -0
  24. package/website/src/lib/components/ui/button/button.svelte +82 -0
  25. package/website/src/lib/components/ui/button/index.ts +17 -0
  26. package/website/src/lib/components/ui/card/card-action.svelte +20 -0
  27. package/website/src/lib/components/ui/card/card-content.svelte +15 -0
  28. package/website/src/lib/components/ui/card/card-description.svelte +20 -0
  29. package/website/src/lib/components/ui/card/card-footer.svelte +20 -0
  30. package/website/src/lib/components/ui/card/card-header.svelte +23 -0
  31. package/website/src/lib/components/ui/card/card-title.svelte +20 -0
  32. package/website/src/lib/components/ui/card/card.svelte +23 -0
  33. package/website/src/lib/components/ui/card/index.ts +25 -0
  34. package/website/src/lib/components/ui/separator/index.ts +7 -0
  35. package/website/src/lib/components/ui/separator/separator.svelte +21 -0
  36. package/website/src/lib/components/ui/tooltip/index.ts +19 -0
  37. package/website/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  38. package/website/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  39. package/website/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  40. package/website/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  41. package/website/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  42. package/website/src/lib/index.ts +1 -0
  43. package/website/src/lib/utils.ts +13 -0
  44. package/website/src/routes/+layout.svelte +23 -0
  45. package/website/src/routes/+page.server.ts +82 -0
  46. package/website/src/routes/+page.svelte +892 -0
  47. package/website/src/routes/layout.css +199 -0
  48. package/website/static/live-editor.png +0 -0
  49. package/website/static/robots.txt +3 -0
  50. package/website/static/theme-colorful.png +0 -0
  51. package/website/static/theme-dark.png +0 -0
  52. package/website/static/theme-default.png +0 -0
  53. package/website/static/theme-minimal.png +0 -0
  54. package/website/svelte.config.js +31 -0
  55. package/website/vite.config.ts +5 -0
  56. package/test-og.js +0 -40
package/AGENTS.md CHANGED
@@ -7,69 +7,67 @@ This file provides guidelines for agentic coding assistants working on the OpenT
7
7
  ```bash
8
8
  # Run the CLI tool
9
9
  npm start
10
- # or: node src/index.js
11
10
 
12
- # CLI options
13
- npm start -- --help # Show help information
14
- npm start -- --init # Create sample config.json
15
- npm start -- --validate-config # Validate config.json
16
- npm start -- --live # Start live preview with config editor
11
+ # Run a single test file
12
+ npm test -- src/utils/parseArgs.test.js
13
+
14
+ # Run tests matching a pattern
15
+ npm test -- -t "should validate"
17
16
 
18
- # Test Open Graph image generation
19
- npm run test-og
17
+ # Run tests once (CI mode)
18
+ npm run test:run
20
19
 
21
- # Run automated tests (Vitest)
22
- npm test # Run tests in watch mode
23
- npm run test:run # Run tests once
24
- npm run test:coverage # Run tests with coverage report
20
+ # Run tests with coverage
21
+ npm run test:coverage
25
22
 
26
23
  # Start live preview mode
27
24
  npm run live
25
+
26
+ # CLI options
27
+ npm start -- --help
28
+ npm start -- --init
28
29
  ```
29
30
 
30
31
  ## Project Overview
31
32
 
32
- OpenTwig is a Node.js CLI tool that generates static "link in bio" pages. It uses CommonJS modules and generates HTML, CSS, QR codes, and Open Graph images from a JSON configuration file.
33
+ OpenTwig is a Node.js CLI tool that generates static "link in bio" pages. Uses CommonJS modules. Generates HTML, CSS, QR codes, and Open Graph images from JSON config.
33
34
 
34
- **Core technologies:** Node.js (v14+), CommonJS, PostCSS, Sharp, qrcode, html-minifier-terser, Express, WebSocket, Chokidar
35
+ **Core technologies:** Node.js (v14+), CommonJS, PostCSS, Sharp, Vitest
35
36
 
36
37
  ## Code Style Guidelines
37
38
 
38
39
  ### Module System
39
40
  - **Use CommonJS exclusively** - no ES6 imports/exports
40
- - Import: `const moduleName = require('./path/to/module');`
41
+ - Import: `const module = require('./path/to/module');`
41
42
  - Export: `module.exports = functionName;` or `module.exports = { name, value };`
42
- - Main entry point includes shebang: `#! /usr/bin/env node`
43
+ - Main entry point includes shebang: `#! /usr/bin/env node` and `'use strict';`
43
44
 
44
45
  ### File Organization
45
46
  - **Single function per file** preferred in `src/utils/`
46
- - Use descriptive filenames in camelCase (e.g., `buildPage.js`, `generateQR.js`)
47
- - Theme structure: `theme/{themeName}/index.js`, `style.css`, `components/`
47
+ - Use descriptive filenames in camelCase (e.g., `buildPage.js`)
48
48
  - Utilities in `src/utils/`, constants in `src/constants.js`
49
+ - Theme structure: `theme/{themeName}/index.js`, `style.css`, `components/`
49
50
 
50
51
  ### Naming Conventions
51
52
  - **Variables/Functions**: camelCase (`const buildPage = async (config) => {}`)
52
53
  - **Constants**: UPPER_SNAKE_CASE (`const OUTPUT_FILES = { HTML: 'index.html' }`)
53
54
  - **Files**: camelCase for JS files (e.g., `loadConfig.js`)
54
- - **Destructured params**: Use descriptive names (`function({title, url, name})`)
55
55
 
56
56
  ### Code Patterns
57
57
 
58
58
  **Function exports:**
59
59
  ```javascript
60
- // Pattern: Immediately export a function
61
60
  module.exports = async function(config) {
62
61
  // implementation
63
62
  };
64
63
  ```
65
64
 
66
- **Async/await:**
65
+ **Async/await with error handling:**
67
66
  ```javascript
68
67
  const buildPage = async (config) => {
69
68
  try {
70
69
  const theme = loadTheme(config);
71
- const html = await generateHTML(config, theme);
72
- return { html, theme };
70
+ return { html: await generateHTML(config, theme), theme };
73
71
  } catch (error) {
74
72
  throw new Error(`Failed to build page: ${error.message}`);
75
73
  }
@@ -91,29 +89,18 @@ module.exports = function({title, name}) {
91
89
  - Always use try-catch for async functions
92
90
  - Log errors with `console.error()`
93
91
  - Use `process.exit(1)` on fatal errors, `process.exit(0)` on success
94
- - Prefix error messages with context (e.g., `ERROR_PREFIX`)
95
- - Centralize error messages in `src/constants.js`
96
-
97
- ```javascript
98
- try {
99
- const result = await someAsyncOperation();
100
- return result;
101
- } catch (error) {
102
- throw new Error(`Operation failed: ${error.message}`);
103
- }
104
- ```
92
+ - Prefix error messages with context from `src/constants.js`
105
93
 
106
94
  ### Security
107
95
  - Always escape HTML output using `escapeHTML()` utility
108
- - Always escape XML for OG images using `escapeXml()`
96
+ - Always escape XML for OG images using `escapeXml()` utility
109
97
  - Validate config fields in `configDefaults.js`
110
98
  - Sanitize all user-generated content
111
99
 
112
100
  ### Documentation
113
101
  - Add JSDoc comments for functions with @param and @returns
114
102
  - Add module-level docstrings describing purpose
115
- - Comment non-obvious logic
116
- - Example:
103
+
117
104
  ```javascript
118
105
  /**
119
106
  * Load theme configuration from theme directory
@@ -137,17 +124,6 @@ Centralize all constants in `src/constants.js`:
137
124
  - CLI options
138
125
  - Messages (ERROR_PREFIX, SUCCESS_PREFIX, etc.)
139
126
  - Default values
140
- - Configuration validation requirements
141
-
142
- ### Component Pattern (Themes)
143
- Theme components in `theme/*/components/` export functions:
144
- ```javascript
145
- module.exports = function({link}) {
146
- return `<a href="${link.url}" target="_blank" rel="noopener">
147
- <span>${link.title}</span>
148
- </a>`;
149
- };
150
- ```
151
127
 
152
128
  ### File Paths
153
129
  - Use `path.join()` for path construction
@@ -155,169 +131,42 @@ module.exports = function({link}) {
155
131
  - Use `process.cwd()` for current working directory
156
132
  - Check file existence with `fs.existsSync()` before operations
157
133
 
134
+ ## Testing Guidelines
135
+
136
+ Tests use Vitest with ESM imports for test utilities and CommonJS require for modules:
137
+
138
+ ```javascript
139
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
140
+
141
+ describe('functionName', () => {
142
+ it('should do something', () => {
143
+ const module = require('./module');
144
+ expect(result).toBe(expected);
145
+ });
146
+ });
147
+ ```
148
+
158
149
  ## Key Files Reference
159
150
 
160
- - `src/index.js` - Main CLI entry point with argument parsing
161
- - `src/constants.js` - Centralized constants (including LIVE_MODE settings)
162
- - `src/utils/loadConfig.js` - Load and validate config.json
163
- - `src/utils/buildPage.js` - Orchestrate page generation
164
- - `src/utils/generateHTML.js` - HTML generation with minification
165
- - `src/utils/processCSS.js` - CSS processing with PostCSS
166
- - `src/utils/generateOGImage.js` - Open Graph image generation
167
- - `src/utils/generateQR.js` - QR code generation
168
- - `src/utils/saveFiles.js` - Save all output files to dist/
169
- - `src/utils/configDefaults.js` - Default values and validation
170
- - `src/utils/startLiveServer.js` - Live preview server (Express + WebSocket)
171
- - `src/utils/websocketServer.js` - WebSocket connection management
172
- - `src/utils/setupWatcher.js` - Config file change watcher
173
- - `src/live-ui/index.html` - Live editor main page
174
- - `src/live-ui/styles.css` - Live editor styles
175
- - `src/live-ui/preview.js` - Preview iframe management
176
- - `src/live-ui/editor.js` - Config editor logic
177
- - `src/live-ui/sidebar.js` - Sidebar components and form rendering
178
- - `theme/*/index.js` - Theme-specific HTML templates
179
- - `validateConfig.js` - Config validation utility
151
+ - `src/index.js` - Main CLI entry point
152
+ - `src/constants.js` - Centralized constants
153
+ - `src/utils/` - Utility functions (one function per file)
154
+ - `theme/` - Theme templates
155
+ - `src/live-ui/` - Live editor UI
180
156
 
181
157
  ## Output Files
182
158
 
183
- All generated files go to `dist/` directory:
159
+ All generated files go to `dist/`:
184
160
  - `index.html` - Main page
185
161
  - `style.css` - Processed CSS
186
- - `avatar.{ext}` - User avatar (if configured)
187
- - `og-image.jpg` - Open Graph preview image
162
+ - `avatar.{ext}` - User avatar
163
+ - `og-image.jpg` - Open Graph image
188
164
  - `qr.svg` - QR code
189
165
 
190
- ## Live Mode Architecture
191
-
192
- ### Overview
193
- Live mode (`--live` flag) provides an interactive development environment with real-time preview and configuration editing.
194
-
195
- ### Components
196
-
197
- **Backend (Node.js):**
198
- - `startLiveServer.js` - Express server with WebSocket support
199
- - HTTP endpoints: `/api/config`, `/api/themes`, `/api/avatar`, `/api/validate`
200
- - Static file serving for `dist/` and `src/live-ui/`
201
- - File upload handling (multer)
202
- - `websocketServer.js` - WebSocket server for real-time updates
203
- - Broadcast events: `reload`, `config-update`, `theme-change`
204
- - Client connection management
205
- - `setupWatcher.js` - Config file watcher
206
- - Chokidar-based file watching
207
- - Debounced change detection
208
- - Auto rebuild on config changes
209
-
210
- **Frontend (Vanilla JS):**
211
- - `index.html` - Main editor page (Preview on left, Sidebar on right)
212
- - `preview.js` - Preview iframe management and WebSocket client
213
- - `editor.js` - Config API communication and auto-save
214
- - `sidebar.js` - Dynamic form generation and event handling
215
-
216
- ### Features
217
-
218
- **Editor Features:**
219
- - Theme selection (default, dark, minimal, colorful)
220
- - Profile editing (URL, name, bio, avatar upload)
221
- - Links management (add, edit, delete, reorder)
222
- - Footer links management (URL or modal content)
223
- - Share settings configuration
224
- - Advanced settings (minify CSS)
225
- - Auto-save with debounce (500ms)
226
- - Export config as JSON
227
-
228
- **Real-time Updates:**
229
- - Config changes → WebSocket broadcast → Preview reload
230
- - File watcher → Rebuild → WebSocket broadcast
231
- - Avatar upload → Save → Rebuild → Preview update
232
-
233
- ### Layout
234
-
235
- ```
236
- ┌─────────────────────────────────────────────┐
237
- │ Header: OpenTwig Live Editor [Save][Export] │
238
- ├──────────────────────────────┬──────────────┤
239
- │ │ │
240
- │ Preview Iframe │ Sidebar │
241
- │ (dist/index.html embedded) │ │
242
- │ │ - Theme │
243
- │ │ - Profile │
244
- │ │ - Links │
245
- │ │ - Footer │
246
- │ │ - Share │
247
- │ │ - Advanced │
248
- │ │ │
249
- ├──────────────────────────────┴──────────────┤
250
- │ Status: Connected | Auto-save: ON │
251
- └─────────────────────────────────────────────┘
252
- ```
253
-
254
- ### WebSocket Events
255
-
256
- **Server → Client:**
257
- ```json
258
- {
259
- "type": "reload"
260
- }
261
-
262
- {
263
- "type": "config-update",
264
- "config": { ... }
265
- }
266
-
267
- {
268
- "type": "theme-change",
269
- "theme": "dark"
270
- }
271
- ```
272
-
273
- **API Endpoints:**
274
-
275
- **GET /api/config**
276
- - Returns current config from config.json
277
- - Creates sample config if not exists
278
-
279
- **POST /api/config**
280
- - Accepts config JSON
281
- - Validates and saves to config.json
282
- - Triggers rebuild and WebSocket broadcast
283
-
284
- **POST /api/avatar**
285
- - Accepts multipart form data with avatar file
286
- - Saves to working directory
287
- - Returns file path
288
-
289
- **GET /api/themes**
290
- - Returns array of available theme names
291
-
292
- **GET /api/validate?config=...**
293
- - Validates config JSON
294
- - Returns errors and warnings
295
-
296
- **GET /api/status**
297
- - Returns server status (connected clients, config path, etc.)
298
-
299
- ## Testing Approach
300
-
301
- The project uses Vitest for automated testing:
302
- 1. Run `npm test` to run tests in watch mode
303
- 2. Run `npm run test:run` to run tests once
304
- 3. Run `npm run test:coverage` to run tests with coverage report
305
-
306
- For manual testing:
307
- 1. Create a config.json using `npm start -- --init`
308
- 2. Run `npm start` to generate the page
309
- 3. Verify `dist/` output files are generated correctly
310
- 4. Test all themes by modifying config.json
311
- 5. Test edge cases: missing avatar, empty config, long text
312
- 6. Verify HTML is valid (open in browser)
313
- 7. Check CSS renders correctly
314
- 8. Confirm images are processed properly
315
-
316
166
  ## When Making Changes
317
167
 
318
168
  1. **Always read the file first** - Use the Read tool before editing
319
169
  2. **Follow existing patterns** - Mimic code style and structure
320
- 3. **Test thoroughly** - Manual testing after changes
170
+ 3. **Test thoroughly** - Run `npm run test:run` after changes
321
171
  4. **Add JSDoc comments** - Document new functions
322
- 5. **Update constants** if needed - Add new constants to `src/constants.js`
323
- 6. **Keep it simple** - One function per file, focused responsibilities
172
+ 5. **Keep it simple** - One function per file, focused responsibilities
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentwig",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "opentwig 🌿 is an open source link in bio page generator.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -11,7 +11,6 @@
11
11
  "test": "vitest",
12
12
  "test:run": "vitest run",
13
13
  "test:coverage": "vitest run --coverage",
14
- "test-og": "node test-og.js",
15
14
  "live": "node src/index.js --live"
16
15
  },
17
16
  "devDependencies": {
@@ -62,4 +61,4 @@
62
61
  "browserslist": [
63
62
  "ie 9"
64
63
  ]
65
- }
64
+ }
@@ -166,6 +166,7 @@ window.configEditor = {
166
166
  loadConfig,
167
167
  saveConfig,
168
168
  autoSave,
169
+ validateField,
169
170
  currentConfig: () => currentConfig,
170
171
  updateConfig: (newConfig) => {
171
172
  currentConfig = newConfig;
@@ -0,0 +1,94 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenTwig Live Editor</title>
7
+ <link rel="stylesheet" href="/live-ui/styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="app-container">
11
+ <header class="app-header">
12
+ <div class="header-left">
13
+ <h1>🌿 OpenTwig Live Editor</h1>
14
+ <span class="status-indicator" id="statusIndicator">
15
+ <span class="status-dot"></span>
16
+ <span class="status-text">Connecting...</span>
17
+ </span>
18
+ </div>
19
+ <div class="header-right">
20
+ <button class="btn btn-primary" id="saveBtn">
21
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
22
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
23
+ <polyline points="17 21 17 13 7 13 7 21"></polyline>
24
+ <polyline points="7 3 7 8 15 8"></polyline>
25
+ </svg>
26
+ Save
27
+ </button>
28
+ <button class="btn btn-secondary" id="exportBtn">
29
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
30
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
31
+ <polyline points="7 10 12 15 17 10"></polyline>
32
+ <line x1="12" y1="15" x2="12" y2="3"></line>
33
+ </svg>
34
+ Export
35
+ </button>
36
+ </div>
37
+ </header>
38
+
39
+ <div class="main-content">
40
+ <div class="preview-container">
41
+ <div class="preview-toolbar">
42
+ <span class="preview-title">Preview</span>
43
+ <button class="refresh-btn" id="refreshBtn" title="Refresh preview">
44
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
45
+ <polyline points="23 4 23 10 17 10"></polyline>
46
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
47
+ </svg>
48
+ </button>
49
+ </div>
50
+ <div class="preview-frame">
51
+ <iframe id="previewFrame" src="/index.html" title="Preview"></iframe>
52
+ </div>
53
+ </div>
54
+
55
+ <aside class="sidebar">
56
+ <div class="sidebar-header">
57
+ <h2>Configuration</h2>
58
+ <button class="toggle-sidebar" id="toggleSidebar" title="Toggle sidebar">
59
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
60
+ <line x1="18" y1="6" x2="6" y2="18"></line>
61
+ <line x1="6" y1="6" x2="18" y2="18"></line>
62
+ </svg>
63
+ </button>
64
+ </div>
65
+
66
+ <div class="sidebar-content">
67
+ <div class="config-section" id="configForm">
68
+ <!-- Dynamic form content will be injected here -->
69
+ </div>
70
+ </div>
71
+
72
+ <div class="sidebar-footer">
73
+ <div class="auto-save-toggle">
74
+ <label class="toggle-switch">
75
+ <input type="checkbox" id="autoSave" checked>
76
+ <span class="slider"></span>
77
+ </label>
78
+ <span>Auto-save</span>
79
+ </div>
80
+ <div class="last-saved">
81
+ Last saved: <span id="lastSaved">Never</span>
82
+ </div>
83
+ </div>
84
+ </aside>
85
+ </div>
86
+
87
+ <div class="notification-container" id="notificationContainer"></div>
88
+ </div>
89
+
90
+ <script src="/live-ui/preview.js"></script>
91
+ <script src="/live-ui/editor.js"></script>
92
+ <script src="/live-ui/sidebar.js"></script>
93
+ </body>
94
+ </html>
@@ -383,6 +383,8 @@ const renderShareSection = (container, config) => {
383
383
 
384
384
  const shareUrlGroup = createFormGroup('Share URL', '', config.share.url, 'text', (value) => {
385
385
  config.share.url = value;
386
+ // Sync with main URL so QR code updates correctly
387
+ config.url = value;
386
388
  window.configEditor.updateConfig(config);
387
389
  window.configEditor.autoSave(config);
388
390
  return [];