Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1130,8 +1130,8 @@ There is deliberately no `videos create`: a video is added either by uploading a
# Libraries
bunny stream libraries list # ID, name, videos, storage, traffic, replication regions
bunny stream libraries create my-library # interactive: prompts for the name when omitted
bunny stream libraries create my-library # stored in DE (the fixed main region) and replicated to SG, LA, NY by default
bunny stream libraries create my-library --replication-regions NY,SG # DE + NY, SG instead; pass DE alone for no replication
bunny stream libraries create my-library # stored in DE (the fixed main region); in a terminal, asks whether to replicate (SG, LA, NY preselected)
bunny stream libraries create my-library --replication-regions NY,SG # DE + NY, SG without asking; pass DE alone for no replication
bunny stream libraries create my-library --encoding-tier premium --codecs x264,vp9 # premium encoding with extra codecs
bunny stream libraries create my-library --link --save-env # link this directory and write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env
bunny stream libraries show my-library # details; API keys are never printed here, in any output format
Expand All @@ -1144,7 +1144,7 @@ bunny stream libraries credentials my-library # library ID + API key, mas
bunny stream libraries credentials my-library --show-secret --read-only # reveal the read-only key
bunny stream libraries credentials my-library --rotate # reset the API key (confirms first), then show the new one
bunny stream libraries credentials my-library --save-env # write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env
bunny stream regions # region codes: DE is the fixed main region, SG, LA, NY the default replicas
bunny stream regions # region codes: DE is the fixed main region, SG, LA, NY the suggested replicas

