feat(packages): add poster component to video skins (#994)

This commit is contained in:
Sam Potts
2026-03-18 17:57:10 +11:00
committed by GitHub
parent b9bada9567
commit 59bbf6c209
45 changed files with 445 additions and 171 deletions
+39 -5
View File
@@ -1,7 +1,16 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import { confirm, filePath, getChanges, printDiff, srcPath, templatesPath } from './shared.js';
import {
confirm,
filePath,
getChanges,
mirrorTemplatesToSrc,
printDiff,
removeGeneratedSrcFiles,
srcPath,
templatesPath,
} from './shared.js';
const changes = getChanges();
@@ -9,25 +18,39 @@ const changes = getChanges();
const modified = changes.filter((d) => d.state === 'distinct');
// Files only in src (will be deleted)
const added = changes.filter((d) => d.state === 'left');
// Files only in templates (will be recreated in src)
const missing = changes.filter((d) => d.state === 'right');
if (modified.length === 0 && added.length === 0) {
if (modified.length === 0 && added.length === 0 && missing.length === 0) {
console.log(chalk.gray('\nNo local changes. Already in sync with templates.\n'));
process.exit(0);
}
console.log(chalk.bold('\nThe following local changes will be lost:\n'));
if (modified.length > 0 || added.length > 0) {
console.log(chalk.bold('\nThe following changes in src/ will be replaced from templates/:\n'));
}
for (const change of modified) {
const label = filePath(change);
console.log(chalk.yellow(` ~ ${label}`));
printDiff(templatesPath(change), srcPath(change), label);
printDiff(srcPath(change), templatesPath(change), label);
}
for (const change of added) {
console.log(chalk.red(` + ${filePath(change)} (will be deleted)`));
}
console.log();
if (missing.length > 0) {
console.log(chalk.bold('\nThe following template files will be restored into src/:\n'));
for (const change of missing) {
console.log(chalk.green(` + ${filePath(change)} (will be restored)`));
}
}
if (modified.length > 0 || added.length > 0 || missing.length > 0) {
console.log();
}
const ok = await confirm('Reset src/ to templates? This cannot be undone.');
@@ -46,4 +69,15 @@ for (const change of added) {
console.log(chalk.red(` ✔ Deleted ${filePath(change)}`));
}
const restored = await mirrorTemplatesToSrc();
const removedGenerated = await removeGeneratedSrcFiles();
for (const file of restored) {
console.log(chalk.green(` ✔ Restored ${file.replace(/^src\//, '')}`));
}
for (const file of removedGenerated) {
console.log(chalk.green(` ✔ Removed generated ${file.replace(/^src\//, '')}`));
}
console.log(chalk.bold.green('\nReset complete!\n'));
+8 -23
View File
@@ -1,27 +1,12 @@
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
import { mirrorTemplatesToSrc, removeGeneratedSrcFiles } from './shared.js';
const root = resolve(import.meta.dirname, '..');
const templatesDir = resolve(root, 'templates');
const srcDir = resolve(root, 'src');
const created = await mirrorTemplatesToSrc();
const removed = await removeGeneratedSrcFiles();
/** Recursively copy template files into `src/`, preserving relative paths. */
function mirror(dir: string) {
for (const entry of readdirSync(dir)) {
const templatePath = resolve(dir, entry);
const targetPath = resolve(srcDir, templatePath.slice(templatesDir.length + 1));
if (statSync(templatePath).isDirectory()) {
mkdirSync(targetPath, { recursive: true });
mirror(templatePath);
continue;
}
if (!existsSync(targetPath)) {
copyFileSync(templatePath, targetPath);
console.log(`Created ${targetPath.slice(root.length + 1)}`);
}
}
for (const file of created) {
console.log(`Created ${file}`);
}
mirror(templatesDir);
for (const file of removed) {
console.log(`Removed generated ${file}`);
}
+69 -1
View File
@@ -8,6 +8,8 @@ import prompts from 'prompts';
export const SRC = './src';
export const TEMPLATES = './templates';
const IGNORED_ROOT_FILES = new Set(['index.html']);
const GENERATED_SRC_FILES = new Set(['index.html', '__app-shell__.ts']);
export interface Change {
state: 'left' | 'right' | 'distinct';
@@ -25,7 +27,7 @@ export function getChanges(): Change[] {
name: (d.name1 ?? d.name2)!,
relativePath: d.relativePath,
}))
.filter((d) => d.name != null);
.filter((d) => d.name != null && !shouldIgnoreChange(d));
}
function colorizePatch(patch: string): string {
@@ -53,6 +55,21 @@ export function printDiff(oldPath: string, newPath: string, label: string): void
console.log(colorizePatch(body));
}
function isGeneratedSrcFile(change: Change): boolean {
return change.relativePath === '.' && GENERATED_SRC_FILES.has(change.name);
}
function isIgnoredRootFile(change: Change): boolean {
return change.relativePath === '.' && IGNORED_ROOT_FILES.has(change.name);
}
function shouldIgnoreChange(change: Change): boolean {
if (isGeneratedSrcFile(change) || isIgnoredRootFile(change)) return true;
// Sync/reset only manage sandbox directories, not loose files at the root.
return change.relativePath === '.';
}
export function filePath(change: Change): string {
return path.join(change.relativePath, change.name);
}
@@ -65,6 +82,57 @@ export function templatesPath(change: Change): string {
return path.join(TEMPLATES, change.relativePath, change.name);
}
export async function mirrorTemplatesToSrc(): Promise<string[]> {
const created: string[] = [];
async function mirror(dir: string): Promise<void> {
for (const entry of await fs.readdir(dir)) {
const templatePath = path.join(dir, entry);
const relativeFilePath = path.relative(TEMPLATES, templatePath);
const targetPath = path.join(SRC, relativeFilePath);
const stats = await fs.stat(templatePath);
if (stats.isDirectory()) {
await fs.ensureDir(targetPath);
await mirror(templatePath);
continue;
}
if (await fs.pathExists(targetPath)) continue;
await fs.copy(templatePath, targetPath);
created.push(targetPath);
}
}
for (const entry of await fs.readdir(TEMPLATES)) {
const templatePath = path.join(TEMPLATES, entry);
const stats = await fs.stat(templatePath);
if (!stats.isDirectory()) continue;
await fs.ensureDir(path.join(SRC, entry));
await mirror(templatePath);
}
return created;
}
export async function removeGeneratedSrcFiles(): Promise<string[]> {
const removed: string[] = [];
for (const file of GENERATED_SRC_FILES) {
const filePath = path.join(SRC, file);
if (!(await fs.pathExists(filePath))) continue;
await fs.remove(filePath);
removed.push(filePath);
}
return removed;
}
export async function confirm(message: string): Promise<boolean> {
const { ok } = await prompts({
type: 'confirm',