mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
fix(core,cli): address PR review — consistent 403 error shape, cap Retry-After, URL-safe ref split
Rames's inline findings on #2112: - forbiddenError now RETURNS in every branch (BAD_TOKEN no longer throws inside) so the caller's single throw covers all cases — no mixed throw/return contract for a future wrapping caller. - retryAfterMs capped at 60s: a spec-legal Retry-After: 3600 no longer silently blocks the CLI for an hour before RATE_LIMITED. - asset ref gathering extracted to gatherAssetRefs() and made URL-safe: bare fileKey:nodeId tokens comma-split, but a figma URL with commas in its query (multi-select node-id=1:2,3:4) is kept whole. - Documented in SKILL that 429 retry lives in the shared request path, so EVERY read endpoint retries (not just asset) — blast-radius note the reviewer asked for. variables intentionally still retries: its fallback is REQUIRES_ENTERPRISE-only, and a 429 there is transient, not a gate. Tests: retry-cap (3600→60000), non-styles endpoint retry, gatherAssetRefs URL-vs-bare split. client 24, cli asset 11. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1d18b30625
commit
87e2a70f9a
@@ -3,7 +3,12 @@ import { describe, expect, it, afterEach } from "vitest";
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { runAssetImport, runAssetImportMany, type AssetImportDeps } from "./asset.js";
|
||||
import {
|
||||
gatherAssetRefs,
|
||||
runAssetImport,
|
||||
runAssetImportMany,
|
||||
type AssetImportDeps,
|
||||
} from "./asset.js";
|
||||
import type { FigmaClient } from "@hyperframes/core/figma";
|
||||
|
||||
const dirs: string[] = [];
|
||||
@@ -174,6 +179,18 @@ describe("runAssetImport", () => {
|
||||
expect(new Set(results.map((r) => r.record.id)).size).toBe(3);
|
||||
});
|
||||
|
||||
it("gatherAssetRefs splits bare comma-joined ids but keeps URLs whole", () => {
|
||||
// bare tokens comma-split
|
||||
expect(gatherAssetRefs(["KEY:1-2,KEY:3-4"])).toEqual(["KEY:1-2", "KEY:3-4"]);
|
||||
// space-separated positionals preserved
|
||||
expect(gatherAssetRefs(["KEY:1-2", "KEY:3-4"])).toEqual(["KEY:1-2", "KEY:3-4"]);
|
||||
// a URL with a comma in its query is NOT torn apart
|
||||
const url = "https://www.figma.com/design/KEY/F?node-id=1:2,3:4";
|
||||
expect(gatherAssetRefs([url])).toEqual([url]);
|
||||
// mixed: URL stays whole, bare token splits
|
||||
expect(gatherAssetRefs([url, "KEY:5-6,KEY:7-8"])).toEqual([url, "KEY:5-6", "KEY:7-8"]);
|
||||
});
|
||||
|
||||
it("splits comma-joined refs and rejects a cross-file batch", async () => {
|
||||
const dir = scratch();
|
||||
await expect(
|
||||
|
||||
@@ -50,6 +50,20 @@ export interface AssetImportResult {
|
||||
reused: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten CLI positionals into asset refs. Comma-splits bare
|
||||
* `fileKey:nodeId` tokens (so `asset A,B` batches) but leaves URL tokens
|
||||
* whole — a figma URL can carry commas in its query (multi-select
|
||||
* `node-id=1:2,3:4`), and splitting those would tear the URL apart. To batch
|
||||
* URLs, pass them as separate positional args.
|
||||
*/
|
||||
export function gatherAssetRefs(positionals: string[]): string[] {
|
||||
return positionals
|
||||
.flatMap((r) => (/^https?:/i.test(r.trim()) ? [r] : r.split(",")))
|
||||
.map((r) => r.trim())
|
||||
.filter((r) => r.length > 0);
|
||||
}
|
||||
|
||||
function requireNodeRef(refInput: string): { fileKey: string; nodeId: string } {
|
||||
const ref = parseFigmaRef(refInput);
|
||||
if (!ref.nodeId)
|
||||
@@ -271,12 +285,10 @@ export default defineCommand({
|
||||
// named `ref`), so use `_` as the source of truth — reading both would
|
||||
// double-count the first. Split any comma-joined ids, so `asset A B`,
|
||||
// `asset A,B`, and `asset URL1 URL2` all batch into ONE /v1/images call.
|
||||
const positionals =
|
||||
Array.isArray(args._) && args._.length > 0 ? (args._ as string[]) : [args.ref];
|
||||
const refs = positionals
|
||||
.flatMap((r) => String(r).split(","))
|
||||
.map((r) => r.trim())
|
||||
.filter((r) => r.length > 0);
|
||||
const positionals = (
|
||||
Array.isArray(args._) && args._.length > 0 ? (args._ as string[]) : [args.ref]
|
||||
).map(String);
|
||||
const refs = gatherAssetRefs(positionals);
|
||||
const results = await runAssetImportMany(
|
||||
refs,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user