# Player and security settings (per library)
bunny stream libraries player show my-library
Expand Down Expand Up @@ -1225,7 +1225,7 @@ Library flags (`bunny stream libraries ...`):
| Flag | Commands | Description |
| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `--name` | `update` | New library name (`create` takes the name as its positional) |
| `--replication-regions` | `create` | Regions besides DE, the fixed main region; default `SG,LA,NY`; `DE` alone means none |
| `--replication-regions` | `create` | Replicas besides DE; omitted asks in a terminal, none otherwise; can't be removed later |
| `--add-replication-regions` | `update` | Add regions later; add-only, since regions can't be removed |
| `--mp4-fallback`, `--early-play`, `--keep-original`, `--multi-audio` (and `--no-` forms) | `create`, `update` | Encoding settings; `--early-play` needs originals and exposes them |
| `--content-tagging` / `--no-content-tagging` | `update` | Machine-learning content tagging (not accepted at create time) |
Expand Down
53 changes: 41 additions & 12 deletions packages/cli/src/commands/stream/library/create.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { createCoreClient } from "@bunny.net/openapi-client";
import {
allRegions,
DEFAULT_REPLICATION_REGIONS,
MAIN_REGION,
parseReplicationRegions,
REGION_CODES,
SUGGESTED_REPLICATION_REGIONS,
} from "@bunny.net/tools/stream";
import {
toSafeVideoLibrary,
type VideoLibraryCreateModel,
type VideoLibraryModel,
} from "@/commands/stream/api.ts";
import { regionName } from "@/commands/stream/regions.ts";
import { resolveConfig } from "@/config/index.ts";
import { clientOptions } from "@/core/client-options.ts";
import { defineCommand } from "@/core/define-command.ts";
import { UserError } from "@/core/errors.ts";
import { logger } from "@/core/logger.ts";
import { isInteractive, prompts, spinner } from "@/core/ui.ts";
import { confirmOrCancel, isInteractive, prompts, spinner } from "@/core/ui.ts";
import {
ENV_STREAM_API_KEY,
ENV_STREAM_LIBRARY_ID,
Expand All @@ -32,6 +34,36 @@ import {
} from "./flags.ts";
import { checkTranscribingLanguages } from "./language-check.ts";

async function resolveReplicationRegions(
flag: string[] | undefined,
output: string | undefined,
): Promise<string[]> {
if (flag !== undefined) return parseReplicationRegions(flag);
if (!isInteractive(output)) return [];

const answer = await confirmOrCancel(
"Replicate this library to other regions? Replicas can't be removed once added.",
);
if (answer === "cancel") throw new UserError("Creation cancelled.");
if (answer === "no") return [];

const { picked } = await prompts({
type: "multiselect",
name: "picked",
message: "Replication regions (each adds storage cost; space to toggle):",
choices: REGION_CODES.filter((code) => code !== MAIN_REGION).map(
(code) => ({
title: `${regionName(code)} (${code})`,
value: code,
selected: SUGGESTED_REPLICATION_REGIONS.includes(code),
}),
),
});

if (picked === undefined) throw new UserError("Creation cancelled.");
return picked;
}

interface LibraryCreateArgs extends LibrarySettingsArgs {
replicationRegions?: string[];
playerVersion?: number;
Expand All @@ -49,11 +81,11 @@ export const streamLibraryCreateCommand = defineCommand<LibraryCreateArgs>({
["$0 stream libraries create", "Interactive: prompts for the name"],
[
"$0 stream libraries create my-library --replication-regions NY,SG",
"Replicate to New York and Singapore only (plus DE, the main region)",
"Replicate to New York and Singapore (plus DE, the main region)",
],
[
"$0 stream libraries create internal --replication-regions DE",
"Keep everything in DE, with no replication",
"Keep everything in DE without asking about replication",
],
[
"$0 stream libraries create my-library --encoding-tier premium --codecs x264,vp9",
Expand Down Expand Up @@ -83,7 +115,7 @@ export const streamLibraryCreateCommand = defineCommand<LibraryCreateArgs>({
.option("replication-regions", {
type: "string",
array: true,
describe: `Replication regions besides DE, the fixed main region (comma-separated or repeated; default ${DEFAULT_REPLICATION_REGIONS.join(",")}; codes ${REGION_CODES.join(", ")}). Pass DE alone for no replication`,
describe: `Replication regions besides DE, the fixed main region (comma-separated or repeated; codes ${REGION_CODES.join(", ")}). Replicas can't be removed later. Omitted: asks in a terminal, none otherwise. Pass DE alone for no replication`,
})
.option("player-version", {
type: "number",
Expand Down Expand Up @@ -128,12 +160,7 @@ export const streamLibraryCreateCommand = defineCommand<LibraryCreateArgs>({
}
const name = nameInput;

// Accept both `--replication-regions NY,SG` and repeated flags. Omitted means
// the default set; DE is the fixed main region, so it is never sent.
const regions =
replicationRegions === undefined
? [...DEFAULT_REPLICATION_REGIONS]
: parseReplicationRegions(replicationRegions);
const regions = await resolveReplicationRegions(replicationRegions, output);
const warnings = librarySettingsWarnings(args);

// The encoding/transcribing flags are shared with `library update`; Name is
Expand Down Expand Up @@ -186,7 +213,9 @@ export const streamLibraryCreateCommand = defineCommand<LibraryCreateArgs>({
return;
}

const where = `replicated to ${allRegions(regions).join(", ")}`;
const where = regions.length
? `replicated to ${allRegions(regions).join(", ")}`
: `stored in ${MAIN_REGION} only`;
logger.success(
created?.Id
? `Created video library ${name} (ID: ${created.Id}), ${where}.`
Expand Down
14 changes: 6 additions & 8 deletions packages/cli/src/commands/stream/regions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
DEFAULT_REPLICATION_REGIONS,
MAIN_REGION,
REGION_CODES,
SUGGESTED_REPLICATION_REGIONS,
} from "@bunny.net/tools/stream";
import { STORAGE_REGIONS } from "@/commands/storage/constants.ts";
import { defineCommand } from "@/core/define-command.ts";
import { formatTable } from "@/core/format.ts";
import { logger } from "@/core/logger.ts";

/** Stream libraries store in the same regions as Storage, so the names come from its catalog. */
function regionName(code: string): string {
export function regionName(code: string): string {
return STORAGE_REGIONS.find((region) => region.code === code)?.name ?? code;
}

Expand All @@ -23,9 +23,7 @@ export const streamRegionsCommand = defineCommand({
code,
name: regionName(code),
main: code === MAIN_REGION,
// The main region is part of every library, so it counts as a default too.
default:
code === MAIN_REGION || DEFAULT_REPLICATION_REGIONS.includes(code),
suggested: SUGGESTED_REPLICATION_REGIONS.includes(code),
}));

if (output === "json") {
Expand All @@ -35,18 +33,18 @@ export const streamRegionsCommand = defineCommand({

logger.log(
formatTable(
["Code", "Name", "Role", "Default"],
["Code", "Name", "Role", "Suggested"],
regions.map((region) => [
region.code,
region.name,
region.main ? "main" : "replication",
region.default ? "yes" : "no",
region.suggested ? "yes" : "no",
]),
output,
),
);
logger.dim(
`${MAIN_REGION} is the fixed main region. Pick replicas with \`bunny stream libraries create --replication-regions\`, or add them later with \`libraries update --add-replication-regions\`.`,
`${MAIN_REGION} is the fixed main region. Pick replicas with \`bunny stream libraries create --replication-regions\`, or add them later with \`libraries update --add-replication-regions\`. Replicas can't be removed once added.`,
);
},
});
4 changes: 2 additions & 2 deletions packages/tools/src/stream/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const REGION_CODES = [
"JH",
] as const;

/** Replication regions a new library gets when none are named (DE is added as the main region). */
export const DEFAULT_REPLICATION_REGIONS: readonly string[] = [
/** Replication regions preselected when `stream libraries create` asks which to add. */
export const SUGGESTED_REPLICATION_REGIONS: readonly string[] = [
"SG",
"LA",
"NY",
Expand Down
4 changes: 2 additions & 2 deletions skills/bunny-cli/references/stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ bunny stream videos update 1a2b3c4d-... --collection 8a7b6c5d-...

```bash
bunny stream libraries list # ID, name, videos, storage, traffic, replication regions
bunny stream libraries create my-library # stored in DE, replicated to SG, LA, NY by default
bunny stream libraries create my-library # stored in DE; asks about replicas in a terminal, none otherwise
bunny stream libraries create my-library --replication-regions NY,SG # DE alone means no replication
bunny stream libraries create my-library --encoding-tier premium --codecs x264,vp9
bunny stream libraries show my-library # API keys are never printed here
Expand All @@ -60,7 +60,7 @@ bunny stream libraries credentials my-library --show-secret --read-only
bunny stream libraries credentials my-library --save-env # write BUNNY_STREAM_LIBRARY_ID and BUNNY_STREAM_API_KEY to .env
bunny stream libraries credentials my-library --rotate # reset the key (confirms first)
bunny stream libraries delete my-library # deletes every video in it; confirms with the count
bunny stream regions # DE is the fixed main region; SG, LA, NY the default replicas
bunny stream regions # DE is the fixed main region; SG, LA, NY the suggested replicas
bunny stream link my-library # same as `libraries link`
bunny stream unlink
```
Expand Down
Loading