mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 10:46:06 +00:00
feat(cli)!: rename --template to --example (#255)
## What PR 4/17 of the catalog system rollout. **Single clean cut** — the old flag is gone, replaced by `--example`. Alias changes from `-t` to `-e`. Stacks on #254. - Rename `--template` → `--example` (alias `-e`) on `hyperframes init` - Accept `--template` as a recognized-but-errored flag so users get a clear rename hint instead of citty silently ignoring the flag and producing a blank project - Update all user-visible strings that referenced "template" as a user-facing concept in the init flow (picker prompt, step comments, offline-fallback suggestion) - New `init.test.ts` covering both the success case (`--example` scaffolds) and the error case (`--template` exits 1 with rename hint) Design doc: [Hyperframes Catalog System](https://www.notion.so/heygen/Hyperframes-Catalog-System-Design-Plan-341449792c69813f899dcd53b4c0383a). ## ⚠️ Breaking change `--template` is no longer accepted. Example: ```bash # before npx hyperframes init my-video --template warm-grain # after npx hyperframes init my-video --example warm-grain ``` Users who still type the old flag will see: ``` The --template flag was renamed to --example. Example: npx hyperframes init my-video --example warm-grain ``` and the command exits with code 1. This is **user guidance, not backwards compat** — the old flag's behavior is fully gone. ## Docs (bundled per the tracker principle) - `docs/templates.mdx` — every `--template` reference - `docs/quickstart.mdx` — agent-mode and video-mode examples - `docs/packages/cli.mdx` — prose, `--help` flag table, `-e` alias - `packages/cli/src/docs/templates.md` — CLI-embedded help topic - `README.md` and `CONTRIBUTING.md` — not affected (no flag references) User-facing renames of the `templates.mdx` page title, nav entry, and URL route are deferred to PR 11 (catalog discoverability UX) as planned. ## Why 1. **"examples"** matches shadcn + Remotion convention for full-project scaffolds and frees the word "template" for future parameterization work (string templating, placeholder substitution) 2. Once `hyperframes add` lands in PR 5, "template" vs "block" vs "component" would be three subtly different concepts sharing one word — renaming the old one to "example" makes the taxonomy self-explaining ## How - **citty silently ignores unknown flags.** Naively removing `--template` would cause `hyperframes init my-video --template warm-grain` to silently fall through and scaffold a blank project. So `--template` stays declared in the args schema, but its run handler immediately errors with a rename hint and exits 1 - **Internal names unchanged** — `templateId` local variables, `getStaticTemplateDir` function, `BUNDLED_TEMPLATES` constant. They're implementation details; their rename is scheduled for PR 5 when the compat shims in `packages/cli/src/templates/` are fully removed alongside the `init` refactor ## Test plan - [x] `bun run test` in `packages/cli`: **72 passed** (was 70 on #254, +2 new `init.test.ts` cases). Same 4 pre-existing failures unchanged - [x] **New unit tests** in `init.test.ts`: - `--example blank` non-interactive: exits 0, writes `index.html` to the target dir - `--template blank` non-interactive: exits non-zero, stderr contains the rename hint + corrected command line, target dir is **not** created - [x] **Manual smoke:** - `npx hyperframes init /tmp/x --example blank` → "Created /tmp/x/" - `npx hyperframes init /tmp/y --template blank` → "The --template flag was renamed to --example..." exit=1 - [x] `bunx oxfmt --check` + `bunx oxlint` on changed files: clean - [x] Pre-commit typecheck (core + studio): clean ## Incidental fix Resolver test regression from PR 3's simplify follow-up: `loadAllItems`' warning-path test was still spying on `console.warn` after the `onWarn` callback refactor. Now uses the callback directly. ## Stacks on #254 — base branch. When #254 merges, this rebases onto `main`. ## Next in stack PR 5 — `feat(cli): add command + hyperframes.json`. The big UX PR where: - `init.ts` gets fully ported to the new registry resolver - Compat shims in `packages/cli/src/templates/` are removed - Users gain the `add` verb for installing blocks and components into existing projects - `hyperframes.json` project-config file lands 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -3,7 +3,7 @@ import type { Example } from "./_examples.js";
|
||||
|
||||
export const examples: Example[] = [
|
||||
["Create a project with the interactive wizard", "hyperframes init my-video"],
|
||||
["Pick a starter template", "hyperframes init my-video --template warm-grain"],
|
||||
["Pick a starter example", "hyperframes init my-video --example warm-grain"],
|
||||
["Start from an existing video file", "hyperframes init my-video --video clip.mp4"],
|
||||
["Start from an audio file", "hyperframes init my-video --audio track.mp3"],
|
||||
["Non-interactive mode (for CI or AI agents)", "hyperframes init my-video --non-interactive"],
|
||||
@@ -362,10 +362,20 @@ export default defineCommand({
|
||||
},
|
||||
args: {
|
||||
name: { type: "positional", description: "Project name", required: false },
|
||||
example: {
|
||||
type: "string",
|
||||
description: "Example name (e.g. warm-grain, swiss-grid, blank)",
|
||||
alias: "e",
|
||||
},
|
||||
// Accepted-but-errored so users who still type the old flag get a clear
|
||||
// message rather than citty silently ignoring it and producing a blank
|
||||
// project. The actual behavior is gone — this exists purely for the
|
||||
// diagnostic. `hidden` keeps it out of --help output so new users aren't
|
||||
// taught about a flag that's already gone.
|
||||
template: {
|
||||
type: "string",
|
||||
description: "Template name (e.g. warm-grain, swiss-grid, blank)",
|
||||
alias: "t",
|
||||
description: "[renamed] Use --example instead.",
|
||||
hidden: true,
|
||||
},
|
||||
video: {
|
||||
type: "string",
|
||||
@@ -401,7 +411,17 @@ export default defineCommand({
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const templateFlag = args.template;
|
||||
if (args.template !== undefined) {
|
||||
// Quote the value in case it looks flag-like — keeps the suggested
|
||||
// command copy-pasteable.
|
||||
console.error(
|
||||
c.error(
|
||||
`The --template flag was renamed to --example. Example:\n npx hyperframes init ${args.name ?? "my-video"} --example "${args.template}"`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const exampleFlag = args.example;
|
||||
const videoFlag = args.video;
|
||||
const audioFlag = args.audio;
|
||||
const skipTranscribe = args["skip-transcribe"] === true;
|
||||
@@ -415,7 +435,7 @@ export default defineCommand({
|
||||
// Non-interactive mode — all inputs from flags, defaults where missing
|
||||
// -----------------------------------------------------------------------
|
||||
if (!interactive) {
|
||||
const templateId = templateFlag ?? "blank";
|
||||
const templateId = exampleFlag ?? "blank";
|
||||
const name = args.name ?? "my-video";
|
||||
const destDir = resolve(name);
|
||||
|
||||
@@ -496,10 +516,10 @@ export default defineCommand({
|
||||
} catch (err) {
|
||||
console.error(
|
||||
c.error(
|
||||
`Failed to scaffold template "${templateId}": ${err instanceof Error ? err.message : err}`,
|
||||
`Failed to scaffold example "${templateId}": ${err instanceof Error ? err.message : err}`,
|
||||
),
|
||||
);
|
||||
console.error(c.dim("Use --template blank for offline use."));
|
||||
console.error(c.dim("Use --example blank for offline use."));
|
||||
process.exit(1);
|
||||
}
|
||||
trackInitTemplate(templateId);
|
||||
@@ -646,17 +666,17 @@ export default defineCommand({
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Pick template — skip prompt if --template was provided
|
||||
// 3. Pick example — skip prompt if --example was provided
|
||||
let templateId: string;
|
||||
|
||||
if (templateFlag) {
|
||||
templateId = templateFlag;
|
||||
if (exampleFlag) {
|
||||
templateId = exampleFlag;
|
||||
} else {
|
||||
// Resolve full template list (bundled + remote)
|
||||
const allTemplates = await resolveTemplateList();
|
||||
const defaultTemplate = "blank";
|
||||
const templateResult = await clack.select({
|
||||
message: "Pick a template",
|
||||
message: "Pick an example",
|
||||
options: allTemplates.map((t: TemplateOption) => ({
|
||||
value: t.id,
|
||||
label: t.label,
|
||||
@@ -675,7 +695,7 @@ export default defineCommand({
|
||||
const spin = clack.spinner();
|
||||
const isBundled = BUNDLED_TEMPLATES.some((t) => t.id === templateId);
|
||||
if (!isBundled) {
|
||||
spin.start(`Downloading template ${c.accent(templateId)}...`);
|
||||
spin.start(`Downloading example ${c.accent(templateId)}...`);
|
||||
}
|
||||
try {
|
||||
await scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
|
||||
@@ -687,7 +707,7 @@ export default defineCommand({
|
||||
spin.stop(c.error("Download failed"));
|
||||
}
|
||||
clack.log.error(
|
||||
`${err instanceof Error ? err.message : err}\n${c.dim("Use --template blank for offline use.")}`,
|
||||
`${err instanceof Error ? err.message : err}\n${c.dim("Use --example blank for offline use.")}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user