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.
- package/FONTS.md +156 -0
- package/HELPERS_DEMO.md +134 -0
- package/PROJECT_STRUCTURE.md +286 -0
- package/PUBLISHING.md +171 -0
- package/README.md +1020 -0
- package/REGISTRY_SETUP.md +427 -0
- package/SHADCN_INTEGRATION.md +269 -0
- package/TAILWIND.md +228 -0
- package/TEMPLATE_SOURCES.md +363 -0
- package/_dsgn/templates/banner-hero/banner-hero.tsx +57 -0
- package/_dsgn/templates/banner-hero/meta.json +14 -0
- package/_dsgn/templates/composite-card/meta.json +16 -0
- package/_dsgn/templates/composite-card/template.tsx +44 -0
- package/_dsgn/templates/image/meta.json +13 -0
- package/_dsgn/templates/image/template.tsx +28 -0
- package/_dsgn/templates/kitchen-sink/meta.json +13 -0
- package/_dsgn/templates/kitchen-sink/template.tsx +72 -0
- package/_dsgn/templates/qr-card/meta.json +14 -0
- package/_dsgn/templates/qr-card/template.tsx +39 -0
- package/_dsgn/templates/test-parent/child/meta.json +11 -0
- package/_dsgn/templates/test-parent/child/template.tsx +27 -0
- package/_dsgn/templates/test-parent/meta.json +12 -0
- package/_dsgn/templates/test-parent/template.tsx +30 -0
- package/_dsgn/templates/test-sibling/meta.json +11 -0
- package/_dsgn/templates/test-sibling/template.tsx +20 -0
- package/_dsgn/templates/video/.tmp/template-1763421345296.mjs +43 -0
- package/_dsgn/templates/video/.tmp/template-1763421362228.mjs +43 -0
- package/_dsgn/templates/video/.tmp/template-1763421377706.mjs +43 -0
- package/_dsgn/templates/video/meta.json +17 -0
- package/_dsgn/templates/video/template.tsx +48 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +86 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/default.d.ts +2 -0
- package/dist/commands/default.d.ts.map +1 -0
- package/dist/commands/default.js +69 -0
- package/dist/commands/default.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +75 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +83 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/preview.d.ts +3 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +296 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/render.d.ts +10 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +204 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +107 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/default-templates/AGENTS.md +229 -0
- package/dist/default-templates/image/meta.json +13 -0
- package/dist/default-templates/image/template.d.ts +20 -0
- package/dist/default-templates/image/template.d.ts.map +1 -0
- package/dist/default-templates/image/template.js +18 -0
- package/dist/default-templates/image/template.js.map +1 -0
- package/dist/default-templates/image/template.tsx +20 -0
- package/dist/default-templates/image-template/meta.json +13 -0
- package/dist/default-templates/image-template/template.tsx +19 -0
- package/dist/default-templates/kitchen-sink/meta.json +13 -0
- package/dist/default-templates/kitchen-sink/template.tsx +64 -0
- package/dist/default-templates/page/meta.json +17 -0
- package/dist/default-templates/page/template.tsx +37 -0
- package/dist/default-templates/video/meta.json +17 -0
- package/dist/default-templates/video/template.d.ts +26 -0
- package/dist/default-templates/video/template.d.ts.map +1 -0
- package/dist/default-templates/video/template.js +33 -0
- package/dist/default-templates/video/template.js.map +1 -0
- package/dist/default-templates/video/template.tsx +37 -0
- package/dist/default-templates/video-template/meta.json +17 -0
- package/dist/default-templates/video-template/template.tsx +36 -0
- package/dist/default-templates/website/meta.json +16 -0
- package/dist/default-templates/website/pages/home.tsx +17 -0
- package/dist/default-templates/website/parts/footer.tsx +17 -0
- package/dist/default-templates/website/parts/header.tsx +17 -0
- package/dist/default-templates/website/template.tsx +17 -0
- package/dist/default-templates/website-template/meta.json +16 -0
- package/dist/default-templates/website-template/pages/home.tsx +16 -0
- package/dist/default-templates/website-template/parts/footer.tsx +16 -0
- package/dist/default-templates/website-template/parts/header.tsx +16 -0
- package/dist/default-templates/website-template/template.tsx +16 -0
- package/dist/lib/config.d.ts +34 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +248 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +7 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +12 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/helpers.d.ts +29 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +159 -0
- package/dist/lib/helpers.js.map +1 -0
- package/dist/lib/installer.d.ts +51 -0
- package/dist/lib/installer.d.ts.map +1 -0
- package/dist/lib/installer.js +215 -0
- package/dist/lib/installer.js.map +1 -0
- package/dist/lib/renderer.d.ts +51 -0
- package/dist/lib/renderer.d.ts.map +1 -0
- package/dist/lib/renderer.js +524 -0
- package/dist/lib/renderer.js.map +1 -0
- package/dist/lib/tailwind-config-loader.d.ts +47 -0
- package/dist/lib/tailwind-config-loader.d.ts.map +1 -0
- package/dist/lib/tailwind-config-loader.js +432 -0
- package/dist/lib/tailwind-config-loader.js.map +1 -0
- package/dist/lib/tailwind-detector.d.ts +36 -0
- package/dist/lib/tailwind-detector.d.ts.map +1 -0
- package/dist/lib/tailwind-detector.js +156 -0
- package/dist/lib/tailwind-detector.js.map +1 -0
- package/dist/lib/tailwind.d.ts +8 -0
- package/dist/lib/tailwind.d.ts.map +1 -0
- package/dist/lib/tailwind.js +994 -0
- package/dist/lib/tailwind.js.map +1 -0
- package/dist/lib/template-validator.d.ts +22 -0
- package/dist/lib/template-validator.d.ts.map +1 -0
- package/dist/lib/template-validator.js +174 -0
- package/dist/lib/template-validator.js.map +1 -0
- package/dist/lib/utils.d.ts +44 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +207 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/version-check.d.ts +16 -0
- package/dist/lib/version-check.d.ts.map +1 -0
- package/dist/lib/version-check.js +88 -0
- package/dist/lib/version-check.js.map +1 -0
- package/dist/lib/video-renderer.d.ts +32 -0
- package/dist/lib/video-renderer.d.ts.map +1 -0
- package/dist/lib/video-renderer.js +226 -0
- package/dist/lib/video-renderer.js.map +1 -0
- package/dist/sdk/index.d.ts +58 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +119 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/template.d.ts +40 -0
- package/dist/sdk/template.d.ts.map +1 -0
- package/dist/sdk/template.js +60 -0
- package/dist/sdk/template.js.map +1 -0
- package/dist/types/config.d.ts +62 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +47 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/template.d.ts +79 -0
- package/dist/types/template.d.ts.map +1 -0
- package/dist/types/template.js +2 -0
- package/dist/types/template.js.map +1 -0
- package/examples/nextjs-api/README.md +180 -0
- package/examples/nextjs-api/package.json +21 -0
- package/examples/nextjs-api/pages/api/intro-video.ts +53 -0
- package/examples/nextjs-api/pages/api/og-image.ts +50 -0
- package/netlify.toml +13 -0
- package/package.json +84 -0
- package/patches/satori+0.18.3.patch +13 -0
- package/test-templates/TESTS.md +63 -0
- package/test-templates/_dsgn/templates/absolute-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/absolute-spin/template.tsx +16 -0
- package/test-templates/_dsgn/templates/animated-intro/.tmp/template-1763468771640.mjs +7 -0
- package/test-templates/_dsgn/templates/animated-intro/meta.json +10 -0
- package/test-templates/_dsgn/templates/animated-intro/template.tsx +23 -0
- package/test-templates/_dsgn/templates/centered-spin/.tmp/template-1763468525386.mjs +7 -0
- package/test-templates/_dsgn/templates/centered-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/centered-spin/template.tsx +11 -0
- package/test-templates/_dsgn/templates/composite/.tmp/template-1763468815645.mjs +7 -0
- package/test-templates/_dsgn/templates/composite/meta.json +9 -0
- package/test-templates/_dsgn/templates/composite/template.tsx +23 -0
- package/test-templates/_dsgn/templates/easing-test/.tmp/template-1763468824501.mjs +7 -0
- package/test-templates/_dsgn/templates/easing-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/easing-test/template.tsx +47 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466364336.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466584319.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466667797.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466746504.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763466930225.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467004552.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467060334.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467124493.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467174690.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467359134.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467451928.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467758275.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763467985201.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468020563.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468090428.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468211036.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/.tmp/template-1763468394057.mjs +10 -0
- package/test-templates/_dsgn/templates/minimal-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/minimal-spin/template.tsx +13 -0
- package/test-templates/_dsgn/templates/no-origin-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/no-origin-spin/template.tsx +10 -0
- package/test-templates/_dsgn/templates/opacity-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/opacity-test/template.tsx +9 -0
- package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468758954.mjs +17 -0
- package/test-templates/_dsgn/templates/qr-code/.tmp/template-1763468815672.mjs +17 -0
- package/test-templates/_dsgn/templates/qr-code/meta.json +9 -0
- package/test-templates/_dsgn/templates/qr-code/template.tsx +20 -0
- package/test-templates/_dsgn/templates/rotation-abs-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/rotation-abs-test/template.tsx +15 -0
- package/test-templates/_dsgn/templates/rotation-corner/meta.json +7 -0
- package/test-templates/_dsgn/templates/rotation-corner/template.tsx +12 -0
- package/test-templates/_dsgn/templates/rotation-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/rotation-test/template.tsx +12 -0
- package/test-templates/_dsgn/templates/shake-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/shake-test/template.tsx +12 -0
- package/test-templates/_dsgn/templates/static-image/.tmp/template-1763468746271.mjs +7 -0
- package/test-templates/_dsgn/templates/static-image/meta.json +9 -0
- package/test-templates/_dsgn/templates/static-image/template.tsx +19 -0
- package/test-templates/_dsgn/templates/translate-test/meta.json +7 -0
- package/test-templates/_dsgn/templates/translate-test/template.tsx +9 -0
- package/test-templates/_dsgn/templates/video-loops/.tmp/template-1763468793192.mjs +15 -0
- package/test-templates/_dsgn/templates/video-loops/meta.json +9 -0
- package/test-templates/_dsgn/templates/video-loops/template.tsx +39 -0
- package/test-templates/_dsgn/templates/wrapped-spin/meta.json +7 -0
- package/test-templates/_dsgn/templates/wrapped-spin/template.tsx +17 -0
- package/test-templates/compare-svgs.mjs +30 -0
- package/test-templates/convert-frames.mjs +15 -0
- package/test-templates/debug-rotation.mjs +25 -0
- package/test-templates/run-tests.sh +39 -0
- package/test-templates/test-sdk.mjs +115 -0
- package/website/.astro/settings.json +5 -0
- package/website/.astro/types.d.ts +1 -0
- package/website/README.md +112 -0
- package/website/astro.config.mjs +18 -0
- package/website/dist/_astro/fonts.DHdiHGBO.css +1 -0
- package/website/dist/fonts/index.html +193 -0
- package/website/dist/helpers/index.html +166 -0
- package/website/dist/images/index.html +314 -0
- package/website/dist/index.html +219 -0
- package/website/dist/llm.txt +2448 -0
- package/website/dist/styling/index.html +365 -0
- package/website/dist/templates/index.html +124 -0
- package/website/dist/video/index.html +636 -0
- package/website/package-lock.json +7606 -0
- package/website/package.json +23 -0
- 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 @@
|
|
|
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
|