@vibeo/cli 0.1.0
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/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +173 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +101 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/preview.d.ts +5 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +74 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/render.d.ts +5 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +187 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
- package/src/commands/create.ts +197 -0
- package/src/commands/list.ts +136 -0
- package/src/commands/preview.ts +88 -0
- package/src/commands/render.ts +206 -0
- package/src/index.ts +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { renderCommand } from "./commands/render.js";
|
|
3
|
+
import { previewCommand } from "./commands/preview.js";
|
|
4
|
+
import { listCommand } from "./commands/list.js";
|
|
5
|
+
import { createCommand } from "./commands/create.js";
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
8
|
+
function printUsage() {
|
|
9
|
+
console.log(`
|
|
10
|
+
vibeo - React video framework CLI
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
vibeo <command> [options]
|
|
14
|
+
|
|
15
|
+
Commands:
|
|
16
|
+
create Create a new project from a template
|
|
17
|
+
render Render a composition to video
|
|
18
|
+
preview Start a dev server with live preview
|
|
19
|
+
list List registered compositions
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--help Show help for a command
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
vibeo create my-video
|
|
26
|
+
vibeo create music-viz --template audio-reactive
|
|
27
|
+
vibeo render --entry src/index.tsx --composition MyComp --output out.mp4
|
|
28
|
+
vibeo preview --entry src/index.tsx
|
|
29
|
+
vibeo list --entry src/index.tsx
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
async function main() {
|
|
33
|
+
if (!command || command === "--help" || command === "-h") {
|
|
34
|
+
printUsage();
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
switch (command) {
|
|
38
|
+
case "create":
|
|
39
|
+
await createCommand(args.slice(1));
|
|
40
|
+
break;
|
|
41
|
+
case "render":
|
|
42
|
+
await renderCommand(args.slice(1));
|
|
43
|
+
break;
|
|
44
|
+
case "preview":
|
|
45
|
+
await previewCommand(args.slice(1));
|
|
46
|
+
break;
|
|
47
|
+
case "list":
|
|
48
|
+
await listCommand(args.slice(1));
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
console.error(`Unknown command: ${command}`);
|
|
52
|
+
printUsage();
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
main().catch((err) => {
|
|
57
|
+
console.error(err);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;CAqBb,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACzD,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,MAAM,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM;QACR,KAAK,SAAS;YACZ,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;YAC7C,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibeo/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vibeo": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@vibeo/core": "0.1.0",
|
|
18
|
+
"@vibeo/renderer": "0.1.0",
|
|
19
|
+
"@vibeo/player": "0.1.0",
|
|
20
|
+
"@vibeo/audio": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.build.json",
|
|
28
|
+
"clean": "rm -rf dist"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/patcito/vibeo.git",
|
|
33
|
+
"directory": "packages/cli"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { resolve, join, basename } from "node:path";
|
|
2
|
+
import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
const TEMPLATES: Record<string, { description: string; example: string }> = {
|
|
6
|
+
basic: {
|
|
7
|
+
description: "Minimal composition with text animation and two scenes",
|
|
8
|
+
example: "basic-composition.tsx",
|
|
9
|
+
},
|
|
10
|
+
"audio-reactive": {
|
|
11
|
+
description: "Audio visualization with frequency bars and amplitude-driven effects",
|
|
12
|
+
example: "audio-reactive-viz.tsx",
|
|
13
|
+
},
|
|
14
|
+
transitions: {
|
|
15
|
+
description: "Scene transitions (fade, slide) between multiple scenes",
|
|
16
|
+
example: "transition-demo.tsx",
|
|
17
|
+
},
|
|
18
|
+
subtitles: {
|
|
19
|
+
description: "Video with SRT subtitle overlay",
|
|
20
|
+
example: "subtitle-overlay.tsx",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
interface CreateArgs {
|
|
25
|
+
name: string;
|
|
26
|
+
template: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseArgs(args: string[]): CreateArgs {
|
|
30
|
+
const result: CreateArgs = { name: "", template: "basic" };
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const arg = args[i]!;
|
|
34
|
+
const next = args[i + 1];
|
|
35
|
+
|
|
36
|
+
if (arg === "--template" && next) {
|
|
37
|
+
result.template = next;
|
|
38
|
+
i++;
|
|
39
|
+
} else if (arg.startsWith("--template=")) {
|
|
40
|
+
result.template = arg.slice("--template=".length);
|
|
41
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
42
|
+
printHelp();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
} else if (!arg.startsWith("-") && !result.name) {
|
|
45
|
+
result.name = arg;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function printHelp(): void {
|
|
53
|
+
console.log(`
|
|
54
|
+
vibeo create - Create a new Vibeo project
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
vibeo create <project-name> [options]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--template <name> Template to use (default: basic)
|
|
61
|
+
--help Show this help
|
|
62
|
+
|
|
63
|
+
Templates:`);
|
|
64
|
+
|
|
65
|
+
for (const [name, { description }] of Object.entries(TEMPLATES)) {
|
|
66
|
+
console.log(` ${name.padEnd(18)} ${description}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`
|
|
70
|
+
Examples:
|
|
71
|
+
vibeo create my-video
|
|
72
|
+
vibeo create music-viz --template audio-reactive
|
|
73
|
+
vibeo create intro --template transitions
|
|
74
|
+
`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Find the examples directory relative to the CLI package
|
|
78
|
+
function findExamplesDir(): string {
|
|
79
|
+
// Walk up from this file to find the repo root with examples/
|
|
80
|
+
let dir = import.meta.dir;
|
|
81
|
+
for (let i = 0; i < 6; i++) {
|
|
82
|
+
const candidate = join(dir, "examples");
|
|
83
|
+
if (existsSync(candidate)) return candidate;
|
|
84
|
+
dir = resolve(dir, "..");
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Could not find examples directory");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function createCommand(args: string[]): Promise<void> {
|
|
90
|
+
const parsed = parseArgs(args);
|
|
91
|
+
|
|
92
|
+
if (!parsed.name) {
|
|
93
|
+
console.error("Error: project name is required\n");
|
|
94
|
+
printHelp();
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const template = TEMPLATES[parsed.template];
|
|
99
|
+
if (!template) {
|
|
100
|
+
console.error(`Error: unknown template "${parsed.template}"`);
|
|
101
|
+
console.error(`Available: ${Object.keys(TEMPLATES).join(", ")}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const projectDir = resolve(parsed.name);
|
|
106
|
+
if (existsSync(projectDir)) {
|
|
107
|
+
console.error(`Error: directory "${parsed.name}" already exists`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`\nCreating Vibeo project: ${parsed.name}`);
|
|
112
|
+
console.log(`Template: ${parsed.template}\n`);
|
|
113
|
+
|
|
114
|
+
// Create project structure
|
|
115
|
+
await mkdir(join(projectDir, "src"), { recursive: true });
|
|
116
|
+
await mkdir(join(projectDir, "public"), { recursive: true });
|
|
117
|
+
|
|
118
|
+
// Copy example as src/index.tsx
|
|
119
|
+
const examplesDir = findExamplesDir();
|
|
120
|
+
const exampleSrc = await readFile(join(examplesDir, template.example), "utf-8");
|
|
121
|
+
await writeFile(join(projectDir, "src", "index.tsx"), exampleSrc);
|
|
122
|
+
|
|
123
|
+
// Write package.json
|
|
124
|
+
const pkg = {
|
|
125
|
+
name: parsed.name,
|
|
126
|
+
version: "0.0.1",
|
|
127
|
+
private: true,
|
|
128
|
+
type: "module",
|
|
129
|
+
scripts: {
|
|
130
|
+
dev: "vibeo preview --entry src/index.tsx",
|
|
131
|
+
build: "vibeo render --entry src/index.tsx",
|
|
132
|
+
list: "vibeo list --entry src/index.tsx",
|
|
133
|
+
typecheck: "bunx tsc --noEmit",
|
|
134
|
+
},
|
|
135
|
+
dependencies: {
|
|
136
|
+
"@vibeo/core": "workspace:*",
|
|
137
|
+
"@vibeo/audio": "workspace:*",
|
|
138
|
+
"@vibeo/effects": "workspace:*",
|
|
139
|
+
"@vibeo/extras": "workspace:*",
|
|
140
|
+
"@vibeo/player": "workspace:*",
|
|
141
|
+
"@vibeo/renderer": "workspace:*",
|
|
142
|
+
"@vibeo/cli": "workspace:*",
|
|
143
|
+
react: "^19.0.0",
|
|
144
|
+
"react-dom": "^19.0.0",
|
|
145
|
+
},
|
|
146
|
+
devDependencies: {
|
|
147
|
+
"@types/react": "^19.0.0",
|
|
148
|
+
typescript: "^5.0.0",
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
await writeFile(join(projectDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
152
|
+
|
|
153
|
+
// Write tsconfig.json
|
|
154
|
+
const tsconfig = {
|
|
155
|
+
compilerOptions: {
|
|
156
|
+
target: "ES2022",
|
|
157
|
+
module: "ESNext",
|
|
158
|
+
moduleResolution: "bundler",
|
|
159
|
+
jsx: "react-jsx",
|
|
160
|
+
strict: true,
|
|
161
|
+
esModuleInterop: true,
|
|
162
|
+
skipLibCheck: true,
|
|
163
|
+
outDir: "dist",
|
|
164
|
+
declaration: true,
|
|
165
|
+
declarationMap: true,
|
|
166
|
+
sourceMap: true,
|
|
167
|
+
},
|
|
168
|
+
include: ["src"],
|
|
169
|
+
};
|
|
170
|
+
await writeFile(join(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
171
|
+
|
|
172
|
+
// Write .gitignore
|
|
173
|
+
await writeFile(
|
|
174
|
+
join(projectDir, ".gitignore"),
|
|
175
|
+
`node_modules/
|
|
176
|
+
dist/
|
|
177
|
+
out/
|
|
178
|
+
*.tmp
|
|
179
|
+
.DS_Store
|
|
180
|
+
`,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
console.log(` Created ${parsed.name}/`);
|
|
184
|
+
console.log(` ├── src/index.tsx`);
|
|
185
|
+
console.log(` ├── public/`);
|
|
186
|
+
console.log(` ├── package.json`);
|
|
187
|
+
console.log(` ├── tsconfig.json`);
|
|
188
|
+
console.log(` └── .gitignore`);
|
|
189
|
+
|
|
190
|
+
console.log(`
|
|
191
|
+
Next steps:
|
|
192
|
+
cd ${parsed.name}
|
|
193
|
+
bun install
|
|
194
|
+
bun run dev # preview in browser
|
|
195
|
+
bun run build # render to video
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { bundle } from "@vibeo/renderer";
|
|
3
|
+
import { launchBrowser, createPage, closeBrowser } from "@vibeo/renderer";
|
|
4
|
+
|
|
5
|
+
interface ListArgs {
|
|
6
|
+
entry: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function parseArgs(args: string[]): ListArgs {
|
|
10
|
+
const result: ListArgs = {
|
|
11
|
+
entry: "",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = args[i]!;
|
|
16
|
+
const next = args[i + 1];
|
|
17
|
+
|
|
18
|
+
if (arg === "--entry" && next) {
|
|
19
|
+
result.entry = next;
|
|
20
|
+
i++;
|
|
21
|
+
} else if (arg.startsWith("--entry=")) {
|
|
22
|
+
result.entry = arg.slice("--entry=".length);
|
|
23
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
24
|
+
printHelp();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function printHelp(): void {
|
|
33
|
+
console.log(`
|
|
34
|
+
vibeo list - List registered compositions
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
vibeo list --entry <path>
|
|
38
|
+
|
|
39
|
+
Required:
|
|
40
|
+
--entry <path> Path to the root file with compositions
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--help Show this help
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface CompositionMeta {
|
|
48
|
+
id: string;
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
fps: number;
|
|
52
|
+
durationInFrames: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Bundle the entry, launch a browser to evaluate it,
|
|
57
|
+
* extract registered compositions, and print a table.
|
|
58
|
+
*/
|
|
59
|
+
export async function listCommand(args: string[]): Promise<void> {
|
|
60
|
+
const parsed = parseArgs(args);
|
|
61
|
+
|
|
62
|
+
if (!parsed.entry) {
|
|
63
|
+
console.error("Error: --entry is required");
|
|
64
|
+
printHelp();
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const entry = resolve(parsed.entry);
|
|
69
|
+
|
|
70
|
+
console.log(`Bundling ${entry}...`);
|
|
71
|
+
const bundleResult = await bundle(entry);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const browser = await launchBrowser();
|
|
75
|
+
const page = await createPage(browser, 1920, 1080);
|
|
76
|
+
|
|
77
|
+
await page.goto(bundleResult.url, { waitUntil: "networkidle" });
|
|
78
|
+
|
|
79
|
+
// Wait briefly for React to register compositions
|
|
80
|
+
await page.waitForTimeout(2000);
|
|
81
|
+
|
|
82
|
+
// Extract composition data from the page
|
|
83
|
+
const compositions = await page.evaluate(() => {
|
|
84
|
+
const win = window as typeof window & {
|
|
85
|
+
vibeo_getCompositions?: () => CompositionMeta[];
|
|
86
|
+
};
|
|
87
|
+
if (typeof win.vibeo_getCompositions === "function") {
|
|
88
|
+
return win.vibeo_getCompositions();
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await page.close();
|
|
94
|
+
await closeBrowser();
|
|
95
|
+
|
|
96
|
+
if (compositions.length === 0) {
|
|
97
|
+
console.log("\nNo compositions found.");
|
|
98
|
+
console.log(
|
|
99
|
+
"Make sure your entry file exports compositions via <Composition /> components.",
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Print table
|
|
105
|
+
console.log("\nRegistered compositions:\n");
|
|
106
|
+
console.log(
|
|
107
|
+
padRight("ID", 25) +
|
|
108
|
+
padRight("Width", 8) +
|
|
109
|
+
padRight("Height", 8) +
|
|
110
|
+
padRight("FPS", 6) +
|
|
111
|
+
padRight("Frames", 8) +
|
|
112
|
+
"Duration",
|
|
113
|
+
);
|
|
114
|
+
console.log("-".repeat(70));
|
|
115
|
+
|
|
116
|
+
for (const comp of compositions) {
|
|
117
|
+
const duration = (comp.durationInFrames / comp.fps).toFixed(1) + "s";
|
|
118
|
+
console.log(
|
|
119
|
+
padRight(comp.id, 25) +
|
|
120
|
+
padRight(String(comp.width), 8) +
|
|
121
|
+
padRight(String(comp.height), 8) +
|
|
122
|
+
padRight(String(comp.fps), 6) +
|
|
123
|
+
padRight(String(comp.durationInFrames), 8) +
|
|
124
|
+
duration,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log();
|
|
129
|
+
} finally {
|
|
130
|
+
await bundleResult.cleanup();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function padRight(str: string, len: number): string {
|
|
135
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
136
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { bundle } from "@vibeo/renderer";
|
|
3
|
+
|
|
4
|
+
interface PreviewArgs {
|
|
5
|
+
entry: string;
|
|
6
|
+
port: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function parseArgs(args: string[]): PreviewArgs {
|
|
10
|
+
const result: PreviewArgs = {
|
|
11
|
+
entry: "",
|
|
12
|
+
port: 3000,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < args.length; i++) {
|
|
16
|
+
const arg = args[i]!;
|
|
17
|
+
const next = args[i + 1];
|
|
18
|
+
|
|
19
|
+
if (arg === "--entry" && next) {
|
|
20
|
+
result.entry = next;
|
|
21
|
+
i++;
|
|
22
|
+
} else if (arg.startsWith("--entry=")) {
|
|
23
|
+
result.entry = arg.slice("--entry=".length);
|
|
24
|
+
} else if (arg === "--port" && next) {
|
|
25
|
+
result.port = parseInt(next, 10);
|
|
26
|
+
i++;
|
|
27
|
+
} else if (arg.startsWith("--port=")) {
|
|
28
|
+
result.port = parseInt(arg.slice("--port=".length), 10);
|
|
29
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
30
|
+
printHelp();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printHelp(): void {
|
|
39
|
+
console.log(`
|
|
40
|
+
vibeo preview - Start a dev server with live preview
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
vibeo preview --entry <path> [options]
|
|
44
|
+
|
|
45
|
+
Required:
|
|
46
|
+
--entry <path> Path to the root file with compositions
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--port <number> Port for the dev server (default: 3000)
|
|
50
|
+
--help Show this help
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Start a dev server hosting the Player with hot reload.
|
|
56
|
+
*/
|
|
57
|
+
export async function previewCommand(args: string[]): Promise<void> {
|
|
58
|
+
const parsed = parseArgs(args);
|
|
59
|
+
|
|
60
|
+
if (!parsed.entry) {
|
|
61
|
+
console.error("Error: --entry is required");
|
|
62
|
+
printHelp();
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const entry = resolve(parsed.entry);
|
|
67
|
+
|
|
68
|
+
console.log(`Starting preview server...`);
|
|
69
|
+
console.log(` Entry: ${entry}`);
|
|
70
|
+
|
|
71
|
+
const bundleResult = await bundle(entry);
|
|
72
|
+
|
|
73
|
+
console.log(`\n Preview running at ${bundleResult.url}`);
|
|
74
|
+
console.log(` Press Ctrl+C to stop\n`);
|
|
75
|
+
|
|
76
|
+
// Keep the process alive until interrupted
|
|
77
|
+
const shutdown = async () => {
|
|
78
|
+
console.log("\nShutting down preview server...");
|
|
79
|
+
await bundleResult.cleanup();
|
|
80
|
+
process.exit(0);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
process.on("SIGINT", shutdown);
|
|
84
|
+
process.on("SIGTERM", shutdown);
|
|
85
|
+
|
|
86
|
+
// Block forever
|
|
87
|
+
await new Promise(() => {});
|
|
88
|
+
}
|