loopwind 0.9.1

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 (245) hide show
  1. package/FONTS.md +156 -0
  2. package/HELPERS_DEMO.md +134 -0
  3. package/PROJECT_STRUCTURE.md +286 -0
  4. package/PUBLISHING.md +171 -0
  5. package/README.md +1020 -0
  6. package/REGISTRY_SETUP.md +427 -0
  7. package/SHADCN_INTEGRATION.md +269 -0
  8. package/TAILWIND.md +228 -0
  9. package/TEMPLATE_SOURCES.md +363 -0
  10. package/_dsgn/templates/banner-hero/banner-hero.tsx +57 -0
  11. package/_dsgn/templates/banner-hero/meta.json +14 -0
  12. package/_dsgn/templates/composite-card/meta.json +16 -0
  13. package/_dsgn/templates/composite-card/template.tsx +44 -0
  14. package/_dsgn/templates/image/meta.json +13 -0
  15. package/_dsgn/templates/image/template.tsx +28 -0
  16. package/_dsgn/templates/kitchen-sink/meta.json +13 -0
  17. package/_dsgn/templates/kitchen-sink/template.tsx +72 -0
  18. package/_dsgn/templates/qr-card/meta.json +14 -0
  19. package/_dsgn/templates/qr-card/template.tsx +39 -0
  20. package/_dsgn/templates/test-parent/child/meta.json +11 -0
  21. package/_dsgn/templates/test-parent/child/template.tsx +27 -0
  22. package/_dsgn/templates/test-parent/meta.json +12 -0
  23. package/_dsgn/templates/test-parent/template.tsx +30 -0
  24. package/_dsgn/templates/test-sibling/meta.json +11 -0
  25. package/_dsgn/templates/test-sibling/template.tsx +20 -0
  26. package/_dsgn/templates/video/.tmp/template-1763421345296.mjs +43 -0
  27. package/_dsgn/templates/video/.tmp/template-1763421362228.mjs +43 -0
  28. package/_dsgn/templates/video/.tmp/template-1763421377706.mjs +43 -0
  29. package/_dsgn/templates/video/meta.json +17 -0
  30. package/_dsgn/templates/video/template.tsx +48 -0
  31. package/dist/cli.d.ts +3 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +70 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/commands/add.d.ts +6 -0
  36. package/dist/commands/add.d.ts.map +1 -0
  37. package/dist/commands/add.js +86 -0
  38. package/dist/commands/add.js.map +1 -0
  39. package/dist/commands/default.d.ts +2 -0
  40. package/dist/commands/default.d.ts.map +1 -0
  41. package/dist/commands/default.js +69 -0
  42. package/dist/commands/default.js.map +1 -0
  43. package/dist/commands/init.d.ts +2 -0
  44. package/dist/commands/init.d.ts.map +1 -0
  45. package/dist/commands/init.js +75 -0
  46. package/dist/commands/init.js.map +1 -0
  47. package/dist/commands/list.d.ts +2 -0
  48. package/dist/commands/list.d.ts.map +1 -0
  49. package/dist/commands/list.js +83 -0
  50. package/dist/commands/list.js.map +1 -0
  51. package/dist/commands/preview.d.ts +3 -0
  52. package/dist/commands/preview.d.ts.map +1 -0
  53. package/dist/commands/preview.js +296 -0
  54. package/dist/commands/preview.js.map +1 -0
  55. package/dist/commands/render.d.ts +10 -0
  56. package/dist/commands/render.d.ts.map +1 -0
  57. package/dist/commands/render.js +204 -0
  58. package/dist/commands/render.js.map +1 -0
  59. package/dist/commands/validate.d.ts +2 -0
  60. package/dist/commands/validate.d.ts.map +1 -0
  61. package/dist/commands/validate.js +107 -0
  62. package/dist/commands/validate.js.map +1 -0
  63. package/dist/default-templates/AGENTS.md +229 -0
  64. package/dist/default-templates/image/meta.json +13 -0
  65. package/dist/default-templates/image/template.d.ts +20 -0
  66. package/dist/default-templates/image/template.d.ts.map +1 -0
  67. package/dist/default-templates/image/template.js +18 -0
  68. package/dist/default-templates/image/template.js.map +1 -0
  69. package/dist/default-templates/image/template.tsx +20 -0
  70. package/dist/default-templates/image-template/meta.json +13 -0
  71. package/dist/default-templates/image-template/template.tsx +19 -0
  72. package/dist/default-templates/kitchen-sink/meta.json +13 -0
  73. package/dist/default-templates/kitchen-sink/template.tsx +64 -0
  74. package/dist/default-templates/page/meta.json +17 -0
  75. package/dist/default-templates/page/template.tsx +37 -0
  76. package/dist/default-templates/video/meta.json +17 -0
  77. package/dist/default-templates/video/template.d.ts +26 -0
  78. package/dist/default-templates/video/template.d.ts.map +1 -0
  79. package/dist/default-templates/video/template.js +33 -0
  80. package/dist/default-templates/video/template.js.map +1 -0
  81. package/dist/default-templates/video/template.tsx +37 -0
  82. package/dist/default-templates/video-template/meta.json +17 -0
  83. package/dist/default-templates/video-template/template.tsx +36 -0
  84. package/dist/default-templates/website/meta.json +16 -0
  85. package/dist/default-templates/website/pages/home.tsx +17 -0
  86. package/dist/default-templates/website/parts/footer.tsx +17 -0
  87. package/dist/default-templates/website/parts/header.tsx +17 -0
  88. package/dist/default-templates/website/template.tsx +17 -0
  89. package/dist/default-templates/website-template/meta.json +16 -0
  90. package/dist/default-templates/website-template/pages/home.tsx +16 -0
  91. package/dist/default-templates/website-template/parts/footer.tsx +16 -0
  92. package/dist/default-templates/website-template/parts/header.tsx +16 -0
  93. package/dist/default-templates/website-template/template.tsx +16 -0
  94. package/dist/lib/config.d.ts +34 -0
  95. package/dist/lib/config.d.ts.map +1 -0
  96. package/dist/lib/config.js +248 -0
  97. package/dist/lib/config.js.map +1 -0
  98. package/dist/lib/constants.d.ts +7 -0
  99. package/dist/lib/constants.d.ts.map +1 -0
  100. package/dist/lib/constants.js +12 -0
  101. package/dist/lib/constants.js.map +1 -0
  102. package/dist/lib/helpers.d.ts +29 -0
  103. package/dist/lib/helpers.d.ts.map +1 -0
  104. package/dist/lib/helpers.js +159 -0
  105. package/dist/lib/helpers.js.map +1 -0
  106. package/dist/lib/installer.d.ts +51 -0
  107. package/dist/lib/installer.d.ts.map +1 -0
  108. package/dist/lib/installer.js +215 -0
  109. package/dist/lib/installer.js.map +1 -0
  110. package/dist/lib/renderer.d.ts +51 -0
  111. package/dist/lib/renderer.d.ts.map +1 -0
  112. package/dist/lib/renderer.js +524 -0
  113. package/dist/lib/renderer.js.map +1 -0
  114. package/dist/lib/tailwind-config-loader.d.ts +47 -0
  115. package/dist/lib/tailwind-config-loader.d.ts.map +1 -0
  116. package/dist/lib/tailwind-config-loader.js +432 -0
  117. package/dist/lib/tailwind-config-loader.js.map +1 -0
  118. package/dist/lib/tailwind-detector.d.ts +36 -0
  119. package/dist/lib/tailwind-detector.d.ts.map +1 -0
  120. package/dist/lib/tailwind-detector.js +156 -0
  121. package/dist/lib/tailwind-detector.js.map +1 -0
  122. package/dist/lib/tailwind.d.ts +8 -0
  123. package/dist/lib/tailwind.d.ts.map +1 -0
  124. package/dist/lib/tailwind.js +994 -0
  125. package/dist/lib/tailwind.js.map +1 -0
  126. package/dist/lib/template-validator.d.ts +22 -0
  127. package/dist/lib/template-validator.d.ts.map +1 -0
  128. package/dist/lib/template-validator.js +174 -0
  129. package/dist/lib/template-validator.js.map +1 -0
  130. package/dist/lib/utils.d.ts +44 -0
  131. package/dist/lib/utils.d.ts.map +1 -0
  132. package/dist/lib/utils.js +207 -0
  133. package/dist/lib/utils.js.map +1 -0
  134. package/dist/lib/version-check.d.ts +16 -0
  135. package/dist/lib/version-check.d.ts.map +1 -0
  136. package/dist/lib/version-check.js +88 -0
  137. package/dist/lib/version-check.js.map +1 -0
  138. package/dist/lib/video-renderer.d.ts +32 -0
  139. package/dist/lib/video-renderer.d.ts.map +1 -0
  140. package/dist/lib/video-renderer.js +226 -0
  141. package/dist/lib/video-renderer.js.map +1 -0
  142. package/dist/sdk/index.d.ts +58 -0
  143. package/dist/sdk/index.d.ts.map +1 -0
  144. package/dist/sdk/index.js +119 -0
  145. package/dist/sdk/index.js.map +1 -0
  146. package/dist/sdk/template.d.ts +40 -0
  147. package/dist/sdk/template.d.ts.map +1 -0
  148. package/dist/sdk/template.js +60 -0
  149. package/dist/sdk/template.js.map +1 -0
  150. package/dist/types/config.d.ts +62 -0
  151. package/dist/types/config.d.ts.map +1 -0
  152. package/dist/types/config.js +47 -0
  153. package/dist/types/config.js.map +1 -0
  154. package/dist/types/template.d.ts +79 -0
  155. package/dist/types/template.d.ts.map +1 -0
  156. package/dist/types/template.js +2 -0
  157. package/dist/types/template.js.map +1 -0
  158. package/examples/nextjs-api/README.md +180 -0
  159. package/examples/nextjs-api/package.json +21 -0
  160. package/examples/nextjs-api/pages/api/intro-video.ts +53 -0
  161. package/examples/nextjs-api/pages/api/og-image.ts +50 -0
  162. package/netlify.toml +13 -0
  163. package/package.json +84 -0
  164. package/patches/satori+0.18.3.patch +13 -0
  165. package/test-templates/TESTS.md +63 -0
  166. package/test-templates/_dsgn/templates/absolute-spin/meta.json +7 -0
  167. package/test-templates/_dsgn/templates/absolute-spin/template.tsx +16 -0
  168. package/test-templates/_dsgn/templates/animated-intro/.tmp/template-1763468771640.mjs +7 -0
  169. package/test-templates/_dsgn/templates/animated-intro/meta.json +10 -0
  170. package/test-templates/_dsgn/templates/animated-intro/template.tsx +23 -0
  171. package/test-templates/_dsgn/templates/centered-spin/.tmp/template-1763468525386.mjs +7 -0
  172. package/test-templates/_dsgn/templates/centered-spin/meta.json +7 -0
  173. package/test-templates/_dsgn/templates/centered-spin/template.tsx +11 -0
  174. package/test-templates/_dsgn/templates/composite/.tmp/template-1763468815645.mjs +7 -0
  175. package/test-templates/_dsgn/templates/composite/meta.json +9 -0
  176. package/test-templates/_dsgn/templates/composite/template.tsx +23 -0
  177. package/test-templates/_dsgn/templates/easing-test/.tmp/template-1763468824501.mjs +7 -0
  178. package/test-templates/_dsgn/templates/easing-test/meta.json +7 -0
  179. package/test-templates/_dsgn/templates/easing-test/template.tsx +47 -0
  180. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466364336.mjs +10 -0
  181. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466584319.mjs +10 -0
  182. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466667797.mjs +10 -0
  183. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466746504.mjs +10 -0
  184. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466930225.mjs +10 -0
  185. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467004552.mjs +10 -0
  186. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467060334.mjs +10 -0
  187. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467124493.mjs +10 -0
  188. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467174690.mjs +10 -0
  189. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467359134.mjs +10 -0
  190. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467451928.mjs +10 -0
  191. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467758275.mjs +10 -0
  192. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467985201.mjs +10 -0
  193. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468020563.mjs +10 -0
  194. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468090428.mjs +10 -0
  195. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468211036.mjs +10 -0
  196. package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468394057.mjs +10 -0
  197. package/test-templates/_dsgn/templates/minimal-spin/meta.json +7 -0
  198. package/test-templates/_dsgn/templates/minimal-spin/template.tsx +13 -0
  199. package/test-templates/_dsgn/templates/no-origin-spin/meta.json +7 -0
  200. package/test-templates/_dsgn/templates/no-origin-spin/template.tsx +10 -0
  201. package/test-templates/_dsgn/templates/opacity-test/meta.json +7 -0
  202. package/test-templates/_dsgn/templates/opacity-test/template.tsx +9 -0
  203. package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468758954.mjs +17 -0
  204. package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468815672.mjs +17 -0
  205. package/test-templates/_dsgn/templates/qr-code/meta.json +9 -0
  206. package/test-templates/_dsgn/templates/qr-code/template.tsx +20 -0
  207. package/test-templates/_dsgn/templates/rotation-abs-test/meta.json +7 -0
  208. package/test-templates/_dsgn/templates/rotation-abs-test/template.tsx +15 -0
  209. package/test-templates/_dsgn/templates/rotation-corner/meta.json +7 -0
  210. package/test-templates/_dsgn/templates/rotation-corner/template.tsx +12 -0
  211. package/test-templates/_dsgn/templates/rotation-test/meta.json +7 -0
  212. package/test-templates/_dsgn/templates/rotation-test/template.tsx +12 -0
  213. package/test-templates/_dsgn/templates/shake-test/meta.json +7 -0
  214. package/test-templates/_dsgn/templates/shake-test/template.tsx +12 -0
  215. package/test-templates/_dsgn/templates/static-image/.tmp/template-1763468746271.mjs +7 -0
  216. package/test-templates/_dsgn/templates/static-image/meta.json +9 -0
  217. package/test-templates/_dsgn/templates/static-image/template.tsx +19 -0
  218. package/test-templates/_dsgn/templates/translate-test/meta.json +7 -0
  219. package/test-templates/_dsgn/templates/translate-test/template.tsx +9 -0
  220. package/test-templates/_dsgn/templates/video-loops/.tmp/template-1763468793192.mjs +15 -0
  221. package/test-templates/_dsgn/templates/video-loops/meta.json +9 -0
  222. package/test-templates/_dsgn/templates/video-loops/template.tsx +39 -0
  223. package/test-templates/_dsgn/templates/wrapped-spin/meta.json +7 -0
  224. package/test-templates/_dsgn/templates/wrapped-spin/template.tsx +17 -0
  225. package/test-templates/compare-svgs.mjs +30 -0
  226. package/test-templates/convert-frames.mjs +15 -0
  227. package/test-templates/debug-rotation.mjs +25 -0
  228. package/test-templates/run-tests.sh +39 -0
  229. package/test-templates/test-sdk.mjs +115 -0
  230. package/website/.astro/settings.json +5 -0
  231. package/website/.astro/types.d.ts +1 -0
  232. package/website/README.md +112 -0
  233. package/website/astro.config.mjs +18 -0
  234. package/website/dist/_astro/fonts.DHdiHGBO.css +1 -0
  235. package/website/dist/fonts/index.html +193 -0
  236. package/website/dist/helpers/index.html +166 -0
  237. package/website/dist/images/index.html +314 -0
  238. package/website/dist/index.html +219 -0
  239. package/website/dist/llm.txt +2448 -0
  240. package/website/dist/styling/index.html +365 -0
  241. package/website/dist/templates/index.html +124 -0
  242. package/website/dist/video/index.html +636 -0
  243. package/website/package-lock.json +7606 -0
  244. package/website/package.json +23 -0
  245. package/website/public/robots.txt +5 -0
