Manage course video publishing workflows by editing metadata, generating descriptions
翻译准备中
AI 摘要 · README
媒体TypeScript · ShellGitHub @mattpocock自行安装运营者未确认
试用过了吗? 点一下就好,无需登录。
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
安装型项目无需部署域名也可验证。个人仓库可通过 GitHub 登录验证,组织仓库可使用上方的仓库验证文件。
正在加载完整 README。 以下为已保存的摘录。
A tool for managing course video publishing workflows — editing metadata, generating descriptions, creating thumbnails, and posting to social platforms.
A Turborepo monorepo over the pnpm workspace:
packages/core has no filesystem access, no child_process and no git
coupling, so it can be deployed as well as run locally. pnpm lint:boundaries
enforces that — anything that needs a machine is injected from apps/local
(see packages/core/services/diagram-thumbnail-store.ts for the shape).查看完整 README ↗A tool for managing course video publishing workflows — editing metadata, generating descriptions, creating thumbnails, and posting to social platforms.
A Turborepo monorepo over the pnpm workspace:
| Directory | What it is |
|---|---|
apps/local | The application as it runs on the author's machine: the React Router app, the Video Editor, the Diagram Playground, the Publish flow, ffmpeg, the recorder service, and the cvm CLI |
packages/core | The domain database — the Drizzle schema, the DrizzleService and every db-* operations service. Every piece of SQL in the repo lives here |
packages/overlay-renderer | The standalone Remotion renderer for every overlay content-kind — subtitles, the CTA, and Definition Cards — with its own toolchain |
packages/core has no filesystem access, no child_process and no git
coupling, so it can be deployed as well as run locally. pnpm lint:boundaries
enforces that — anything that needs a machine is injected from apps/local
(see packages/core/services/diagram-thumbnail-store.ts for the shape).
.env lives at the workspace root: one file for the whole monorepo, which is
also where cvm looks for it (apps/local/app/cli/env.ts).
Run these from the workspace root; Turborepo fans them out and re-runs only what changed.
| Script | Description |
|---|---|
pnpm typecheck | Typecheck every package |
pnpm test | Run every suite once |
pnpm test:watch | Run every suite in watch mode |
pnpm lint:boundaries | Enforce the package boundaries |
pnpm dev | Start the local application |
pnpm build | Build the local application |
Each of these filters out @cvm/overlay-renderer: it ships its own
toolchain (Remotion, and a Chromium download) and has never been part of the
application's checks. Run it with pnpm --filter @cvm/overlay-renderer.
Vercel gets one project per deployable directory, each with its own Root
Directory, and relies on Vercel's built-in unaffected-project skipping to
decide what to deploy. There is deliberately no Ignored Build Step:
turbo-ignore is deprecated, and native skipping does not consume a concurrent
build slot. If a custom step is ever needed it is turbo query affected.
Only main deploys. apps/remote/vercel.json sets
git.deploymentEnabled to { "**": false, "main": true }, so a push to any
other branch builds nothing: no Preview Deployment for an open PR. Production
still deploys on a merge to main, because a branch that matches two rules
deploys if either one is true. The pattern is ** and not *, as * does
not match a slash and a feat/thing branch would still deploy. This is the
supported per-branch control — Vercel's dashboard has no equivalent, and the
repo takes no Ignored Build Step.
Schema changes are managed with drizzle-kit generate / migrate (versioned SQL files), not push. The schema, the migrations and the drizzle config all live in packages/core.
packages/core/db/schema.ts.pnpm db:generate — creates a new numbered .sql file under packages/core/db/migrations/.pnpm db:migrate — run by hand, against DIRECT_DATABASE_URL — before or as part of deploying apps/remote. Applying migrations used to be the deploy's job exclusively; it moved to a manual step because that ran on every Vercel build, previews included, and could land an unmerged migration on the production schema. See apps/remote/README.md and ADR 0026.Against any non-local host, pnpm db:migrate (and drizzle-kit push) refuses unless you are on a clean main at exactly origin/main — an unmerged branch once migrated production out of order. Merge first, then migrate from the main checkout. Local databases are never checked. See the ADR 0026 addendum.
Migrations are additive-only: no dropped or renamed columns without a two-step release. A cvm invocation may be in flight while a deploy lands, and it is the additive rule — not the version gate — that keeps that from breaking. The version gate refuses the box's next command, naming both migration counts and telling it to pull (packages/core/rpc/schema-version.ts).
If the database was originally created via drizzle-kit push and has never run migrations:
pnpm db:baseline
This registers the 0000 baseline migration as already-applied so the next pnpm db:migrate won't replay the initial CREATE TABLE statements.
| Script | Description |
|---|---|
pnpm db:generate | Generate a new migration from schema changes |
pnpm db:migrate | Apply pending migrations by hand (deploy no longer does this) |
pnpm db:baseline | Mark the 0000 baseline as applied (one-time setup) |
pnpm db:studio | Open Drizzle Studio |
The app uses a Dropbox → Zapier → Buffer pipeline to post videos to social media. When you click "Post to Buffer" in the app, it:
dropbox filestatus to poll sync status)| Variable | Description |
|---|---|
ZAPIER_BUFFER_WEBHOOK_URL | The webhook URL generated by your Zapier Zap (see below) |
AI_HERO_BASE_URL | Base URL for the AI Hero instance (e.g. https://www.aihero.dev). Required for AI Hero posting integration. |
ZAPIER_BUFFER_WEBHOOK_URL environment variable in your appThe webhook receives a JSON payload with this shape:
{
"caption": "Your post caption text",
"dropboxFilePath": "/full/path/to/buffer-posts/video.mp4"
}
dropboxFilePath value from the webhook payloadcaption value from the webhook payload