diff --git a/packages/cli/README.md b/packages/cli/README.md index 4fc385e2..eb2c4956 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -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 @@ -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 @@ -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) | diff --git a/packages/cli/src/commands/stream/library/create.ts b/packages/cli/src/commands/stream/library/create.ts index 43ec3189..09246b7d 100644 --- a/packages/cli/src/commands/stream/library/create.ts +++ b/packages/cli/src/commands/stream/library/create.ts @@ -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, @@ -32,6 +34,36 @@ import { } from "./flags.ts"; import { checkTranscribingLanguages } from "./language-check.ts"; +async function resolveReplicationRegions( + flag: string[] | undefined, + output: string | undefined, +): Promise { + 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; @@ -49,11 +81,11 @@ export const streamLibraryCreateCommand = defineCommand({ ["$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", @@ -83,7 +115,7 @@ export const streamLibraryCreateCommand = defineCommand({ .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", @@ -128,12 +160,7 @@ export const streamLibraryCreateCommand = defineCommand({ } 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 @@ -186,7 +213,9 @@ export const streamLibraryCreateCommand = defineCommand({ 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}.` diff --git a/packages/cli/src/commands/stream/regions.ts b/packages/cli/src/commands/stream/regions.ts index 58b47fba..a6ef0652 100644 --- a/packages/cli/src/commands/stream/regions.ts +++ b/packages/cli/src/commands/stream/regions.ts @@ -1,7 +1,7 @@ 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"; @@ -9,7 +9,7 @@ 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; } @@ -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") { @@ -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.`, ); }, }); diff --git a/packages/tools/src/stream/model.ts b/packages/tools/src/stream/model.ts index e4c3877e..5d29562e 100644 --- a/packages/tools/src/stream/model.ts +++ b/packages/tools/src/stream/model.ts @@ -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", diff --git a/skills/bunny-cli/references/stream.md b/skills/bunny-cli/references/stream.md index 5565ae16..8973dbff 100644 --- a/skills/bunny-cli/references/stream.md +++ b/skills/bunny-cli/references/stream.md @@ -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 @@ -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 ```