mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
Merge f86f39ef2b into 3337cc8990
This commit is contained in:
@@ -4,12 +4,24 @@ import { dirname, join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
const publishState = vi.hoisted(() => ({ publish: vi.fn() }));
|
||||
const authState = vi.hoisted(() => ({ tryResolveCredential: vi.fn().mockResolvedValue(null) }));
|
||||
const clackState = vi.hoisted(() => ({ confirm: vi.fn() }));
|
||||
|
||||
vi.mock("../utils/publishProject.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../utils/publishProject.js")>()),
|
||||
publishProjectArchive: publishState.publish,
|
||||
}));
|
||||
|
||||
vi.mock("../auth/index.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../auth/index.js")>()),
|
||||
tryResolveCredential: authState.tryResolveCredential,
|
||||
}));
|
||||
|
||||
vi.mock("@clack/prompts", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@clack/prompts")>()),
|
||||
confirm: clackState.confirm,
|
||||
}));
|
||||
|
||||
import publishCommand, { parseUpdateTarget } from "./publish.js";
|
||||
|
||||
describe("parseUpdateTarget", () => {
|
||||
@@ -90,3 +102,56 @@ describe("publish default-entry preflight", () => {
|
||||
expect(output).toContain("publish accepts project directories, not individual HTML files");
|
||||
});
|
||||
});
|
||||
|
||||
describe("publish consent notice", () => {
|
||||
async function runConsent(credential: unknown): Promise<string> {
|
||||
const project = mkdtempSync(join(tmpdir(), "hf-publish-consent-"));
|
||||
writeFileSync(
|
||||
join(project, "index.html"),
|
||||
`<html><body><div data-composition-id="main" data-width="1920" data-height="1080" data-start="0" data-duration="5"><div class="clip" data-start="0" data-duration="5">Visible</div></div></body></html>`,
|
||||
);
|
||||
publishState.publish.mockReset();
|
||||
authState.tryResolveCredential.mockReset().mockResolvedValue(credential);
|
||||
// Declining the prompt stops the run right after the notice — no network, no upload.
|
||||
clackState.confirm.mockReset().mockResolvedValue(false);
|
||||
const lines: string[] = [];
|
||||
const log = vi.spyOn(console, "log").mockImplementation((...parts: unknown[]) => {
|
||||
lines.push(parts.map(String).join(" "));
|
||||
});
|
||||
|
||||
try {
|
||||
await publishCommand.run?.({ args: { dir: project, proxy: false } } as never);
|
||||
expect(publishState.publish).not.toHaveBeenCalled();
|
||||
return lines.join("\n");
|
||||
} finally {
|
||||
log.mockRestore();
|
||||
rmSync(project, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
it("offers the claim step when publishing anonymously", async () => {
|
||||
const output = await runConsent(null);
|
||||
|
||||
expect(output).toContain("claim it after authenticating");
|
||||
});
|
||||
|
||||
it("tells a signed-in publisher there is no claim link", async () => {
|
||||
const output = await runConsent({ type: "api_key", key: "k", source: "env" });
|
||||
|
||||
expect(output).toContain("you own it on publish and there is no claim link");
|
||||
expect(output).not.toContain("claim it after authenticating");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["anonymous", null],
|
||||
["signed in", { type: "api_key", key: "k", source: "env" }],
|
||||
])("discloses the exposure before uploading (%s)", async (_label, credential) => {
|
||||
// The consent prompt is the only place the user is told the artifact is world-readable:
|
||||
// the result block prints no exposure line on the owned branch. Dropping it from either
|
||||
// branch means someone approves an upload without being told who can reach it.
|
||||
const output = await runConsent(credential);
|
||||
|
||||
expect(output).toContain("creates a stable public URL");
|
||||
expect(output).toContain("Anyone with the URL can open the published project");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,13 +119,24 @@ export default defineCommand({
|
||||
}
|
||||
}
|
||||
|
||||
// Resolved once: the consent notice and the --update/--space gate must agree on
|
||||
// whether this publish is authenticated.
|
||||
const credential = await tryResolveCredential();
|
||||
|
||||
if (args.yes !== true) {
|
||||
console.log();
|
||||
console.log(
|
||||
` ${c.bold("hyperframes publish uploads this project and creates a stable public URL.")}`,
|
||||
);
|
||||
// Both branches must state the exposure — the published project is readable by anyone
|
||||
// holding the URL whether or not it is owned. Only the claim half differs: an
|
||||
// anonymous publish gets a claim token, an authenticated one is owned on creation, so
|
||||
// promising a claim step to a signed-in user sends them looking for a token the server
|
||||
// never issued.
|
||||
console.log(
|
||||
` ${c.dim("Anyone with the URL can open the published project and claim it after authenticating.")}`,
|
||||
credential === null
|
||||
? ` ${c.dim("Anyone with the URL can open the published project and claim it after authenticating.")}`
|
||||
: ` ${c.dim("Anyone with the URL can open the published project. You are signed in, so you own it on publish and there is no claim link.")}`,
|
||||
);
|
||||
console.log();
|
||||
const approved = await clack.confirm({ message: "Publish this project?" });
|
||||
@@ -147,7 +158,6 @@ export default defineCommand({
|
||||
// --update / --space only take effect for an authenticated owner. Fail loudly rather
|
||||
// than silently minting a fresh URL — the exact failure mode this feature removes.
|
||||
if (updateTarget || spaceOverride) {
|
||||
const credential = await tryResolveCredential();
|
||||
if (!credential) {
|
||||
console.log();
|
||||
console.log(
|
||||
|
||||
Reference in New Issue
Block a user