@warpgogol/forge 1.1.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,729 +0,0 @@
1
- schema: forge/stack-profile@1
2
- id: editframe
3
- displayName: Editframe Video
4
- detect:
5
- anyOf:
6
- - editframe.config.*
7
- domain: video
8
- register: creative
9
- terminology:
10
- artifact: composition
11
- artifactPlural: compositions
12
- module: scene
13
- source: composition file
14
- output: render
15
- verify: render-verify
16
- operator: director
17
- devServer:
18
- command: editframe preview
19
- port: 4321
20
- artifacts:
21
- - id: composition
22
- extensions:
23
- - .tsx
24
- - .html
25
- produce:
26
- command: editframe render
27
- output: dist/{composition}.mp4
28
- validate:
29
- command: editframe check
30
- determinism:
31
- hashable: true
32
- inputs:
33
- - compositions/**/*.tsx
34
- - compositions/**/*.html
35
- - assets/**
36
- assets:
37
- dir: assets
38
- types:
39
- - id: video
40
- extensions: [.mp4, .webm]
41
- referencePattern: 'src="([^"]+\.(?:mp4|webm))"'
42
- - id: audio
43
- extensions: [.mp3, .wav]
44
- referencePattern: 'src="([^"]+\.(?:mp3|wav))"'
45
- - id: image
46
- extensions: [.png, .jpg, .jpeg, .webp, .svg]
47
- referencePattern: 'src="([^"]+\.(?:png|jpe?g|webp|svg))"'
48
- release:
49
- target: local
50
- outputDir: release
51
- manifestName: release-manifest.json
52
- prerequisites:
53
- - id: nodejs
54
- name: Node.js 24+
55
- check: node --version
56
- installHint: "https://nodejs.org/en/download/"
57
- severity: error
58
- - id: ffmpeg
59
- name: FFmpeg
60
- check: ffmpeg -version
61
- installHint: "https://ffmpeg.org/download.html"
62
- severity: error
63
- workspaceTypes:
64
- - id: composition
65
- detect:
66
- glob: "*.tsx"
67
- contains: Timegroup
68
- packageJsonDep: "@editframe/react"
69
- skills:
70
- - ef-onboard
71
- - ef-composition-review
72
- - ef-render-verify
73
- agentsMdTemplate: editframe-templates/composition-agents.md
74
- - id: html-composition
75
- detect:
76
- glob: "*.html"
77
- contains: ef-timegroup
78
- packageJsonDep: "@editframe/cli"
79
- skills:
80
- - ef-onboard
81
- - ef-composition-review
82
- - ef-render-verify
83
- agentsMdTemplate: editframe-templates/html-composition-agents.md
84
- invariants:
85
- - id: VIDEO-01
86
- rule: Composition filenames must use kebab-case (lowercase letters, digits, hyphens only)
87
- severity: error
88
- check:
89
- kind: filename-pattern
90
- glob: "compositions/**/*.{tsx,html}"
91
- pattern: "^[a-z0-9-]+\\.(tsx|html)$"
92
- - id: VIDEO-02
93
- rule: Scene durations must use contain mode by default to avoid unexpected cropping
94
- severity: warning
95
- check:
96
- kind: file-contains
97
- glob: "compositions/**/*.{tsx,html}"
98
- pattern: contain
99
- - id: VIDEO-03
100
- rule: All speech audio elements must have corresponding Captions components for accessibility
101
- severity: error
102
- check:
103
- kind: file-contains
104
- glob: "compositions/**/*.{tsx,html}"
105
- pattern: Captions
106
- - id: VIDEO-04
107
- rule: Root Timegroup must declare duration or use mode="contain" or mode="fit"
108
- severity: error
109
- check:
110
- kind: file-contains
111
- glob: "compositions/**/*.{tsx,html}"
112
- pattern: 'Timegroup[^>]*(duration|mode="contain"|mode="fit")[^>]*>|ef-timegroup[^>]*(duration|mode="contain"|mode="fit")[^>]*>'
113
- - id: VIDEO-05
114
- rule: All duration values must be valid CSS time strings (e.g. 5s, 500ms, 2.5s)
115
- severity: error
116
- check:
117
- kind: attribute-pattern
118
- glob: "compositions/**/*.{tsx,html}"
119
- elements:
120
- - ef-timegroup
121
- - Timegroup
122
- attribute: "duration"
123
- pattern: "^\\d+(\\.\\d+)?(s|ms)$"
124
- - id: VIDEO-06
125
- rule: "mode attribute must be one of: sequence, fixed, contain, fit"
126
- severity: error
127
- check:
128
- kind: attribute-pattern
129
- glob: "compositions/**/*.{tsx,html}"
130
- elements:
131
- - ef-timegroup
132
- - Timegroup
133
- attribute: "mode"
134
- pattern: "^(sequence|fixed|contain|fit)$"
135
- - id: VIDEO-07
136
- rule: fps must be a positive integer (default 30)
137
- severity: warning
138
- check:
139
- kind: attribute-pattern
140
- glob: "compositions/**/*.{tsx,html}"
141
- elements:
142
- - ef-timegroup
143
- - Timegroup
144
- attribute: "fps"
145
- pattern: "^[1-9]\\d*$"
146
- - id: VIDEO-08
147
- rule: loop attribute should only be used on the root Timegroup (no effect on render for nested groups)
148
- severity: warning
149
- check:
150
- kind: file-not-contains
151
- glob: "compositions/**/*.{tsx,html}"
152
- negatedPattern: "<(Timegroup|ef-timegroup)[^>]*loop[^>]*>[\\s\\S]*<(Timegroup|ef-timegroup)[^>]*loop"
153
- - id: VIDEO-09
154
- rule: offset values must be valid CSS time strings (e.g. 0.5s, 3s, 500ms)
155
- severity: warning
156
- check:
157
- kind: attribute-pattern
158
- glob: "compositions/**/*.{tsx,html}"
159
- elements:
160
- - ef-timegroup
161
- - Timegroup
162
- attribute: "offset"
163
- pattern: "^\\d+(\\.\\d+)?(s|ms)$"
164
- workspace:
165
- dirs:
166
- - compositions
167
- - packages
168
- - services
169
- files:
170
- - path: pnpm-workspace.yaml
171
- content: |
172
- packages:
173
- - "compositions/*"
174
- - "packages/*"
175
- - "services/*"
176
- allowBuilds:
177
- "@editframe/cli": true
178
- node-av: true
179
- - path: turbo.json
180
- content: |
181
- {
182
- "$schema": "https://turbo.build/schema.json",
183
- "tasks": {
184
- "build": {
185
- "dependsOn": ["^build"],
186
- "outputs": ["dist/**"]
187
- },
188
- "build:check": {
189
- "dependsOn": ["^build:check"]
190
- },
191
- "test": {
192
- "dependsOn": ["^build:check"]
193
- }
194
- }
195
- }
196
- - path: .gitignore
197
- content: |
198
- node_modules
199
- dist
200
- .turbo
201
- .cache
202
- operator-profile.md
203
- - path: .github/workflows/ci.yml
204
- content: |
205
- name: CI
206
-
207
- on:
208
- pull_request:
209
- push:
210
- branches: [main]
211
- workflow_dispatch:
212
-
213
- concurrency:
214
- group: ${{ github.workflow }}-${{ github.ref }}
215
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
216
-
217
- permissions:
218
- contents: read
219
-
220
- jobs:
221
- ci:
222
- runs-on: ubuntu-latest
223
- timeout-minutes: 15
224
- name: Lint, type-check, build, test
225
- env:
226
- TZ: UTC
227
-
228
- steps:
229
- - name: Checkout
230
- uses: actions/checkout@v5
231
-
232
- - name: Enable Corepack
233
- run: corepack enable
234
-
235
- - name: Setup Node.js
236
- uses: actions/setup-node@v5
237
- with:
238
- node-version: "24"
239
- cache: "pnpm"
240
-
241
- - name: Install dependencies
242
- run: pnpm install --frozen-lockfile
243
-
244
- - name: Format check
245
- run: pnpm format:check
246
-
247
- - name: Type check
248
- run: pnpm build:check
249
-
250
- - name: Build
251
- run: pnpm build
252
-
253
- - name: Test
254
- run: pnpm test
255
-
256
- # Windows CI (optional — uncomment if Windows checks are needed):
257
- #
258
- # windows-ci:
259
- # runs-on: windows-latest
260
- # name: Lint, type-check, build, test (Windows)
261
- # env:
262
- # GIT_CONFIG_COUNT: 1
263
- # GIT_CONFIG_KEY_0: core.longpaths
264
- # GIT_CONFIG_VALUE_0: "true"
265
- # steps:
266
- # - name: Checkout
267
- # uses: actions/checkout@v5
268
- # - name: Enable Corepack
269
- # run: corepack enable
270
- # - name: Setup Node.js
271
- # uses: actions/setup-node@v5
272
- # with:
273
- # node-version: "24"
274
- # cache: "pnpm"
275
- # - name: Install dependencies
276
- # run: pnpm install --frozen-lockfile
277
- # - name: Format check
278
- # run: pnpm format:check
279
- # - name: Type check
280
- # run: pnpm build:check
281
- # - name: Build
282
- # run: pnpm build
283
- # - name: Test
284
- # run: pnpm test
285
- #
286
- # The GIT_CONFIG_COUNT env block MUST be set before the checkout step
287
- # so git can handle long file paths during clone on Windows.
288
- # Do not add Windows to the matrix "just in case" — it requires
289
- # separate path-length and platform-specific dependency verification.
290
- - path: package.json
291
- content: |
292
- {
293
- "name": "__PROJECT_NAME__",
294
- "version": "0.1.0",
295
- "private": true,
296
- "type": "module",
297
- "packageManager": "pnpm@11.10.0",
298
- "engines": {
299
- "node": ">=24 <25"
300
- },
301
- "scripts": {
302
- "clean": "node scripts/clean.mjs",
303
- "format": "prettier --write .",
304
- "format:check": "prettier --check .",
305
- "build": "turbo run build",
306
- "build:check": "turbo run build:check",
307
- "test": "turbo run test",
308
- "upgrade-packages": "pnpm -r up"
309
- }
310
- }
311
- - path: .prettierrc.mjs
312
- content: |
313
- /** @type {import("prettier").Config} */
314
- export default {
315
- semi: true,
316
- singleQuote: false,
317
- trailingComma: "all",
318
- printWidth: 100,
319
- tabWidth: 2,
320
- };
321
- - path: scripts/clean.mjs
322
- content: |
323
- import { readdir, rm } from "node:fs/promises";
324
- import { join } from "node:path";
325
- import { fileURLToPath } from "node:url";
326
-
327
- const TARGETS = new Set(["node_modules", ".turbo", "dist", ".cache"]);
328
-
329
- const repoRoot = join(fileURLToPath(import.meta.url), "..", "..");
330
-
331
- async function walk(dir, toDelete) {
332
- const entries = await readdir(dir, { withFileTypes: true });
333
- for (const entry of entries) {
334
- if (!entry.isDirectory()) continue;
335
- const name = entry.name;
336
- const fullPath = join(dir, name);
337
- if (TARGETS.has(name)) {
338
- toDelete.push(fullPath);
339
- continue;
340
- }
341
- await walk(fullPath, toDelete);
342
- }
343
- }
344
-
345
- const toDelete = [];
346
- await walk(repoRoot, toDelete);
347
-
348
- if (toDelete.length === 0) {
349
- console.log("Nothing to clean.");
350
- process.exit(0);
351
- }
352
-
353
- await Promise.all(toDelete.map((p) => rm(p, { recursive: true, force: true })));
354
-
355
- for (const p of toDelete) {
356
- console.log("deleted", p.replace(repoRoot + "/", ""));
357
- }
358
-
359
- console.log(`Cleaned ${toDelete.length} directories.`);
360
- install:
361
- - pnpm add -w -D @editframe/cli @warpgogol/forge turbo prettier
362
- firstWorkspace:
363
- path: compositions/my-first-video
364
- files:
365
- - path: package.json
366
- content: |
367
- {
368
- "name": "my-first-video",
369
- "version": "0.1.0",
370
- "private": true,
371
- "type": "module",
372
- "scripts": {
373
- "preview": "editframe preview",
374
- "render": "editframe render -o dist/my-first-video.mp4",
375
- "check": "editframe check"
376
- }
377
- }
378
- - path: index.html
379
- content: |
380
- <!doctype html>
381
- <html lang="en">
382
- <head>
383
- <meta charset="UTF-8" />
384
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
385
- <title>my-first-video</title>
386
- </head>
387
- <body>
388
- <div id="root"></div>
389
- <script type="module" src="/src/main.tsx"></script>
390
- </body>
391
- </html>
392
- - path: src/main.tsx
393
- content: |
394
- import React from "react";
395
- import { createRoot } from "react-dom/client";
396
- import "@editframe/elements/gui";
397
- import "@editframe/elements/styles.css";
398
- import Composition from "../composition";
399
-
400
- document.documentElement.style.height = "100%";
401
- document.body.style.height = "100%";
402
- document.body.style.margin = "0";
403
- const rootEl = document.getElementById("root");
404
- if (rootEl) rootEl.style.height = "100%";
405
-
406
- const styleOverride = document.createElement("style");
407
- styleOverride.textContent = "ef-workbench { display: grid; }";
408
- document.head.appendChild(styleOverride);
409
-
410
- const root = document.getElementById("root");
411
- if (!root) throw new Error("#root element not found");
412
-
413
- createRoot(root).render(
414
- <React.StrictMode>
415
- <Composition />
416
- </React.StrictMode>,
417
- );
418
- - path: vite.config.ts
419
- content: |
420
- import { defineConfig } from "vite";
421
- import react from "@vitejs/plugin-react";
422
- import { vitePluginEditframe as editframePlugin } from "@editframe/vite-plugin";
423
- import tailwindcss from "@tailwindcss/vite";
424
- import path from "node:path";
425
-
426
- const root = import.meta.dirname;
427
- const cacheRoot = path.join(root, ".ef-cache");
428
-
429
- export default defineConfig({
430
- plugins: [react(), tailwindcss(), editframePlugin({ root, cacheRoot })],
431
- });
432
- - path: tsconfig.json
433
- content: |
434
- {
435
- "compilerOptions": {
436
- "target": "ES2022",
437
- "module": "ESNext",
438
- "moduleResolution": "bundler",
439
- "jsx": "react-jsx",
440
- "strict": true,
441
- "esModuleInterop": true,
442
- "skipLibCheck": true,
443
- "noEmit": true,
444
- "lib": ["ES2022", "DOM", "DOM.Iterable"]
445
- },
446
- "include": ["src", "composition.tsx", "vite.config.ts"]
447
- }
448
- - path: src/index.css
449
- content: |
450
- @import "tailwindcss";
451
- - path: composition.tsx
452
- content: |
453
- /*
454
- Editframe React composition — my-first-video
455
- This file declares a video composition using @editframe/react components.
456
- Run `editframe preview` to preview, `editframe render` to produce output.
457
- */
458
- import { useEffect, useRef } from "react";
459
- import { Configuration, Timegroup, Text, Workbench } from "@editframe/react";
460
-
461
- const EFWorkbench = Workbench as any;
462
- const EFConfiguration = Configuration as any;
463
- const EFTimegroup = Timegroup as any;
464
- const EFText = Text as any;
465
-
466
- export default function Composition() {
467
- const workbenchRef = useRef<HTMLElement>(null);
468
-
469
- useEffect(() => {
470
- const wb = workbenchRef.current;
471
- if (!wb) return;
472
-
473
- const fixReplay = () => {
474
- const config = wb.querySelector("ef-configuration");
475
- const controller = (config as any)?.playback;
476
- if (!controller) return;
477
-
478
- const originalPlay = controller.play.bind(controller);
479
- controller.play = (opts?: any) => {
480
- if (!opts?.from && controller.currentTime >= controller.duration) {
481
- originalPlay({ from: 0, to: opts?.to });
482
- return;
483
- }
484
- originalPlay(opts);
485
- };
486
- };
487
-
488
- fixReplay();
489
- wb.addEventListener("playback-attached", fixReplay);
490
- return () => wb.removeEventListener("playback-attached", fixReplay);
491
- }, []);
492
-
493
- return (
494
- <EFWorkbench ref={workbenchRef} resolution="1920x1080">
495
- <EFConfiguration>
496
- <EFTimegroup
497
- id="root"
498
- duration="10s"
499
- mode="contain"
500
- style={{
501
- width: "1920px",
502
- height: "1080px",
503
- background: "#000",
504
- display: "flex",
505
- alignItems: "center",
506
- justifyContent: "center",
507
- }}
508
- >
509
- <EFText
510
- duration="10s"
511
- style={{
512
- color: "white",
513
- fontSize: "72px",
514
- textAlign: "center",
515
- fontFamily: "ui-sans-serif, system-ui, sans-serif",
516
- opacity: "min(1, var(--ef-progress, 0) * 5)",
517
- transform: "scale(min(1, 0.8 + var(--ef-progress, 0) * 0.2))",
518
- }}
519
- >
520
- Hello, Editframe!
521
- </EFText>
522
- </EFTimegroup>
523
- </EFConfiguration>
524
- </EFWorkbench>
525
- );
526
- }
527
- install:
528
- - pnpm add -D @editframe/react @editframe/elements @editframe/vite-plugin @editframe/dev-server react react-dom typescript @types/react @types/react-dom vite @vitejs/plugin-react tailwindcss @tailwindcss/vite vitest
529
- templates:
530
- - id: react
531
- default: true
532
- firstWorkspace:
533
- path: compositions/my-first-video
534
- files:
535
- - path: package.json
536
- content: |
537
- {
538
- "name": "my-first-video",
539
- "version": "0.1.0",
540
- "private": true,
541
- "type": "module",
542
- "scripts": {
543
- "preview": "editframe preview",
544
- "render": "editframe render -o dist/my-first-video.mp4",
545
- "check": "editframe check"
546
- }
547
- }
548
- - path: index.html
549
- content: |
550
- <!doctype html>
551
- <html lang="en">
552
- <head>
553
- <meta charset="UTF-8" />
554
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
555
- <title>my-first-video</title>
556
- </head>
557
- <body>
558
- <div id="root"></div>
559
- <script type="module" src="/src/main.tsx"></script>
560
- </body>
561
- </html>
562
- - path: src/main.tsx
563
- content: |
564
- import React from "react";
565
- import { createRoot } from "react-dom/client";
566
- import "@editframe/elements/gui";
567
- import "@editframe/elements/styles.css";
568
- import Composition from "../composition";
569
-
570
- document.documentElement.style.height = "100%";
571
- document.body.style.height = "100%";
572
- document.body.style.margin = "0";
573
- const rootEl = document.getElementById("root");
574
- if (rootEl) rootEl.style.height = "100%";
575
-
576
- const styleOverride = document.createElement("style");
577
- styleOverride.textContent = "ef-workbench { display: grid; }";
578
- document.head.appendChild(styleOverride);
579
-
580
- const root = document.getElementById("root");
581
- if (!root) throw new Error("#root element not found");
582
-
583
- createRoot(root).render(
584
- <React.StrictMode>
585
- <Composition />
586
- </React.StrictMode>,
587
- );
588
- - path: vite.config.ts
589
- content: |
590
- import { defineConfig } from "vite";
591
- import react from "@vitejs/plugin-react";
592
- import { vitePluginEditframe as editframePlugin } from "@editframe/vite-plugin";
593
- import tailwindcss from "@tailwindcss/vite";
594
- import path from "node:path";
595
-
596
- const root = import.meta.dirname;
597
- const cacheRoot = path.join(root, ".ef-cache");
598
-
599
- export default defineConfig({
600
- plugins: [react(), tailwindcss(), editframePlugin({ root, cacheRoot })],
601
- });
602
- - path: tsconfig.json
603
- content: |
604
- {
605
- "compilerOptions": {
606
- "target": "ES2022",
607
- "module": "ESNext",
608
- "moduleResolution": "bundler",
609
- "jsx": "react-jsx",
610
- "strict": true,
611
- "esModuleInterop": true,
612
- "skipLibCheck": true,
613
- "noEmit": true,
614
- "lib": ["ES2022", "DOM", "DOM.Iterable"]
615
- },
616
- "include": ["src", "composition.tsx", "vite.config.ts"]
617
- }
618
- - path: src/index.css
619
- content: |
620
- @import "tailwindcss";
621
- - path: composition.tsx
622
- content: |
623
- /*
624
- Editframe React composition — my-first-video
625
- This file declares a video composition using @editframe/react components.
626
- Run `editframe preview` to preview, `editframe render` to produce output.
627
- */
628
- import { useEffect, useRef } from "react";
629
- import { Configuration, Timegroup, Text, Workbench } from "@editframe/react";
630
-
631
- const EFWorkbench = Workbench as any;
632
- const EFConfiguration = Configuration as any;
633
- const EFTimegroup = Timegroup as any;
634
- const EFText = Text as any;
635
-
636
- export default function Composition() {
637
- const workbenchRef = useRef<HTMLElement>(null);
638
-
639
- useEffect(() => {
640
- const wb = workbenchRef.current;
641
- if (!wb) return;
642
-
643
- const fixReplay = () => {
644
- const config = wb.querySelector("ef-configuration");
645
- const controller = (config as any)?.playback;
646
- if (!controller) return;
647
-
648
- const originalPlay = controller.play.bind(controller);
649
- controller.play = (opts?: any) => {
650
- if (!opts?.from && controller.currentTime >= controller.duration) {
651
- originalPlay({ from: 0, to: opts?.to });
652
- return;
653
- }
654
- originalPlay(opts);
655
- };
656
- };
657
-
658
- fixReplay();
659
- wb.addEventListener("playback-attached", fixReplay);
660
- return () => wb.removeEventListener("playback-attached", fixReplay);
661
- }, []);
662
-
663
- return (
664
- <EFWorkbench ref={workbenchRef} resolution="1920x1080">
665
- <EFConfiguration>
666
- <EFTimegroup
667
- id="root"
668
- duration="10s"
669
- mode="contain"
670
- style={{
671
- width: "1920px",
672
- height: "1080px",
673
- background: "#000",
674
- display: "flex",
675
- alignItems: "center",
676
- justifyContent: "center",
677
- }}
678
- >
679
- <EFText
680
- duration="10s"
681
- style={{
682
- color: "white",
683
- fontSize: "72px",
684
- textAlign: "center",
685
- fontFamily: "ui-sans-serif, system-ui, sans-serif",
686
- opacity: "min(1, var(--ef-progress, 0) * 5)",
687
- transform: "scale(min(1, 0.8 + var(--ef-progress, 0) * 0.2))",
688
- }}
689
- >
690
- Hello, Editframe!
691
- </EFText>
692
- </EFTimegroup>
693
- </EFConfiguration>
694
- </EFWorkbench>
695
- );
696
- }
697
- install:
698
- - pnpm add -D @editframe/react @editframe/elements @editframe/vite-plugin @editframe/dev-server react react-dom typescript @types/react @types/react-dom vite @vitejs/plugin-react tailwindcss @tailwindcss/vite vitest
699
- - id: html
700
- firstWorkspace:
701
- path: compositions/my-first-video
702
- files:
703
- - path: package.json
704
- content: |
705
- {
706
- "name": "my-first-video",
707
- "version": "0.1.0",
708
- "private": true,
709
- "type": "module",
710
- "scripts": {
711
- "preview": "editframe preview",
712
- "render": "editframe render -o dist/my-first-video.mp4",
713
- "check": "editframe check"
714
- }
715
- }
716
- - path: index.html
717
- content: |
718
- <!--
719
- Editframe HTML composition — my-first-video
720
- This file declares a video composition using Editframe web components.
721
- Run `editframe preview` to preview, `editframe render` to produce output.
722
- -->
723
- <ef-timeline duration="10s">
724
- <ef-timegroup duration="10s" mode="contain" style="width:1920px;height:1080px;background:#000;display:flex;align-items:center;justify-content:center;">
725
- <ef-text duration="5s" style="color:white;font-size:48px;text-align:center;">Hello, Editframe!</ef-text>
726
- </ef-timegroup>
727
- </ef-timeline>
728
- install:
729
- - pnpm add -D vitest