Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions packages/cli/src/commands/init.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { spawnSync } from "node:child_process";
import { existsSync, mkdtempSync, rmSync } from "node:fs";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
Expand All @@ -24,13 +24,32 @@ function runInit(args: string[]): { status: number; stdout: string; stderr: stri
}

describe("hyperframes init flag rename", () => {
it("--example blank scaffolds a bundled project", () => {
it("--example blank scaffolds a bundled project with npm scripts", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-init-test-"));
const target = join(dir, "proj");
try {
const res = runInit([target, "--example", "blank", "--non-interactive", "--skip-skills"]);
expect(res.status).toBe(0);
expect(existsSync(join(target, "index.html"))).toBe(true);
expect(res.stdout).toContain("npm run dev");
expect(res.stdout).toContain("npm run check");
expect(res.stdout).toContain("npm run render");

const pkg = JSON.parse(readFileSync(join(target, "package.json"), "utf-8")) as {
private?: boolean;
type?: string;
scripts?: Record<string, string>;
};
expect(pkg.private).toBe(true);
expect(pkg.type).toBe("module");
expect(pkg.scripts).toMatchObject({
dev: "npx --yes hyperframes preview",
check:
"npx --yes hyperframes lint && npx --yes hyperframes validate && npx --yes hyperframes inspect",
render: "npx --yes hyperframes render",
publish: "npx --yes hyperframes publish",
});
expect(Object.keys(pkg.scripts ?? {}).sort()).toEqual(["check", "dev", "publish", "render"]);
} finally {
rmSync(dir, { recursive: true, force: true });
}
Expand Down
57 changes: 54 additions & 3 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import { fetchRemoteTemplate } from "../templates/remote.js";
import { trackInitTemplate } from "../telemetry/events.js";
import { hasFFmpeg } from "../whisper/manager.js";
import { VERSION } from "../version.js";

interface VideoMeta {
durationSeconds: number;
Expand Down Expand Up @@ -168,6 +169,51 @@ function getSharedTemplateDir(): string {
return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
}

function toPackageName(projectName: string): string {
const normalized = basename(projectName)
.trim()
.toLowerCase()
.replace(/^[._]+/, "")
.replace(/[^a-z0-9._~-]+/g, "-")
.replace(/-+/g, "-")
.replace(/^[-.]+|[-.]+$/g, "");

return normalized || "hyperframes-project";
}

function getHyperframesPackageSpecifier(): string {
return VERSION === "0.0.0-dev" ? "hyperframes" : `hyperframes@${VERSION}`;
}

function hyperframesScript(command: string): string {
return `npx --yes ${getHyperframesPackageSpecifier()} ${command}`;
}

function writeDefaultPackageJson(destDir: string, projectName: string): void {
const packageJsonPath = resolve(destDir, "package.json");
if (existsSync(packageJsonPath)) return;

writeFileSync(
packageJsonPath,
`${JSON.stringify(
{
name: toPackageName(projectName),
private: true,
type: "module",
scripts: {
dev: hyperframesScript("preview"),
check: `${hyperframesScript("lint")} && ${hyperframesScript("validate")} && ${hyperframesScript("inspect")}`,
render: hyperframesScript("render"),
publish: hyperframesScript("publish"),
},
},
null,
2,
)}\n`,
"utf-8",
);
}

function patchVideoSrc(
dir: string,
videoFilename: string | undefined,
Expand Down Expand Up @@ -346,6 +392,8 @@ async function scaffoldProject(
writeProjectConfig(destDir, DEFAULT_PROJECT_CONFIG);
}

writeDefaultPackageJson(destDir, name);

// Copy shared files (CLAUDE.md, AGENTS.md) for AI agent context
const sharedDir = getSharedTemplateDir();
if (existsSync(sharedDir)) {
Expand Down Expand Up @@ -559,10 +607,13 @@ export default defineCommand({
console.log(` ${c.dim("More patterns: hyperframes.heygen.com/guides/prompting")}`);
console.log();
console.log(` ${c.accent("4.")} Preview in the browser:`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npx hyperframes preview")}`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npm run dev")}`);
console.log();
console.log(` ${c.accent("5.")} Check the composition:`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npm run check")}`);
console.log();
console.log(` ${c.accent("5.")} Render to MP4 when ready:`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npx hyperframes render")}`);
console.log(` ${c.accent("6.")} Render to MP4 when ready:`);
console.log(` ${c.accent(`cd ${name}`)} && ${c.accent("npm run render")}`);
console.log();
console.log(` ${c.dim("Full docs: hyperframes.heygen.com")}`);
return;
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/templates/_shared/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ Skills encode patterns like `window.__timelines` registration, `data-*` attribut
## Commands

```bash
npx hyperframes preview # preview in browser (studio editor)
npx hyperframes render # render to MP4
npx hyperframes lint # validate compositions (errors + warnings)
npx hyperframes lint --json # machine-readable output for CI
npm run dev # preview in browser (studio editor)
npm run check # lint + validate + inspect
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes docs <topic> # reference docs in terminal
```

Expand All @@ -30,10 +30,10 @@ npx hyperframes docs <topic> # reference docs in terminal

## Linting — Always Run After Changes

After creating or editing any `.html` composition, run the linter before considering the task complete:
After creating or editing any `.html` composition, run the full check before considering the task complete:

```bash
npx hyperframes lint
npm run check
```

Fix all errors before presenting the result.
Expand Down
13 changes: 7 additions & 6 deletions packages/cli/src/templates/_shared/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
## Commands

```bash
npx hyperframes preview # preview in browser (studio editor)
npx hyperframes render # render to MP4
npx hyperframes lint # validate compositions (errors + warnings)
npm run dev # preview in browser (studio editor)
npm run check # lint + validate + inspect
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
Expand Down Expand Up @@ -56,13 +57,13 @@ https://hyperframes.heygen.com/llms.txt

## Linting — ALWAYS RUN AFTER CHANGES

After creating or editing any `.html` composition, **always** run the linter before considering the task complete:
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:

```bash
npx hyperframes lint
npm run check
```

Fix all errors before presenting the result. Warnings are informational and usually safe to ignore.
Fix all errors before presenting the result. Inspect warnings should be reviewed before rendering.

## Key Rules

Expand Down
Loading