feat(cli): add remote template fetching via giget (#162)

* feat(cli): add remote template fetching via giget

* fix: update remote.ts to use templates/ instead of examples/

* fix(cli): validate template ID against manifest before downloading

Fails fast with available template list instead of downloading
an empty directory for nonexistent templates.

* refactor(cli): simplify to single --template flag with dynamic validation

- Remove --example flag (--template handles bundled + remote)
- Remove static ALL_TEMPLATE_IDS list (validates against GitHub manifest)
- No CLI release needed to add new templates — just add to templates/ and templates.json
- scaffoldProject auto-detects bundled vs remote

* chore: update lockfiles for giget dependency

* fix(cli): remove undefined isAudioOnly reference
This commit is contained in:
James Russo
2026-03-31 13:41:01 -07:00
committed by GitHub
parent b43b6fb1dc
commit b866a28545
6 changed files with 229 additions and 57 deletions
+39 -23
View File
@@ -1,32 +1,48 @@
export type TemplateId =
| "blank"
| "warm-grain"
| "play-mode"
| "swiss-grid"
| "vignelli"
| "decision-tree"
| "kinetic-type"
| "product-promo"
| "nyt-graph";
import { listRemoteTemplates, type RemoteTemplateInfo } from "./remote.js";
export type TemplateSource = "bundled" | "remote";
export interface TemplateOption {
id: TemplateId;
id: string;
label: string;
hint: string;
source: TemplateSource;
}
export const TEMPLATES: TemplateOption[] = [
{ id: "blank", label: "Blank", hint: "Empty composition — just the scaffolding" },
{ id: "warm-grain", label: "Warm Grain", hint: "Cream aesthetic with grain texture" },
{ id: "play-mode", label: "Play Mode", hint: "Playful elastic animations" },
{ id: "swiss-grid", label: "Swiss Grid", hint: "Structured grid layout" },
{ id: "vignelli", label: "Vignelli", hint: "Bold typography with red accents" },
{ id: "decision-tree", label: "Decision Tree", hint: "Animated flowchart with branching paths" },
{ id: "kinetic-type", label: "Kinetic Type", hint: "Bold kinetic typography promo" },
/** Templates bundled in the CLI package (available offline). */
export const BUNDLED_TEMPLATES: TemplateOption[] = [
{
id: "product-promo",
label: "Product Promo",
hint: "Multi-scene product showcase with SVG assets",
id: "blank",
label: "Blank",
hint: "Empty composition — just the scaffolding",
source: "bundled",
},
{ id: "nyt-graph", label: "NYT Graph", hint: "Animated data chart in print editorial style" },
];
/**
* Resolve the full template list by merging bundled and remote templates.
* Fetches templates.json from GitHub (cached 24h). No CLI release needed to add templates.
* If offline, returns only bundled templates.
*/
export async function resolveTemplateList(): Promise<TemplateOption[]> {
const bundled = [...BUNDLED_TEMPLATES];
const bundledIds = new Set(bundled.map((t) => t.id));
let remote: RemoteTemplateInfo[] = [];
try {
remote = await listRemoteTemplates();
} catch {
// Offline — return bundled only
}
const remoteOptions: TemplateOption[] = remote
.filter((r) => !r.bundled && !bundledIds.has(r.id))
.map((r) => ({
id: r.id,
label: r.label,
hint: r.hint,
source: "remote" as const,
}));
return [...bundled, ...remoteOptions];
}