egaki 0.9.0 → 0.10.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/CHANGELOG.md +25 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +39 -0
- package/dist/cli/dev-server.d.ts +30 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +266 -0
- package/dist/cli/dev-server.test.d.ts +2 -0
- package/dist/cli/dev-server.test.d.ts.map +1 -0
- package/dist/cli/dev-server.test.js +95 -0
- package/dist/vite/angled-screen-shader.d.ts +5 -0
- package/dist/vite/angled-screen-shader.d.ts.map +1 -1
- package/dist/vite/angled-screen-shader.js +77 -34
- package/dist/vite/vite-plugin.d.ts.map +1 -1
- package/dist/vite/vite-plugin.js +108 -9
- package/package.json +2 -6
- package/src/cli/cli.ts +50 -0
- package/src/cli/dev-server.test.ts +109 -0
- package/src/cli/dev-server.ts +303 -0
- package/src/vite/angled-screen-shader.tsx +81 -34
- package/src/vite/vite-plugin.ts +117 -9
package/src/vite/vite-plugin.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type { Plugin, PluginOption } from 'vite'
|
|
|
20
20
|
import { spiceflowPlugin } from 'spiceflow/vite'
|
|
21
21
|
import react from '@vitejs/plugin-react'
|
|
22
22
|
import tailwindcss from '@tailwindcss/vite'
|
|
23
|
+
import pc from 'picocolors'
|
|
23
24
|
import { mdxParse } from 'safe-mdx/parse'
|
|
24
25
|
import { collectServerImportSources } from './server-mdx.ts'
|
|
25
26
|
import { parseFrontmatter } from './mdx-parse.ts'
|
|
@@ -44,6 +45,25 @@ const RESOLVED_MODULES = '\0' + VIRTUAL_MODULES
|
|
|
44
45
|
|
|
45
46
|
const PKG_NAME = 'egaki'
|
|
46
47
|
|
|
48
|
+
// Packages that must resolve to a single physical copy (same pattern as
|
|
49
|
+
// spiceflow:dedupe-singleton). Remotion Player + useVideoConfig share React
|
|
50
|
+
// context; two copies → "No video config found". Motion patches need one
|
|
51
|
+
// JSAnimation class. resolve.dedupe alone fails under pnpm when the package
|
|
52
|
+
// is only a transitive dep of egaki (not hoisted to the consumer root).
|
|
53
|
+
const dedupePackages = new Set([
|
|
54
|
+
'remotion',
|
|
55
|
+
'@remotion/player',
|
|
56
|
+
'@remotion/media',
|
|
57
|
+
'@remotion/web-renderer',
|
|
58
|
+
'motion',
|
|
59
|
+
'motion-dom',
|
|
60
|
+
'motion-utils',
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
// Fake importer anchored inside egaki's package so this.resolve() finds the
|
|
64
|
+
// same remotion/motion copy egaki itself depends on.
|
|
65
|
+
const dedupeImporter = path.join(__pkgRoot, '_dedupe_importer_.js')
|
|
66
|
+
|
|
47
67
|
export interface VideoPluginOptions {
|
|
48
68
|
/** Path to the default MDX entry file (relative to vite root or absolute).
|
|
49
69
|
* When omitted, auto-discovers: video.mdx > index.mdx > first .mdx found.
|
|
@@ -258,7 +278,7 @@ export function video(options?: VideoPluginOptions): PluginOption[] {
|
|
|
258
278
|
const fullPath = path.join(dir, entry.name)
|
|
259
279
|
if (entry.isDirectory()) {
|
|
260
280
|
walkDir(fullPath)
|
|
261
|
-
} else if (/\.(tsx?|jsx?|mdx?)$/.test(entry.name) && !/\.(test|spec|config)\./.test(entry.name)) {
|
|
281
|
+
} else if (/\.(tsx?|jsx?|mdx?)$/.test(entry.name) && !/\.(test|spec|config)\./.test(entry.name) && !/\.d\.ts$/.test(entry.name)) {
|
|
262
282
|
// Entry MDX files are NOT skipped: they may be imported as
|
|
263
283
|
// partials by other MDX files (e.g. `import Intro from './intro.mdx'`).
|
|
264
284
|
// Self-import is a user error and harmless (safe-mdx handles it).
|
|
@@ -314,6 +334,52 @@ export function video(options?: VideoPluginOptions): PluginOption[] {
|
|
|
314
334
|
}
|
|
315
335
|
},
|
|
316
336
|
|
|
337
|
+
// Print SDK usage instructions after Vite's startup banner.
|
|
338
|
+
// Agents read terminal output to learn how to interact with the
|
|
339
|
+
// running dev server via Playwriter + window.egakiSDK.
|
|
340
|
+
configureServer(server) {
|
|
341
|
+
server.httpServer?.on('listening', () => {
|
|
342
|
+
const log = server.config.logger
|
|
343
|
+
const p = (s: string) => log.info(s)
|
|
344
|
+
// Small delay so our block prints after Vite's "ready in" + URL lines
|
|
345
|
+
setTimeout(() => {
|
|
346
|
+
const title = ` ${pc.cyan(pc.bold('egaki SDK'))} ${pc.dim('—')} ${pc.white('programmatic control via')} ${pc.yellow('window.egakiSDK')}`
|
|
347
|
+
const sep = pc.dim(' ─'.padEnd(72, '─'))
|
|
348
|
+
|
|
349
|
+
p('')
|
|
350
|
+
p(title)
|
|
351
|
+
p(sep)
|
|
352
|
+
p('')
|
|
353
|
+
p(` ${pc.green('●')} ${pc.bold('Get info')} ${pc.dim('composition metadata, sections, fps')}`)
|
|
354
|
+
p(` ${pc.cyan(`playwriter -s 1 -e 'console.log(await state.page.evaluate(() => window.egakiSDK.getInfo()))'`)}`)
|
|
355
|
+
p('')
|
|
356
|
+
p(` ${pc.green('●')} ${pc.bold('Screenshot')} ${pc.dim('capture a single frame as PNG')}`)
|
|
357
|
+
p(` ${pc.cyan("playwriter -s 1 -e \"$(cat <<'JS'")}`)
|
|
358
|
+
p(` ${pc.cyan('const dataUrl = await state.page.evaluate(() => window.egakiSDK.screenshot({ frame: 60 }))')}`)
|
|
359
|
+
p(` ${pc.cyan('const buf = Buffer.from(await (await fetch(dataUrl)).arrayBuffer())')}`)
|
|
360
|
+
p(` ${pc.cyan('require("node:fs").writeFileSync("/tmp/frame-60.png", buf)')}`)
|
|
361
|
+
p(` ${pc.cyan('JS')}`)
|
|
362
|
+
p(` ${pc.cyan(')"')}`)
|
|
363
|
+
p('')
|
|
364
|
+
p(` ${pc.green('●')} ${pc.bold('Filmstrip')} ${pc.dim('grid of frames across scenes')}`)
|
|
365
|
+
p(` ${pc.cyan("playwriter -s 1 -e \"$(cat <<'JS'")}`)
|
|
366
|
+
p(` ${pc.cyan('const dataUrl = await state.page.evaluate(() =>')}`)
|
|
367
|
+
p(` ${pc.cyan(' window.egakiSDK.filmstrip({ scenes: [0, 1, 2], framesPerScene: 3 }))')}`)
|
|
368
|
+
p(` ${pc.cyan('const buf = Buffer.from(await (await fetch(dataUrl)).arrayBuffer())')}`)
|
|
369
|
+
p(` ${pc.cyan('require("node:fs").writeFileSync("/tmp/filmstrip.png", buf)')}`)
|
|
370
|
+
p(` ${pc.cyan('JS')}`)
|
|
371
|
+
p(` ${pc.cyan(')"')}`)
|
|
372
|
+
p('')
|
|
373
|
+
p(` ${pc.green('●')} ${pc.bold('Export video')} ${pc.dim('render MP4 via WebCodecs')}`)
|
|
374
|
+
p(` ${pc.cyan(`playwriter -s 1 -e 'await state.page.evaluate(() => window.egakiSDK.export({ path: "output.mp4" }))'`)}`)
|
|
375
|
+
p('')
|
|
376
|
+
p(` ${pc.green('●')} ${pc.bold('Seek')} ${pc.dim('jump to a specific frame')}`)
|
|
377
|
+
p(` ${pc.cyan(`playwriter -s 1 -e 'await state.page.evaluate(() => window.egakiSDK.seekTo(120))'`)}`)
|
|
378
|
+
p('')
|
|
379
|
+
}, 50)
|
|
380
|
+
})
|
|
381
|
+
},
|
|
382
|
+
|
|
317
383
|
// HMR for file changes in the project.
|
|
318
384
|
//
|
|
319
385
|
// Entry MDX: the source string flows server → client through the RSC
|
|
@@ -440,16 +506,13 @@ export function video(options?: VideoPluginOptions): PluginOption[] {
|
|
|
440
506
|
arr.push(/^tweakpane/)
|
|
441
507
|
config.resolve.noExternal = arr
|
|
442
508
|
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
// Always applied (not gated by hasMotion) because configEnvironment
|
|
448
|
-
// runs before configResolved where hasMotion is set. Harmless when
|
|
449
|
-
// motion isn't installed — dedupe on missing packages is a no-op.
|
|
509
|
+
// Baseline hint for Vite's built-in resolver (same list as the
|
|
510
|
+
// resolveId singleton plugin below). Real enforcement is the
|
|
511
|
+
// egaki:dedupe-singleton plugin — resolve.dedupe alone is not enough
|
|
512
|
+
// under pnpm when remotion is only a transitive dep of egaki.
|
|
450
513
|
config.resolve.dedupe = mergeUnique(
|
|
451
514
|
config.resolve.dedupe as string[] | undefined,
|
|
452
|
-
[
|
|
515
|
+
[...dedupePackages],
|
|
453
516
|
)
|
|
454
517
|
|
|
455
518
|
if (name === 'client') {
|
|
@@ -458,6 +521,14 @@ export function video(options?: VideoPluginOptions): PluginOption[] {
|
|
|
458
521
|
config.optimizeDeps.exclude,
|
|
459
522
|
[PKG_NAME],
|
|
460
523
|
)
|
|
524
|
+
// Prebundle safe-mdx as a single entry so its pure-CJS leaves
|
|
525
|
+
// (format via fault/micromark) stay internalized with CJS interop.
|
|
526
|
+
// Do NOT list format/fault as separate optimizeDeps entries — that
|
|
527
|
+
// splits them into named-only ESM chunks and the browser crashes with
|
|
528
|
+
// "does not provide an export named 'default'" (same bug Holocron hit
|
|
529
|
+
// when safe-mdx leaked to the client; see holocron vite CHANGELOG).
|
|
530
|
+
// Holocron's clean fix is keep safe-mdx server-only; egaki needs it on
|
|
531
|
+
// the client for MDX-in-browser, so correct prebundling is the fix.
|
|
461
532
|
config.optimizeDeps.include = mergeUnique(
|
|
462
533
|
config.optimizeDeps.include,
|
|
463
534
|
[
|
|
@@ -465,6 +536,8 @@ export function video(options?: VideoPluginOptions): PluginOption[] {
|
|
|
465
536
|
`${PKG_NAME} > remotion`,
|
|
466
537
|
`${PKG_NAME} > @remotion/player`,
|
|
467
538
|
`${PKG_NAME} > safe-mdx`,
|
|
539
|
+
'safe-mdx',
|
|
540
|
+
'safe-mdx/parse',
|
|
468
541
|
],
|
|
469
542
|
)
|
|
470
543
|
}
|
|
@@ -479,9 +552,44 @@ export function video(options?: VideoPluginOptions): PluginOption[] {
|
|
|
479
552
|
},
|
|
480
553
|
}
|
|
481
554
|
|
|
555
|
+
// Force remotion / motion to a single copy across all importers, even when
|
|
556
|
+
// egaki is a transitive or file:-linked dep under pnpm. Mirrors
|
|
557
|
+
// spiceflow:dedupe-singleton (spiceflow/src/vite.tsx).
|
|
558
|
+
//
|
|
559
|
+
// Vite's resolve.dedupe sets basedir=root then walks node_modules/, but
|
|
560
|
+
// pnpm only symlinks direct deps at the root. Transitive deps stay buried
|
|
561
|
+
// in .pnpm/, so dedupe silently falls back to importer-based resolution
|
|
562
|
+
// and Player vs useVideoConfig load different remotion instances.
|
|
563
|
+
const dedupeSingletonPlugin: Plugin = {
|
|
564
|
+
name: 'egaki:dedupe-singleton',
|
|
565
|
+
enforce: 'pre',
|
|
566
|
+
async resolveId(id, _importer, options) {
|
|
567
|
+
if (
|
|
568
|
+
id.startsWith('.') ||
|
|
569
|
+
id.startsWith('/') ||
|
|
570
|
+
id.startsWith('\0') ||
|
|
571
|
+
id.includes('?')
|
|
572
|
+
) {
|
|
573
|
+
return null
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const pkgName = id.startsWith('@')
|
|
577
|
+
? id.split('/').slice(0, 2).join('/')
|
|
578
|
+
: (id.split('/')[0] ?? id)
|
|
579
|
+
|
|
580
|
+
if (!dedupePackages.has(pkgName)) return null
|
|
581
|
+
|
|
582
|
+
return this.resolve(id, dedupeImporter, {
|
|
583
|
+
...options,
|
|
584
|
+
skipSelf: true,
|
|
585
|
+
})
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
|
|
482
589
|
return [
|
|
483
590
|
videoPlugin,
|
|
484
591
|
rscPackagePlugin,
|
|
592
|
+
dedupeSingletonPlugin,
|
|
485
593
|
tailwindcss(),
|
|
486
594
|
spiceflowPlugin({ entry: VIRTUAL_APP }),
|
|
487
595
|
react(),
|