mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
feat(cli): add version check system with agent-friendly output
- New `updateCheck.ts` utility: cached npm registry check (24h TTL),
sync `getUpdateMeta()` for _meta envelope, `printUpdateNotice()` for
passive stderr banner
- `upgrade --check --json`: machine-readable version check for AI agents
Returns { current, latest, updateAvailable }
- `_meta` envelope on all --json commands (info, lint, benchmark,
compositions): includes version, latestVersion, updateAvailable
- `doctor` shows version check as first row
- Passive update notice on stderr after command completes (skipped in
CI, non-TTY, --json, --quiet)
- Background check fires on startup (non-blocking, populates cache)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { loadProducer } from "../utils/producer.js";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { formatBytes, formatDuration, errorBox } from "../ui/format.js";
|
||||
import * as clack from "@clack/prompts";
|
||||
import { withMeta } from "../utils/updateCheck.js";
|
||||
|
||||
interface BenchmarkConfig {
|
||||
label: string;
|
||||
@@ -151,16 +152,18 @@ export default defineCommand({
|
||||
if (jsonOutput) {
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
results.map((r) => ({
|
||||
config: r.config.label,
|
||||
fps: r.config.fps,
|
||||
quality: r.config.quality,
|
||||
workers: r.config.workers,
|
||||
avgTimeMs: r.avgTime,
|
||||
avgSizeBytes: r.avgSize,
|
||||
failures: r.failures,
|
||||
runs: r.runs,
|
||||
})),
|
||||
withMeta({
|
||||
results: results.map((r) => ({
|
||||
config: r.config.label,
|
||||
fps: r.config.fps,
|
||||
quality: r.config.quality,
|
||||
workers: r.config.workers,
|
||||
avgTimeMs: r.avgTime,
|
||||
avgSizeBytes: r.avgSize,
|
||||
failures: r.failures,
|
||||
runs: r.runs,
|
||||
})),
|
||||
}),
|
||||
null,
|
||||
2,
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { ensureDOMParser } from "../utils/dom.js";
|
||||
import { resolveProject } from "../utils/project.js";
|
||||
import { withMeta } from "../utils/updateCheck.js";
|
||||
|
||||
interface CompositionInfo {
|
||||
id: string;
|
||||
@@ -79,7 +80,7 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (args.json) {
|
||||
console.log(JSON.stringify(compositions, null, 2));
|
||||
console.log(JSON.stringify(withMeta({ compositions }), null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { execSync } from "node:child_process";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { findBrowser } from "../browser/manager.js";
|
||||
import { findFFmpeg } from "../browser/ffmpeg.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { getUpdateMeta } from "../utils/updateCheck.js";
|
||||
|
||||
interface Check {
|
||||
name: string;
|
||||
@@ -84,6 +86,18 @@ function checkDockerRunning(): CheckResult {
|
||||
}
|
||||
}
|
||||
|
||||
function checkVersion(): CheckResult {
|
||||
const meta = getUpdateMeta();
|
||||
if (meta.updateAvailable && meta.latestVersion) {
|
||||
return {
|
||||
ok: false,
|
||||
detail: `${VERSION} \u2192 ${meta.latestVersion} available`,
|
||||
hint: "Run: hyperframes upgrade",
|
||||
};
|
||||
}
|
||||
return { ok: true, detail: `${VERSION} (latest)` };
|
||||
}
|
||||
|
||||
function checkNode(): CheckResult {
|
||||
return { ok: true, detail: `${process.version} (${process.platform} ${process.arch})` };
|
||||
}
|
||||
@@ -97,6 +111,7 @@ export default defineCommand({
|
||||
console.log();
|
||||
|
||||
const checks: Check[] = [
|
||||
{ name: "Version", run: checkVersion },
|
||||
{ name: "Node.js", run: checkNode },
|
||||
{ name: "FFmpeg", run: checkFFmpeg },
|
||||
{ name: "FFprobe", run: checkFFprobe },
|
||||
|
||||
@@ -6,6 +6,7 @@ import { c } from "../ui/colors.js";
|
||||
import { formatBytes, label } from "../ui/format.js";
|
||||
import { ensureDOMParser } from "../utils/dom.js";
|
||||
import { resolveProject } from "../utils/project.js";
|
||||
import { withMeta } from "../utils/updateCheck.js";
|
||||
|
||||
function totalSize(dir: string): number {
|
||||
let total = 0;
|
||||
@@ -52,7 +53,7 @@ export default defineCommand({
|
||||
if (args.json) {
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
withMeta({
|
||||
name: project.name,
|
||||
resolution: parsed.resolution,
|
||||
width: parsed.resolution === "portrait" ? 1080 : 1920,
|
||||
@@ -62,7 +63,7 @@ export default defineCommand({
|
||||
tracks: tracks.size,
|
||||
types: typeCounts,
|
||||
size,
|
||||
},
|
||||
}),
|
||||
null,
|
||||
2,
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
||||
import { lintHyperframeHtml } from "@hyperframes/core/lint";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { resolveProject } from "../utils/project.js";
|
||||
import { withMeta } from "../utils/updateCheck.js";
|
||||
|
||||
export default defineCommand({
|
||||
meta: { name: "lint", description: "Validate a composition for common mistakes" },
|
||||
@@ -16,7 +17,7 @@ export default defineCommand({
|
||||
const result = lintHyperframeHtml(html, { filePath: project.indexPath });
|
||||
|
||||
if (args.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
console.log(JSON.stringify(withMeta(result), null, 2));
|
||||
process.exit(result.ok ? 0 : 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,52 +2,49 @@ import { defineCommand } from "citty";
|
||||
import * as clack from "@clack/prompts";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { checkForUpdate, withMeta } from "../utils/updateCheck.js";
|
||||
|
||||
export default defineCommand({
|
||||
meta: { name: "upgrade", description: "Check for updates and show upgrade instructions" },
|
||||
args: {
|
||||
yes: { type: "boolean", alias: "y", description: "Show upgrade commands without prompting" },
|
||||
check: { type: "boolean", description: "Check for updates and exit (no prompt)" },
|
||||
json: { type: "boolean", description: "Output as JSON", default: false },
|
||||
},
|
||||
async run({ args }) {
|
||||
const autoYes = args.yes === true;
|
||||
const useJson = args.json === true;
|
||||
const checkOnly = args.check === true;
|
||||
|
||||
// JSON mode: always force-check and output structured data
|
||||
if (useJson) {
|
||||
const result = await checkForUpdate(true);
|
||||
console.log(JSON.stringify(withMeta(result), null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const autoYes = args.yes === true;
|
||||
clack.intro(c.bold("hyperframes upgrade"));
|
||||
|
||||
const s = clack.spinner();
|
||||
s.start("Checking for updates...");
|
||||
|
||||
let latest: string;
|
||||
try {
|
||||
const res = await fetch("https://registry.npmjs.org/hyperframes/latest");
|
||||
if (!res.ok) {
|
||||
s.stop("Could not check for updates");
|
||||
clack.outro(c.dim("Package not yet published to npm."));
|
||||
return;
|
||||
}
|
||||
const data = (await res.json()) as { version?: string };
|
||||
latest = data.version ?? VERSION;
|
||||
} catch {
|
||||
s.stop("Could not check for updates");
|
||||
clack.outro(c.dim("Network error. Check your connection."));
|
||||
return;
|
||||
}
|
||||
const result = await checkForUpdate(true);
|
||||
|
||||
if (latest === VERSION) {
|
||||
if (result.latest === result.current) {
|
||||
s.stop(c.success("Already up to date"));
|
||||
clack.outro(`${c.success("◇")} ${c.bold("v" + VERSION)}`);
|
||||
clack.outro(`${c.success("\u25C7")} ${c.bold("v" + VERSION)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
s.stop("Update available");
|
||||
|
||||
console.log();
|
||||
console.log(` ${c.dim("Current:")} ${c.bold("v" + VERSION)}`);
|
||||
console.log(` ${c.dim("Latest:")} ${c.bold(c.accent("v" + latest))}`);
|
||||
console.log(` ${c.dim("Current:")} ${c.bold("v" + result.current)}`);
|
||||
console.log(` ${c.dim("Latest:")} ${c.bold(c.accent("v" + result.latest))}`);
|
||||
console.log();
|
||||
|
||||
if (checkOnly) {
|
||||
clack.outro(c.accent("Update available: v" + latest));
|
||||
clack.outro(c.accent("Update available: v" + result.latest));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,9 +60,9 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(` ${c.accent("npm install -g hyperframes@" + latest)}`);
|
||||
console.log(` ${c.accent("npm install -g hyperframes@" + result.latest)}`);
|
||||
console.log(` ${c.dim("or")}`);
|
||||
console.log(` ${c.accent("npx hyperframes@" + latest + " --version")}`);
|
||||
console.log(` ${c.accent("npx hyperframes@" + result.latest + " --version")}`);
|
||||
console.log();
|
||||
|
||||
clack.outro(c.success("Run one of the commands above to upgrade."));
|
||||
|
||||
Reference in New Issue
Block a user