mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 17:30:50 +00:00
* fix(cli): resolve and install transitive registry dependencies `hyperframes add`, `hyperframes new` (fetchRemoteTemplate), and the studio "add block" path each resolved a single registry item and silently dropped any `registryDependencies` it declared. Add `resolveItemWithDependencies` (DFS topological sort, cycle detection, missing-dependency errors, and dedup of shared/diamond deps) and route all three install paths through it so dependencies are installed before the item that needs them. `resolveItem` becomes a thin guard that throws on dep-bearing items, so no future caller can silently reintroduce the drop. `runAdd` now returns the ordered `installed` list and compatibility-gates every dependency before any write. Reworks the stale PR #414 onto current main and addresses its review feedback: fetchRemoteTemplate installs deps, no out-of-scope files, dead null-checks dropped, diamond test added, and the deliberate serial-fetch tradeoff is noted. Co-authored-by: Rakibul Islam <40rakib70@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(cli): make getItem async so missing-dep surfaces as rejection Addresses review nit on #1396: getItem was typed Promise<RegistryItem> but threw synchronously on a missing dependency. Marking it async keeps the control flow consistent with the return type — the throw now becomes a rejection. The body has no await, so the item cache is still populated synchronously on first request and dedup is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): compatibility-gate transitive deps in all install paths Addresses Via's review on #1396: `assertCompatibleOrThrow` only ran inside `runAdd`, so `fetchRemoteTemplate` (hyperframes new) and the Studio "add block" action installed resolved items — now including transitive dependencies — with no minCliVersion enforcement or deprecation warnings. A pre-existing single-item asymmetry that this PR's dep loops amplify across N items. - Add shared `gateRegistryItemsCompatibility` + `RegistryCompatibilityError` to compatibility.ts; all three install paths now gate the full resolved set before any write. `runAdd` keeps its AddError mapping by wrapping the shared gate. - Surface deprecation warnings from the template/studio paths to stderr. - Extract the studio viewport rewrite into `rewriteWrittenToHostViewport` (also drops redundant dynamic node:fs imports) and document that it intentionally rewrites dep-shipped .html too (Via item 3). - Unit-test the shared gate directly (no fetch/cache flakiness): compatible set, accumulated deprecation warnings, and throw-on-incompatible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Rakibul Islam <40rakib70@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
2.7 KiB
TypeScript
69 lines
2.7 KiB
TypeScript
// Compat shim — fetchRemoteTemplate delegates to the registry resolver +
|
|
// installer (packages/cli/src/registry/). Kept so init.ts and external imports
|
|
// that reference this path keep working. Deletable once init.ts is fully
|
|
// ported to call the resolver directly.
|
|
|
|
import { existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { installItem, listRegistryItems, loadAllItems } from "../registry/index.js";
|
|
import { resolveItemWithDependencies } from "../registry/resolver.js";
|
|
import { gateRegistryItemsCompatibility } from "../registry/compatibility.js";
|
|
|
|
// Re-exported for the existing remote.test.ts regression guard. These paths
|
|
// describe the repo layout under the default registry URL; updating them in
|
|
// sync with any future move prevents silent breakage of installed CLIs.
|
|
export const TEMPLATES_DIR = "registry/examples";
|
|
export const MANIFEST_FILENAME = "templates.json";
|
|
|
|
export interface RemoteTemplateInfo {
|
|
id: string;
|
|
label: string;
|
|
hint: string;
|
|
bundled: boolean;
|
|
}
|
|
|
|
/**
|
|
* List available remote templates — kept for backwards compat with external
|
|
* imports. Internally, `resolveTemplateList` in generators.ts is what init.ts
|
|
* uses, and it goes through the registry resolver directly.
|
|
*/
|
|
export async function listRemoteTemplates(): Promise<RemoteTemplateInfo[]> {
|
|
const entries = await listRegistryItems({ type: "hyperframes:example" });
|
|
const items = await loadAllItems(entries);
|
|
return items.map((item) => ({
|
|
id: item.name,
|
|
label: item.title,
|
|
hint: item.description,
|
|
bundled: false,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Download a template into destDir. Delegates to the registry installer.
|
|
*
|
|
* Resolves the template's transitive `registryDependencies` and installs them
|
|
* before the template itself, so a template that depends on other registry
|
|
* items gets a complete install rather than silently dropping its deps.
|
|
*
|
|
* Every resolved item is compatibility-gated up front (same gate as
|
|
* `hyperframes add`), so an incompatible template — or any of its deps —
|
|
* aborts before a single file is written.
|
|
*/
|
|
export async function fetchRemoteTemplate(templateId: string, destDir: string): Promise<void> {
|
|
const items = await resolveItemWithDependencies(templateId);
|
|
const warnings = gateRegistryItemsCompatibility(items);
|
|
for (const warning of warnings) {
|
|
process.stderr.write(`hyperframes:registry ${warning}\n`);
|
|
}
|
|
for (const item of items) {
|
|
await installItem(item, { destDir });
|
|
}
|
|
|
|
// Safety check — an item with no index.html isn't a valid example.
|
|
if (!existsSync(join(destDir, "index.html"))) {
|
|
throw new Error(
|
|
`Example "${templateId}" installed but missing index.html. The registry item may be malformed.`,
|
|
);
|
|
}
|
|
}
|