@@ -0,0 +1,296 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import express from 'express';
4
+ import { watch } from 'chokidar';
5
+ import { renderToSVG } from '../lib/renderer.js';
6
+ import { getTemplatePathAsync } from '../lib/utils.js';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import fs from 'fs/promises';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ export const previewCommand = new Command('preview')
13
+ .description('Preview a template with hot module replacement')
14
+ .argument('<template>', 'Template name to preview')
15
+ .argument('[props]', 'Template props as JSON string or path to JSON file')
16
+ .option('-p, --port <port>', 'Port to run the preview server on', '3000')
17
+ .option('-f, --format <format>', 'Output format (svg or png)', 'png')
18
+ .option('--no-open', 'Don\'t automatically open the browser')
19
+ .addHelpText('after', `
20
+ Examples:
21
+ $ loopwind preview banner-hero
22
+ $ loopwind preview banner-hero '{"title":"Hello World"}'
23
+ $ loopwind preview banner-hero props.json
24
+ $ loopwind preview banner-hero '{"title":"Test"}' --format svg
25
+ $ loopwind preview banner-hero --port 8080 --no-open
26
+ `)
27
+ .action(async (templateName, propsArg, options) => {
28
+ try {
29
+ console.log(chalk.blue('\n🎨 Starting preview server...\n'));
30
+ // Get template path
31
+ const templatePath = await getTemplatePathAsync(templateName);
32
+ // Validate template exists
33
+ try {
34
+ await fs.access(path.join(path.dirname(templatePath), 'template.tsx'));
35
+ }
36
+ catch {
37
+ console.error(chalk.red(`✖ Template "${templateName}" not found`));
38
+ console.log(chalk.dim(` Run ${chalk.cyan('loopwind list')} to see available templates`));
39
+ process.exit(1);
40
+ }
41
+ // Parse props
42
+ let props = {};
43
+ if (propsArg) {
44
+ if (propsArg.startsWith('{')) {
45
+ try {
46
+ props = JSON.parse(propsArg);
47
+ }
48
+ catch (error) {
49
+ console.error(chalk.red('✖ Invalid JSON props'));
50
+ process.exit(1);
51
+ }
52
+ }
53
+ else {
54
+ try {
55
+ const propsContent = await fs.readFile(propsArg, 'utf-8');
56
+ props = JSON.parse(propsContent);
57
+ }
58
+ catch (error) {
59
+ console.error(chalk.red(`✖ Could not read props file: ${propsArg}`));
60
+ process.exit(1);
61
+ }
62
+ }
63
+ }
64
+ // Create Express app
65
+ const app = express();
66
+ const port = typeof options.port === 'string' ? parseInt(options.port) : (options.port || 3000);
67
+ const format = options.format || 'png';
68
+ // Store the latest render
69
+ let latestRender = '';
70
+ let lastError = null;
71
+ let clients = new Set();
72
+ // Function to render template
73
+ const renderTemplate = async () => {
74
+ try {
75
+ lastError = null;
76
+ // Load and validate metadata
77
+ const { loadTemplateMeta } = await import('../lib/utils.js');
78
+ const meta = await loadTemplateMeta(templateName);
79
+ // Validate dimensions
80
+ if (!meta.size || !meta.size.width || !meta.size.height) {
81
+ throw new Error('Template metadata is missing size.width or size.height');
82
+ }
83
+ if (meta.size.width <= 0 || meta.size.height <= 0) {
84
+ throw new Error(`Invalid template dimensions: ${meta.size.width}x${meta.size.height}`);
85
+ }
86
+ if (format === 'svg') {
87
+ latestRender = await renderToSVG(templateName, props);
88
+ }
89
+ else {
90
+ // First render to SVG to validate it
91
+ const svg = await renderToSVG(templateName, props);
92
+ // Validate SVG contains valid dimensions
93
+ if (!svg.includes('width=') || !svg.includes('height=')) {
94
+ throw new Error('Generated SVG is missing width or height attributes');
95
+ }
96
+ // Convert to PNG (this is where resvg can crash)
97
+ const { renderToPNG } = await import('../lib/renderer.js');
98
+ latestRender = await renderToPNG(templateName, props);
99
+ }
100
+ console.log(chalk.green('✓ Template rendered'));
101
+ // Notify all connected clients to refresh
102
+ clients.forEach(res => {
103
+ res.write('data: reload\n\n');
104
+ });
105
+ }
106
+ catch (error) {
107
+ lastError = error.message || String(error);
108
+ console.error(chalk.red('✖ Render error:'), lastError);
109
+ // Provide helpful suggestions
110
+ if (lastError && (lastError.includes('display: flex') || lastError.includes('Satori'))) {
111
+ console.log(chalk.yellow('\n 💡 Common fixes:'));
112
+ console.log(chalk.dim(' • Add tw("flex") to containers with multiple children'));
113
+ console.log(chalk.dim(' • Ensure all props match the template requirements'));
114
+ console.log(chalk.dim(' • Try --format svg to see the raw SVG output\n'));
115
+ }
116
+ else if (lastError && lastError.includes('dimensions')) {
117
+ console.log(chalk.yellow('\n 💡 Check your template metadata for valid size.width and size.height\n'));
118
+ }
119
+ }
120
+ };
121
+ // Initial render
122
+ console.log(chalk.dim('Performing initial render...'));
123
+ await renderTemplate();
124
+ // Show helpful tip if using PNG format
125
+ if (format === 'png') {
126
+ console.log(chalk.dim('\n💡 Tip: If the preview crashes, try:'));
127
+ console.log(chalk.dim(` ${chalk.cyan(`loopwind preview ${templateName} --format svg`)}`));
128
+ console.log(chalk.dim(' This helps identify template issues without PNG conversion.\n'));
129
+ }
130
+ // Serve the preview page
131
+ app.get('/', (req, res) => {
132
+ const html = `
133
+ <!DOCTYPE html>
134
+ <html>
135
+ <head>
136
+ <meta charset="UTF-8">
137
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
138
+ <title>Preview: ${templateName}</title>
139
+ <style>
140
+ * {
141
+ margin: 0;
142
+ padding: 0;
143
+ box-sizing: border-box;
144
+ }
145
+ body {
146
+ background: #0a0a0a;
147
+ color: #ffffff;
148
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
149
+ min-height: 100vh;
150
+ display: flex;
151
+ flex-direction: column;
152
+ }
153
+ .header {
154
+ background: #18181b;
155
+ border-bottom: 1px solid #27272a;
156
+ padding: 1rem 2rem;
157
+ display: flex;
158
+ justify-content: space-between;
159
+ align-items: center;
160
+ }
161
+ .header h1 {
162
+ font-size: 1.25rem;
163
+ font-weight: 600;
164
+ }
165
+ .badge {
166
+ background: #3b82f6;
167
+ color: white;
168
+ padding: 0.25rem 0.75rem;
169
+ border-radius: 0.375rem;
170
+ font-size: 0.875rem;
171
+ font-weight: 500;
172
+ }
173
+ .container {
174
+ flex: 1;
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ padding: 2rem;
179
+ overflow: auto;
180
+ }
181
+ .image-wrapper {
182
+ max-width: 100%;
183
+ max-height: 100%;
184
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
185
+ border-radius: 0.5rem;
186
+ overflow: hidden;
187
+ }
188
+ img {
189
+ display: block;
190
+ max-width: 100%;
191
+ height: auto;
192
+ }
193
+ .error {
194
+ background: #7f1d1d;
195
+ border: 1px solid #991b1b;
196
+ color: #fecaca;
197
+ padding: 1rem 1.5rem;
198
+ border-radius: 0.5rem;
199
+ font-family: 'Courier New', monospace;
200
+ white-space: pre-wrap;
201
+ max-width: 800px;
202
+ }
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div class="header">
207
+ <h1>loopwind preview: ${templateName}</h1>
208
+ <span class="badge">${format.toUpperCase()}</span>
209
+ </div>
210
+ <div class="container" id="container">
211
+ ${lastError ? `<div class="error">${lastError}</div>` : `
212
+ <div class="image-wrapper">
213
+ <img src="/render.${format}?t=${Date.now()}" alt="Template preview" />
214
+ </div>
215
+ `}
216
+ </div>
217
+ <script>
218
+ const eventSource = new EventSource('/events');
219
+ eventSource.onmessage = (event) => {
220
+ if (event.data === 'reload') {
221
+ // Always reload the whole page to ensure metadata changes (like size) are reflected
222
+ window.location.reload();
223
+ }
224
+ };
225
+ </script>
226
+ </body>
227
+ </html>
228
+ `;
229
+ res.send(html);
230
+ });
231
+ // Serve the rendered image/svg
232
+ app.get(`/render.${format}`, (req, res) => {
233
+ if (lastError) {
234
+ res.status(500).send(lastError);
235
+ return;
236
+ }
237
+ if (format === 'svg') {
238
+ res.setHeader('Content-Type', 'image/svg+xml');
239
+ res.send(latestRender);
240
+ }
241
+ else {
242
+ res.setHeader('Content-Type', 'image/png');
243
+ res.send(latestRender);
244
+ }
245
+ });
246
+ // Server-sent events for HMR
247
+ app.get('/events', (req, res) => {
248
+ res.setHeader('Content-Type', 'text/event-stream');
249
+ res.setHeader('Cache-Control', 'no-cache');
250
+ res.setHeader('Connection', 'keep-alive');
251
+ res.flushHeaders();
252
+ clients.add(res);
253
+ req.on('close', () => {
254
+ clients.delete(res);
255
+ });
256
+ });
257
+ // Start server
258
+ const server = app.listen(port, () => {
259
+ console.log(chalk.green('✓ Preview server started\n'));
260
+ console.log(chalk.cyan(' Local: ') + chalk.underline(`http://localhost:${port}`));
261
+ console.log(chalk.dim('\n Press Ctrl+C to stop\n'));
262
+ // Open browser if requested
263
+ if (options.open !== false) {
264
+ import('open').then(({ default: open }) => {
265
+ open(`http://localhost:${port}`);
266
+ }).catch(() => {
267
+ // Silently fail if open package is not available
268
+ });
269
+ }
270
+ });
271
+ // Watch for changes in the template directory
272
+ const templateDir = path.dirname(templatePath);
273
+ const watcher = watch(templateDir, {
274
+ persistent: true,
275
+ ignoreInitial: true,
276
+ });
277
+ watcher.on('change', async (filePath) => {
278
+ console.log(chalk.blue(`\n📝 ${path.basename(filePath)} changed`));
279
+ await renderTemplate();
280
+ });
281
+ // Handle cleanup
282
+ const cleanup = () => {
283
+ console.log(chalk.yellow('\n\n👋 Shutting down preview server...'));
284
+ watcher.close();
285
+ server.close();
286
+ process.exit(0);
287
+ };
288
+ process.on('SIGINT', cleanup);
289
+ process.on('SIGTERM', cleanup);
290
+ }
291
+ catch (error) {
292
+ console.error(chalk.red('✖ Error:'), error.message);
293
+ process.exit(1);
294
+ }
295
+ });
296
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/commands/preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ interface RenderOptions {
2
+ props?: string;
3
+ out?: string;
4
+ format?: 'png' | 'svg' | 'webp' | 'jpg' | 'jpeg';
5
+ framesOnly?: boolean;
6
+ crf?: number;
7
+ }
8
+ export declare function renderCommand(templateName: string, propsArg: string | undefined, options: RenderOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"AAYA,UAAU,aAAa;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAgOf"}
@@ -0,0 +1,204 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import gradient from 'gradient-string';
4
+ import path from 'path';
5
+ import { isTemplateInstalled, parseProps, ensureOutputDir, loadTemplateMeta } from '../lib/utils.js';
6
+ import { render } from '../lib/renderer.js';
7
+ import { renderVideo, renderVideoFrames } from '../lib/video-renderer.js';
8
+ import { validateTemplateForRendering, enhanceSatoriError } from '../lib/template-validator.js';
9
+ import { getResolvedPaths } from '../lib/config.js';
10
+ import { checkForUpdates } from '../lib/version-check.js';
11
+ export async function renderCommand(templateName, propsArg, options) {
12
+ // Start version check early (non-blocking)
13
+ const updateCheck = checkForUpdates();
14
+ // Helper to show update notification at the end
15
+ const showUpdateNotification = async () => {
16
+ const result = await updateCheck;
17
+ if (result?.isOutdated) {
18
+ console.log(chalk.dim(`Update available: ${result.currentVersion} → ${chalk.green(result.latestVersion)}`));
19
+ console.log(chalk.dim(`Run: ${chalk.cyan('npm install -g loopwind@latest')}\n`));
20
+ }
21
+ };
22
+ const purpleIndigo = gradient(['#a855f7', '#6366f1', '#8b5cf6']);
23
+ console.log('\n' + purpleIndigo(`Rendering template: ${templateName}`) + '\n');
24
+ const spinner = ora({
25
+ text: 'Checking template...',
26
+ color: 'magenta'
27
+ }).start();
28
+ try {
29
+ // Check if template is installed
30
+ const installed = await isTemplateInstalled(templateName);
31
+ if (!installed) {
32
+ spinner.fail(chalk.red(`Template ${templateName} is not installed`));
33
+ console.log(chalk.dim(`\nInstall it with: ${chalk.cyan(`loopwind add ${templateName}`)}\n`));
34
+ process.exit(1);
35
+ }
36
+ // Ensure output directory exists
37
+ await ensureOutputDir();
38
+ // Load template metadata to check type
39
+ spinner.text = 'Loading template...';
40
+ const meta = await loadTemplateMeta(templateName);
41
+ const templateType = meta.type || 'image';
42
+ // Check for unsupported types
43
+ if (templateType === 'presentation') {
44
+ spinner.fail(chalk.yellow('Presentation rendering is not yet implemented'));
45
+ console.log(chalk.dim(`\nPresentation rendering will be available in a future release.`));
46
+ console.log(chalk.dim(`Presentations will export multi-page PDFs with React + Tailwind.\n`));
47
+ process.exit(1);
48
+ }
49
+ if (templateType === 'website') {
50
+ spinner.fail(chalk.yellow('Website rendering is not yet implemented'));
51
+ console.log(chalk.dim(`\nWebsite rendering will be available in a future release.`));
52
+ console.log(chalk.dim(`Websites will export static HTML/CSS from React + Tailwind.\n`));
53
+ process.exit(1);
54
+ }
55
+ // Parse props - prioritize positional argument over --props flag
56
+ spinner.text = 'Loading props...';
57
+ const propsInput = propsArg || options.props;
58
+ const props = await parseProps(propsInput);
59
+ // Validate template and props
60
+ spinner.text = 'Validating template...';
61
+ const validation = await validateTemplateForRendering(templateName, props);
62
+ if (!validation.valid) {
63
+ spinner.fail(chalk.red('Template validation failed'));
64
+ console.log();
65
+ for (const error of validation.errors) {
66
+ console.log(chalk.red(` ✗ ${error.field}: ${error.message}`));
67
+ if (error.suggestion) {
68
+ console.log(chalk.dim(` → ${error.suggestion}`));
69
+ }
70
+ console.log();
71
+ }
72
+ console.log(chalk.yellow('Fix the errors above and try again.\n'));
73
+ process.exit(1);
74
+ }
75
+ // Determine output path (default to outputs folder)
76
+ const paths = await getResolvedPaths();
77
+ // Handle video templates
78
+ if (templateType === 'video') {
79
+ const defaultFileName = `${templateName}-${Date.now()}.mp4`;
80
+ let outputPath = options.out
81
+ ? options.out
82
+ : path.join(paths.outputs, defaultFileName);
83
+ // Auto-correct extension if user provided wrong format
84
+ if (options.out && !outputPath.endsWith('.mp4')) {
85
+ const wrongExt = path.extname(outputPath);
86
+ outputPath = outputPath.replace(/\.[^.]+$/, '.mp4');
87
+ spinner.warn(chalk.yellow(`Video templates must output to .mp4 (changed ${wrongExt} → .mp4)`));
88
+ spinner.start();
89
+ }
90
+ // Frames-only mode
91
+ if (options.framesOnly) {
92
+ const framesDir = options.out || path.join(paths.outputs, `${templateName}-frames-${Date.now()}`);
93
+ spinner.text = 'Rendering video frames...';
94
+ let lastUpdate = Date.now();
95
+ const { totalFrames, fps } = await renderVideoFrames(templateName, props, framesDir, {
96
+ format: options.format === 'svg' ? 'svg' : 'png',
97
+ onProgress: (frame, total, phase) => {
98
+ // Update spinner every 100ms to avoid too many updates
99
+ if (Date.now() - lastUpdate > 100) {
100
+ if (phase === 'svg') {
101
+ spinner.text = `Generating SVGs: ${frame}/${total} (${Math.round((frame / total) * 100)}%)`;
102
+ }
103
+ else if (phase === 'convert') {
104
+ spinner.text = `Converting to PNG: ${frame}/${total} (${Math.round((frame / total) * 100)}%)`;
105
+ }
106
+ else {
107
+ spinner.text = `Rendering frames: ${frame}/${total} (${Math.round((frame / total) * 100)}%)`;
108
+ }
109
+ lastUpdate = Date.now();
110
+ }
111
+ },
112
+ });
113
+ spinner.succeed(chalk.green(`Successfully rendered ${totalFrames} frames to ${chalk.bold(framesDir)}`));
114
+ console.log(chalk.dim(`\nTemplate: ${templateName}`));
115
+ console.log(chalk.dim(`Frames: ${totalFrames} at ${fps}fps`));
116
+ console.log(chalk.dim(`Format: ${options.format === 'svg' ? 'SVG' : 'PNG'}`));
117
+ console.log(chalk.dim(`Output: ${path.resolve(framesDir)}`));
118
+ console.log(chalk.dim(`\nTo encode to video with ffmpeg:`));
119
+ console.log(chalk.cyan(` ffmpeg -framerate ${fps} -i ${framesDir}/frame-%04d.${options.format === 'svg' ? 'svg' : 'png'} -c:v libx264 -pix_fmt yuv420p output.mp4`));
120
+ console.log();
121
+ return;
122
+ }
123
+ // Render video (no ffmpeg required - uses WASM encoder!)
124
+ spinner.text = 'Rendering video...';
125
+ let lastUpdate = Date.now();
126
+ await renderVideo(templateName, props, outputPath, {
127
+ quality: options.crf ? parseInt(String(options.crf), 10) : undefined,
128
+ onFrameProgress: (frame, total, phase) => {
129
+ if (Date.now() - lastUpdate > 100) {
130
+ if (phase === 'svg') {
131
+ spinner.text = `Generating SVGs: ${frame}/${total} (${Math.round((frame / total) * 100)}%)`;
132
+ }
133
+ else if (phase === 'encode') {
134
+ spinner.text = `Encoding video: ${frame}/${total} (${Math.round((frame / total) * 100)}%)`;
135
+ }
136
+ lastUpdate = Date.now();
137
+ }
138
+ },
139
+ });
140
+ spinner.succeed(chalk.green(`Successfully rendered video to ${chalk.bold(outputPath)}`));
141
+ console.log(chalk.dim(`\nTemplate: ${templateName}`));
142
+ console.log(chalk.dim(`Format: MP4`));
143
+ console.log(chalk.dim(`Output: ${path.resolve(outputPath)}\n`));
144
+ await showUpdateNotification();
145
+ return;
146
+ }
147
+ // Handle image templates
148
+ const format = options.format || 'png';
149
+ const defaultFileName = `${templateName}-${Date.now()}.${format}`;
150
+ const outputPath = options.out
151
+ ? options.out
152
+ : path.join(paths.outputs, defaultFileName);
153
+ // Render
154
+ spinner.text = `Rendering to ${format.toUpperCase()}...`;
155
+ await render({
156
+ templateName,
157
+ props,
158
+ outputPath,
159
+ format,
160
+ });
161
+ spinner.succeed(chalk.green(`Successfully rendered to ${chalk.bold(outputPath)}`));
162
+ console.log(chalk.dim(`\nTemplate: ${templateName}`));
163
+ console.log(chalk.dim(`Format: ${format.toUpperCase()}`));
164
+ console.log(chalk.dim(`Output: ${path.resolve(outputPath)}\n`));
165
+ await showUpdateNotification();
166
+ }
167
+ catch (error) {
168
+ spinner.fail(chalk.red('Failed to render template'));
169
+ const err = error;
170
+ // Check if it's a props parsing error (from parseProps function)
171
+ if (err.message.includes('Failed to parse props') ||
172
+ err.message.includes('Failed to parse JSON props') ||
173
+ err.message.includes('Props file not found')) {
174
+ // Props parsing errors already have helpful messages
175
+ console.log();
176
+ console.log(chalk.red(err.message));
177
+ console.log();
178
+ process.exit(1);
179
+ }
180
+ // Check if it's a Satori error and provide enhanced message
181
+ const enhanced = enhanceSatoriError(err);
182
+ console.log();
183
+ console.log(chalk.red(`Error: ${enhanced.message}`));
184
+ console.log();
185
+ if (enhanced.suggestions.length > 0) {
186
+ console.log(chalk.yellow('Suggestions:'));
187
+ for (const suggestion of enhanced.suggestions) {
188
+ console.log(chalk.dim(` • ${suggestion}`));
189
+ }
190
+ console.log();
191
+ }
192
+ // Show original error for debugging
193
+ if (process.env.DEBUG) {
194
+ console.log(chalk.dim('Original error:'));
195
+ console.error(chalk.dim(err.stack));
196
+ console.log();
197
+ }
198
+ else {
199
+ console.log(chalk.dim('Run with DEBUG=1 for full error stack trace\n'));
200
+ }
201
+ process.exit(1);
202
+ }
203
+ }
204
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function validateCommand(templateName?: string): Promise<void>;
2
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AA0DA,wBAAsB,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0D1E"}
@@ -0,0 +1,107 @@
1
+ import chalk from 'chalk';
2
+ import { getInstalledTemplates, loadTemplateMeta, isTemplateInstalled } from '../lib/utils.js';
3
+ /**
4
+ * Validate a single template's metadata
5
+ */
6
+ function validateMeta(meta, templateName) {
7
+ const errors = [];
8
+ // Check required fields
9
+ if (!meta.name)
10
+ errors.push('Missing required field: name');
11
+ if (!meta.description)
12
+ errors.push('Missing required field: description');
13
+ // Check size
14
+ if (!meta.size) {
15
+ errors.push('Missing required field: size');
16
+ }
17
+ else {
18
+ if (typeof meta.size.width !== 'number')
19
+ errors.push('size.width must be a number');
20
+ if (typeof meta.size.height !== 'number')
21
+ errors.push('size.height must be a number');
22
+ }
23
+ // Check props
24
+ if (!meta.props || typeof meta.props !== 'object') {
25
+ errors.push('Missing or invalid field: props (must be an object)');
26
+ }
27
+ // Check type (optional, defaults to "image")
28
+ const validTypes = ['image', 'video', 'presentation', 'website'];
29
+ if (meta.type && !validTypes.includes(meta.type)) {
30
+ errors.push(`type must be one of: ${validTypes.join(', ')}`);
31
+ }
32
+ // Check type-specific settings
33
+ if (meta.type === 'video' && !meta.video) {
34
+ errors.push('video settings required when type is "video"');
35
+ }
36
+ if (meta.type === 'presentation' && !meta.presentation) {
37
+ errors.push('presentation settings required when type is "presentation"');
38
+ }
39
+ if (meta.type === 'website' && !meta.website) {
40
+ errors.push('website settings required when type is "website"');
41
+ }
42
+ if (meta.video) {
43
+ if (typeof meta.video.fps !== 'number') {
44
+ errors.push('video.fps must be a number');
45
+ }
46
+ if (typeof meta.video.duration !== 'number') {
47
+ errors.push('video.duration must be a number');
48
+ }
49
+ }
50
+ return errors;
51
+ }
52
+ export async function validateCommand(templateName) {
53
+ console.log(chalk.blue('\nValidating templates...\n'));
54
+ try {
55
+ const templatesToValidate = templateName
56
+ ? [templateName]
57
+ : await getInstalledTemplates();
58
+ if (templatesToValidate.length === 0) {
59
+ console.log(chalk.dim('No templates to validate.\n'));
60
+ return;
61
+ }
62
+ let totalErrors = 0;
63
+ let totalTemplates = 0;
64
+ for (const name of templatesToValidate) {
65
+ totalTemplates++;
66
+ // Check if installed
67
+ const installed = await isTemplateInstalled(name);
68
+ if (!installed) {
69
+ console.log(chalk.red(`✗ ${name}: Template not found`));
70
+ totalErrors++;
71
+ continue;
72
+ }
73
+ try {
74
+ const meta = await loadTemplateMeta(name);
75
+ const errors = validateMeta(meta, name);
76
+ if (errors.length === 0) {
77
+ console.log(chalk.green(`✓ ${name}: Valid`));
78
+ }
79
+ else {
80
+ console.log(chalk.red(`✗ ${name}: ${errors.length} error(s)`));
81
+ errors.forEach(err => {
82
+ console.log(chalk.dim(` - ${err}`));
83
+ });
84
+ totalErrors += errors.length;
85
+ }
86
+ }
87
+ catch (error) {
88
+ console.log(chalk.red(`✗ ${name}: Failed to load metadata`));
89
+ console.log(chalk.dim(` - ${error.message}`));
90
+ totalErrors++;
91
+ }
92
+ }
93
+ console.log();
94
+ if (totalErrors === 0) {
95
+ console.log(chalk.green(`All ${totalTemplates} template(s) are valid!\n`));
96
+ }
97
+ else {
98
+ console.log(chalk.yellow(`Found ${totalErrors} error(s) in ${totalTemplates} template(s)\n`));
99
+ process.exit(1);
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error(chalk.red(`\nError: ${error.message}\n`));
104
+ process.exit(1);
105
+ }
106
+ }
107
+ //# sourceMappingURL=validate.js.map