// Tiny synthetic inputs for the importer tests. Nothing here is real course material. import { existsSync } from "node:fs"; import { mkdir, mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { run } from "../src/util/exec"; // PyMuPDF is needed for PDF tests: set STC_PYTHON, or this machine's venv is used when present. const VENV_PY = "/home/user/.venvs/pdf/bin/python"; export const PYTHON = process.env.STC_PYTHON ?? (existsSync(VENV_PY) ? VENV_PY : "python3"); export async function tmp(prefix = "stc-imp-"): Promise { return mkdtemp(join(tmpdir(), prefix)); } export async function makeVideo(path: string, seconds = 2): Promise { await mkdir(dirname(path), { recursive: true }); await run(["ffmpeg", "-v", "error", "-y", "-f", "lavfi", "-i", `testsrc=duration=${seconds}:size=160x90:rate=10`, "-f", "lavfi", "-i", `sine=frequency=440:duration=${seconds}`, "-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac", "-shortest", path]); } /** Minimal valid PDF with one line of text per page. */ export function pdfBytes(pages: string[]): Uint8Array { const objs: string[] = []; const kids = pages.map((_, i) => `${4 + i * 2} 0 R`).join(" "); objs.push("<< /Type /Catalog /Pages 2 0 R >>"); objs.push(`<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>`); objs.push("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"); pages.forEach((text, i) => { const content = `BT /F1 24 Tf 40 150 Td (${text.replace(/[()\\]/g, "")}) Tj ET`; objs.push(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 320 200] /Resources << /Font << /F1 3 0 R >> >> /Contents ${5 + i * 2} 0 R >>`); objs.push(`<< /Length ${content.length} >>\nstream\n${content}\nendstream`); }); let out = "%PDF-1.4\n"; const offsets: number[] = []; objs.forEach((o, i) => { offsets.push(out.length); out += `${i + 1} 0 obj\n${o}\nendobj\n`; }); const xref = out.length; out += `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n` + offsets.map((o) => `${String(o).padStart(10, "0")} 00000 n \n`).join(""); out += `trailer\n<< /Size ${objs.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`; return new TextEncoder().encode(out); } export async function makePdf(path: string, pages = ["First slide alpha", "Second slide beta"]): Promise { await mkdir(dirname(path), { recursive: true }); await writeFile(path, pdfBytes(pages)); } export async function zipDir(srcDir: string, zipPath: string): Promise { await run([PYTHON, "-I", "-c", "import sys,os,zipfile\nsrc,out=sys.argv[1:3]\nz=zipfile.ZipFile(out,'w',zipfile.ZIP_STORED)\nfor r,d,f in os.walk(src):\n for n in f:\n p=os.path.join(r,n); z.write(p, os.path.relpath(p,src))\nz.close()", srcDir, zipPath]); } const li = (t: string) => `
  • ${t}
  • `; const clipHtml = (s: number, m: number, title: string) => `
  • ${s}.${m} ${title}

    In this video, ${title}, you'll learn it.
    Watch Video
    Download
  • `; export const PACKT_SECTIONS = [ { title: "Intro to Docker", clips: ["Course Introduction", "Docker & Friends"] }, { title: "Images (Sample)", clips: ["Topic Introduction - Images"] }, ]; /** Mimics the real package layout: summary lists without "Video N.M:" prefixes, then chapter blocks. */ export function packtIndexHtml(opts: { dropChapter?: number } = {}): string { const summary = PACKT_SECTIONS.map((s, i) => `
  • Section ${i + 1}: ${s.title}

    Video ${i + 1}.1: ${s.clips[0]}

      ${s.clips.map(li).join("\n")}
    Browse Videos
  • `).join(""); const chapters = PACKT_SECTIONS.map((s, i) => i + 1 === opts.dropChapter ? "" : ` `).join(""); return `Fake

    Fake Docker Course

      ${summary}
    ${chapters}
    `; } export async function makePacktTree(root: string): Promise { await mkdir(join(root, "package", "videos"), { recursive: true }); await writeFile(join(root, "package", "index.html"), packtIndexHtml()); await writeFile(join(root, "Video Package_User Guide.pdf"), "not used"); await makeVideo(join(root, "package/videos/video1_1.mp4"), 2); for (const n of ["video1_2", "video2_1"]) await Bun.write(join(root, "package/videos", `${n}.mp4`), Bun.file(join(root, "package/videos/video1_1.mp4"))); } export async function makePacktZip(dir: string): Promise { const tree = join(dir, "packt-tree"); await makePacktTree(tree); const zip = join(dir, "video_zip.zip"); await zipDir(tree, zip); return zip; } /** Companion repo with folders that have spaces; S01 has a deck and a cheat sheet, S02 has code. */ export async function makeRepoTree(root: string): Promise { const top = join(root, "Course-Repo-main"); await makePdf(join(top, "S01 - Intro to Docker", "Slides_Intro.pdf"), ["Repo deck one", "Repo deck two", "Repo deck three"]); await makePdf(join(top, "S01 - Intro to Docker", "Docker_CheatSheet.pdf"), ["cheats"]); await mkdir(join(top, "S02 - Images (Sample)", "app"), { recursive: true }); await writeFile(join(top, "S02 - Images (Sample)", "app", "main.py"), "print('hi')\n"); await writeFile(join(top, "S02 - Images (Sample)", "Ünïcode notes.txt"), "notes\n"); await mkdir(join(top, "Bonus Extras"), { recursive: true }); await writeFile(join(top, "Bonus Extras", "x.txt"), "x"); await writeFile(join(top, "README.md"), "# repo\n"); return top; } export async function makeRepoZip(dir: string): Promise { const tree = join(dir, "repo-tree"); await makeRepoTree(tree); const zip = join(dir, "repo.zip"); await zipDir(tree, zip); return zip; }