demo-composer 0.1.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/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # demo-composer
2
+
3
+ Generate narrated demo videos from Cucumber BDD tests using Playwright and Gemini AI.
4
+
5
+ demo-composer records your Cucumber scenarios running against a live application, generates AI narration with text-to-speech, and composes polished demo videos — complete with title cards, subtitles, sidebar overlays, cursor effects, and optional background music.
6
+
7
+ ## Prerequisites
8
+
9
+ - **Node.js** >= 18
10
+ - **FFmpeg** with the `sidechaincompress` filter (required for background music ducking)
11
+ ```sh
12
+ brew install ffmpeg
13
+ ```
14
+ - **Google Cloud credentials** configured for Vertex AI (Gemini)
15
+
16
+ ## Quick Start
17
+
18
+ ```sh
19
+ npm install demo-composer
20
+ npx demo-composer
21
+ ```
22
+
23
+ ## Project Conventions
24
+
25
+ demo-composer follows **convention over configuration**. Place your files according to these conventions and everything works out of the box:
26
+
27
+ ```
28
+ project-root/
29
+ ├── demo.json # Optional: override convention defaults
30
+ ├── demo-music.mp3 # Optional: background music (auto-detected)
31
+ ├── features/
32
+ │ ├── login/
33
+ │ │ ├── login.feature # Gherkin feature files
34
+ │ │ └── login.steps.ts # Step definitions
35
+ │ └── checkout/
36
+ │ ├── checkout.feature
37
+ │ └── checkout.steps.ts
38
+ ├── support/
39
+ │ └── world.ts # Optional: extend DemoWorld for project-specific helpers
40
+ └── demos/ # Output directory (auto-created)
41
+ └── login/
42
+ └── login.mp4
43
+ ```
44
+
45
+ ### Feature Files
46
+
47
+ Place Gherkin `.feature` files in `features/**/*.feature`. These are the same files you use for testing — no separate demo features needed.
48
+
49
+ ### Step Definitions
50
+
51
+ Place step definition files as `features/**/*.steps.ts`. Use the `DemoWorld` API in your steps.
52
+
53
+ ### Support Files
54
+
55
+ If you need to extend `DemoWorld` (e.g., to add project-specific helpers or override the World constructor), place a `support/world.ts` file. It will be auto-imported.
56
+
57
+ ## Writing Step Definitions
58
+
59
+ Import `DemoWorld` and use its methods for visual demo actions:
60
+
61
+ ```typescript
62
+ import { Given, When, Then } from '@cucumber/cucumber';
63
+ import type { DemoWorld } from 'demo-composer';
64
+
65
+ When('the user clicks the sign in button', async function (this: DemoWorld) {
66
+ const button = this.page.getByRole('button', { name: 'Sign In' });
67
+ await this.demoClick(button);
68
+ });
69
+
70
+ When('the user enters their email', async function (this: DemoWorld) {
71
+ const input = this.page.getByLabel('Email');
72
+ await this.demoType(input, 'user@example.com');
73
+ });
74
+
75
+ Then('the user should see their profile', async function (this: DemoWorld) {
76
+ const profile = this.page.getByTestId('profile');
77
+ await this.demoVisible(profile);
78
+ });
79
+ ```
80
+
81
+ ### DemoWorld API
82
+
83
+ | Method | Description |
84
+ |--------|-------------|
85
+ | `demoClick(locator, page?)` | Zoom to element, glide cursor, click, zoom back |
86
+ | `demoType(locator, text, page?)` | Zoom to element, click, type with visible keystrokes, zoom back |
87
+ | `demoVisible(locator)` | Highlight element with pulsing glow, zoom in, then reset |
88
+ | `this.page` | The Playwright `Page` instance |
89
+ | `this.context` | The Playwright `BrowserContext` (with video recording) |
90
+ | `this.appBaseUrl` | The application URL (from `APP_BASE_URL` env var) |
91
+
92
+ When recording is disabled (i.e., running as a normal test), `demoClick` performs a plain click, `demoType` clears and types, and `demoVisible` is a no-op. This lets you **reuse the same step definitions for both tests and demo videos** with no code changes.
93
+
94
+ ### Using DemoWorld for Tests
95
+
96
+ `DemoWorld` works in lightweight mode by default — no video recording, no visual effects. Just re-export it as your test World:
97
+
98
+ ```typescript
99
+ // support/world.ts
100
+ export { DemoWorld } from 'demo-composer';
101
+ ```
102
+
103
+ ```typescript
104
+ // support/hooks.ts
105
+ import { Before, After, setWorldConstructor, AfterAll } from '@cucumber/cucumber';
106
+ import { DemoWorld } from './world.ts';
107
+
108
+ setWorldConstructor(DemoWorld);
109
+
110
+ Before(async function (this: DemoWorld) {
111
+ await this.init();
112
+ });
113
+
114
+ After(async function (this: DemoWorld) {
115
+ await this.cleanup();
116
+ });
117
+
118
+ AfterAll(async function () {
119
+ await DemoWorld.closeBrowser();
120
+ });
121
+ ```
122
+
123
+ When you run `npx demo-composer`, the demo hooks automatically enable recording mode, and the same step definitions produce polished demo videos.
124
+
125
+ ### Extending DemoWorld
126
+
127
+ For project-specific helpers, extend `DemoWorld` in `support/world.ts`:
128
+
129
+ ```typescript
130
+ import { DemoWorld } from 'demo-composer';
131
+ import { setWorldConstructor, type IWorldOptions } from '@cucumber/cucumber';
132
+
133
+ class MyWorld extends DemoWorld {
134
+ constructor(options: IWorldOptions) {
135
+ super(options);
136
+ }
137
+
138
+ override async init() {
139
+ await super.init();
140
+ // Project-specific initialization
141
+ }
142
+ }
143
+
144
+ setWorldConstructor(MyWorld);
145
+ ```
146
+
147
+ ## Custom Templates
148
+
149
+ Override the visual design of title cards, subtitles, and overlays by passing `TemplateOverrides` to the programmatic API. To reference the default implementations when writing overrides, import them from `demo-composer/templates`:
150
+
151
+ ```typescript
152
+ import { titleCardHtml } from 'demo-composer/templates';
153
+ ```
154
+
155
+ ## Environment Variables
156
+
157
+ ### Required
158
+
159
+ | Variable | Description |
160
+ |----------|-------------|
161
+ | `APP_BASE_URL` | URL of the running application to record |
162
+ | `GEMINI_API_KEY` | Google Gemini API key (or use Application Default Credentials) |
163
+
164
+ ### Optional
165
+
166
+ | Variable | Description | Default |
167
+ |----------|-------------|---------|
168
+ | `GOOGLE_CLOUD_PROJECT` | Google Cloud project ID | — |
169
+ | `GOOGLE_CLOUD_REGION` | Google Cloud region | `us-central1` |
170
+
171
+ ## Configuration: `demo.json`
172
+
173
+ Place an optional `demo.json` at the project root to override convention defaults. All fields are optional:
174
+
175
+ ```json
176
+ {
177
+ "width": 1920,
178
+ "height": 1080,
179
+ "outputDir": "demos",
180
+ "featuresGlob": "features/**/*.feature",
181
+ "musicFile": "demo-music.mp3",
182
+ "musicVolume": 0.15,
183
+ "musicFadeSeconds": 2,
184
+ "stepDelayMs": 500,
185
+ "stepSettleMs": 500,
186
+ "scenarioEndPauseMs": 2000,
187
+ "ttsModel": "gemini-2.5-pro-tts",
188
+ "ttsVoice": "Kore",
189
+ "ttsStylePreamble": "Read the following in a calm, steady, professional tone...",
190
+ "introLabel": "Demo"
191
+ }
192
+ ```
193
+
194
+ ### Field Reference
195
+
196
+ | Field | Type | Default | Description |
197
+ |-------|------|---------|-------------|
198
+ | `width` | number | `1920` | Video width in pixels |
199
+ | `height` | number | `1080` | Video height in pixels |
200
+ | `outputDir` | string | `"demos"` | Output directory (relative to project root) |
201
+ | `featuresGlob` | string | `"features/**/*.feature"` | Glob pattern for feature files |
202
+ | `musicFile` | string | `"demo-music.mp3"` if present | Path to background music file |
203
+ | `musicVolume` | number | `0.15` | Background music volume (0–1) |
204
+ | `musicFadeSeconds` | number | `2` | Fade in/out duration for music |
205
+ | `stepDelayMs` | number | `500` | Delay before each step (milliseconds) |
206
+ | `stepSettleMs` | number | `500` | Settle time after each step |
207
+ | `scenarioEndPauseMs` | number | `2000` | Pause at end of each scenario |
208
+ | `ttsModel` | string | `"gemini-2.5-pro-tts"` | Gemini TTS model |
209
+ | `ttsVoice` | string | `"Kore"` | TTS voice name |
210
+ | `ttsStylePreamble` | string | *(calm professional tone)* | Preamble text prepended to every TTS request |
211
+ | `introLabel` | string | `"Demo"` | Label shown on the intro title card |
212
+
213
+ ### Config Resolution Order
214
+
215
+ ```
216
+ Built-in conventions → demo.json overrides → Environment variables (secrets/runtime only)
217
+ ```
218
+
219
+ ## Background Music
220
+
221
+ If a file named `demo-music.mp3` exists at the project root, it is automatically used as background music. The music:
222
+
223
+ - Loops for the full video duration
224
+ - Fades in at the start and out at the end
225
+ - Automatically ducks (reduces volume) when narration is playing
226
+
227
+ To use a different file or disable music, set the `musicFile` field in `demo.json`.
228
+
229
+ ## CLI
230
+
231
+ ```sh
232
+ npx demo-composer [options]
233
+ ```
234
+
235
+ | Option | Description |
236
+ |--------|-------------|
237
+ | `--config <path>` | Path to a custom Cucumber config file |
238
+ | `--cwd <path>` | Working directory (default: current directory) |
239
+ | `-h, --help` | Show help |
240
+
241
+ ### Custom Cucumber Config
242
+
243
+ If you provide `--config`, you are responsible for importing demo-composer hooks:
244
+
245
+ ```typescript
246
+ import type { IConfiguration } from '@cucumber/cucumber';
247
+
248
+ export default {
249
+ failFast: true,
250
+ format: ['summary'],
251
+ paths: ['features/**/*.feature'],
252
+ import: ['demo-composer/hooks', 'support/**/*.ts', 'features/**/*.steps.ts'],
253
+ } satisfies Partial<IConfiguration>;
254
+ ```
255
+
256
+ ## How It Works
257
+
258
+ 1. **Discover** feature files via glob pattern
259
+ 2. **Generate narration** text using Gemini AI from the Gherkin scenarios
260
+ 3. **Synthesize speech** for all narration clips using Gemini TTS
261
+ 4. **Record** scenarios running in Playwright with video recording, cursor effects, and dynamic step delays synchronized to narration audio durations
262
+ 5. **Compose** final videos with title cards, subtitle overlays, sidebar progress panels, picture-in-picture popups, and background music with sidechain ducking
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../runner.js';
3
+ const args = process.argv.slice(2);
4
+ if (args.includes('--help') || args.includes('-h')) {
5
+ console.log(`
6
+ demo-composer — Generate narrated demo videos from Cucumber BDD tests
7
+
8
+ Usage: demo-composer [options]
9
+
10
+ Options:
11
+ --config <path> Path to a custom Cucumber config file
12
+ --cwd <path> Working directory (default: current directory)
13
+ -h, --help Show this help message
14
+
15
+ Environment variables (required):
16
+ GEMINI_API_KEY Google Gemini API key
17
+ APP_BASE_URL URL of the running application
18
+
19
+ Environment variables (optional):
20
+ GOOGLE_CLOUD_PROJECT Google Cloud project ID
21
+ GOOGLE_CLOUD_REGION Google Cloud region (default: us-central1)
22
+
23
+ Configuration:
24
+ Place a demo.json file at the project root to override convention defaults.
25
+ See the README for the full schema.
26
+ `);
27
+ process.exit(0);
28
+ }
29
+ let cwd;
30
+ let cucumberConfig;
31
+ for (let i = 0; i < args.length; i++) {
32
+ if (args[i] === '--config' && args[i + 1]) {
33
+ cucumberConfig = args[++i];
34
+ }
35
+ else if (args[i] === '--cwd' && args[i + 1]) {
36
+ cwd = args[++i];
37
+ }
38
+ }
39
+ if (!process.env.APP_BASE_URL) {
40
+ console.error('Error: APP_BASE_URL environment variable is required.');
41
+ process.exit(1);
42
+ }
43
+ run({ cwd, cucumberConfig }).catch((err) => {
44
+ console.error('Demo generation failed:', err);
45
+ process.exit(1);
46
+ });
47
+ //# sourceMappingURL=demo-composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo-composer.js","sourceRoot":"","sources":["../../src/bin/demo-composer.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;CAqBf,CAAC,CAAC;IACC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,GAAuB,CAAC;AAC5B,IAAI,cAAkC,CAAC;AAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACxC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;SAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5C,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,GAAG,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACvC,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ScenarioRecording } from './world.js';
2
+ import type { AudioClip } from './tts.js';
3
+ import type { ScenarioNarration } from './narration.js';
4
+ import { type TemplateOverrides } from './templates.js';
5
+ interface ScenarioCompositionInput {
6
+ recording: ScenarioRecording;
7
+ narration: ScenarioNarration;
8
+ titleAudio: AudioClip;
9
+ stepAudios: AudioClip[];
10
+ }
11
+ export interface MusicConfig {
12
+ filePath: string;
13
+ volume: number;
14
+ fadeSeconds: number;
15
+ }
16
+ export declare function composeFeatureVideo(inputs: ScenarioCompositionInput[], featureName: string, featureDescription: string, introAudio: AudioClip, conclusionAudio: AudioClip, outputDir: string, width: number, height: number, music?: MusicConfig, templates?: TemplateOverrides): Promise<string>;
17
+ export {};