mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
Both flags were silently broken via the same root cause: citty parses
`--no-FOO` as a negation of the base flag `FOO`, so a flag literally
named "no-confirm" gets routed as `args.confirm=false` (not
`args["no-confirm"]=true`), and same for "no-wait".
Surfaced during the end-to-end smoke test on the just-merged stack:
- `cloud delete <id> --no-confirm` was hitting "Confirmation required"
and exiting 1 without calling the API.
- `cloud render --no-wait` was running the full poll + download flow
instead of submitting and exiting with the render_id.
Renamed the arg keys to `confirm` (default true) and `wait` (default
true) so citty's built-in negation handles the user-facing flags
correctly. Flag names stay the same; only the runtime arg keys change.
Live-tested both: delete now removes the render and a subsequent get
404s; --no-wait now returns just {render_id, status: "queued"} and
exits.
Note: a third instance of the same pattern exists in commands/add.ts
(`--no-clipboard`) and is also latently broken. Out of scope for this
fix; should be addressed alongside any audit of the CLI's interactive-
vs-noninteractive defaults.
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
/**
|
|
* `hyperframes cloud delete <render_id>` — soft-delete a cloud render.
|
|
*
|
|
* Subsequent GET calls return 404. The signed video URL stops working
|
|
* shortly after. There's no undo from the CLI side.
|
|
*/
|
|
|
|
import { defineCommand } from "citty";
|
|
import { createCloudClient } from "../../cloud/index.js";
|
|
import { reportApiError } from "../../cloud/errors.js";
|
|
import { withMeta } from "../../utils/updateCheck.js";
|
|
import { c } from "../../ui/colors.js";
|
|
import { errorBox } from "../../ui/format.js";
|
|
|
|
export default defineCommand({
|
|
meta: { name: "delete", description: "Soft-delete a cloud render" },
|
|
args: {
|
|
id: {
|
|
type: "positional",
|
|
required: true,
|
|
description: "Render ID to delete",
|
|
},
|
|
json: {
|
|
type: "boolean",
|
|
description: "Emit machine-readable JSON",
|
|
default: false,
|
|
},
|
|
// Citty intercepts the `--no-` prefix as a negation of the base
|
|
// flag, so a flag literally named "no-confirm" gets parsed as
|
|
// `--confirm=false` and the `args["no-confirm"]` lookup never
|
|
// sees `true`. Naming the flag `confirm` with `default: true` lets
|
|
// citty's negation handle `--no-confirm` correctly — same
|
|
// user-facing flag (`--no-confirm` to skip the prompt), correct
|
|
// runtime semantics.
|
|
confirm: {
|
|
type: "boolean",
|
|
description:
|
|
"Prompt before deleting (default: true). Pass `--no-confirm` to skip — required for scripts and --json.",
|
|
default: true,
|
|
},
|
|
},
|
|
// fallow-ignore-next-line complexity
|
|
async run({ args }) {
|
|
if (args.confirm) {
|
|
// Don't auto-bypass the prompt just because stdin isn't a TTY
|
|
// or `--json` was passed — both used to silently skip the
|
|
// safety check. Force the caller to opt in via `--no-confirm`
|
|
// so cron jobs, CI shells, and JSON consumers can't soft-delete
|
|
// by accident.
|
|
if (args.json || !process.stdin.isTTY) {
|
|
errorBox(
|
|
"Confirmation required",
|
|
"delete cannot prompt for confirmation here — stdin isn't a TTY or --json was passed.",
|
|
"Re-run with --no-confirm to acknowledge the irreversible delete.",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
const ok = await confirmDelete(args.id);
|
|
if (!ok) {
|
|
// Distinct exit code so wrapper scripts can tell an explicit
|
|
// decline apart from an API/system error.
|
|
console.log(c.dim("Aborted."));
|
|
process.exit(2);
|
|
}
|
|
}
|
|
const client = await createCloudClient();
|
|
try {
|
|
const response = await client.deleteRender({ render_id: args.id });
|
|
if (args.json) {
|
|
console.log(
|
|
JSON.stringify(
|
|
withMeta({ render: { render_id: response.render_id }, deleted: true }),
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
console.log(`${c.success("✓")} Deleted ${c.accent(response.render_id)}`);
|
|
} catch (err) {
|
|
reportApiError("Could not delete render", err, {
|
|
notFound: `No render found with id "${args.id}".`,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
async function confirmDelete(id: string): Promise<boolean> {
|
|
const clack = await import("@clack/prompts");
|
|
const answer = await clack.confirm({
|
|
message: `Delete render ${id}? This is irreversible.`,
|
|
initialValue: false,
|
|
});
|
|
return answer === true;
|
|
}
|