-
Notifications
You must be signed in to change notification settings - Fork 5
refactor(cli/db): extract shared helpers and quickstart snippets #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
54eede3
refactor(cli/db): extract shared helpers and quickstart snippets
jamie-at-bunny d57ef1f
fix(cli): exit non-zero when an interactive selection is cancelled
jamie-at-bunny ea3dc7c
Merge remote-tracking branch 'origin/main' into refactor/cli-db-dedup…
jamie-at-bunny 5f57e62
docs: agents
jamie-at-bunny File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@bunny.net/cli": patch | ||
| --- | ||
|
|
||
| Cancelling an interactive selection prompt now exits non-zero so scripts and CI can tell a cancelled command apart from a successful one. Previously `db link`, `db regions update`, and `scripts env remove` printed a "Cancelled." line and exited `0` when you aborted the picker with Ctrl-C/Esc, making a no-op indistinguishable from success. They now exit `1` (and emit a proper `{"error":…}` payload under `--output json`), matching `scripts link`, `apps link`, and the shared `resolveDbId` selection prompt. Declining a confirmation ("Delete?", "Replace?") still exits `0` — that's a deliberate answer, not an abort. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import type { createDbClient } from "@bunny.net/openapi-client"; | ||
| import type { components } from "@bunny.net/openapi-client/generated/database.d.ts"; | ||
| import { UserError } from "../../core/errors.ts"; | ||
| import { DB_PAGE_SIZE, TOKEN_TTL_MINUTES } from "./constants.ts"; | ||
|
|
||
| type DbClient = ReturnType<typeof createDbClient>; | ||
| type Database = components["schemas"]["Database2"]; | ||
| type RegionConfig = components["schemas"]["ListConfigAPIResponse"]; | ||
| type GenerateTokenResponse = | ||
| components["schemas"]["GenerateTokenDatabaseV2Response"]; | ||
| type TokenAuthorization = | ||
| components["schemas"]["GenerateTokenDatabaseV2Payload"]["authorization"]; | ||
| type DBLiveStatus = components["schemas"]["DBLiveStatus"]; | ||
|
|
||
| /** Fetch a single database by ID, throwing a UserError if it doesn't exist. */ | ||
| export async function fetchDatabase( | ||
| client: DbClient, | ||
| id: string, | ||
| ): Promise<Database> { | ||
| const { data } = await client.GET("/v2/databases/{db_id}", { | ||
| params: { path: { db_id: id } }, | ||
| }); | ||
| if (!data?.db) throw new UserError(`Database ${id} not found.`); | ||
| return data.db; | ||
| } | ||
|
|
||
| /** Fetch every database in the account, paginating until exhausted. */ | ||
| export async function fetchAllDatabases(client: DbClient): Promise<Database[]> { | ||
| const all: Database[] = []; | ||
| let page = 1; | ||
|
|
||
| while (true) { | ||
| const { data } = await client.GET("/v2/databases", { | ||
| params: { query: { page, per_page: DB_PAGE_SIZE } }, | ||
| }); | ||
|
|
||
| all.push(...(data?.databases ?? [])); | ||
|
|
||
| if (!data?.page_info?.has_more_items) break; | ||
| page++; | ||
| } | ||
|
|
||
| return all; | ||
| } | ||
|
|
||
| /** Fetch the global region configuration, throwing if unavailable. */ | ||
| export async function fetchRegionConfig( | ||
| client: DbClient, | ||
| ): Promise<RegionConfig> { | ||
| const { data } = await client.GET("/v1/config", { params: {} }); | ||
| if (!data) throw new UserError("Could not fetch region configuration."); | ||
| return data; | ||
| } | ||
|
|
||
| /** Fetch a database and the region config in parallel. */ | ||
| export async function fetchDatabaseWithRegions( | ||
| client: DbClient, | ||
| id: string, | ||
| ): Promise<{ db: Database; config: RegionConfig }> { | ||
| const [db, config] = await Promise.all([ | ||
| fetchDatabase(client, id), | ||
| fetchRegionConfig(client), | ||
| ]); | ||
| return { db, config }; | ||
| } | ||
|
|
||
| /** Build a region id → display name lookup from the region config. */ | ||
| export function regionNameMap(config: RegionConfig): Map<string, string> { | ||
| const map = new Map<string, string>(); | ||
| for (const r of [...config.primary_regions, ...config.replica_regions]) { | ||
| map.set(r.id, r.name); | ||
| } | ||
| return map; | ||
| } | ||
|
|
||
| /** Generate an auth token for a database. */ | ||
| export async function generateToken( | ||
| client: DbClient, | ||
| id: string, | ||
| opts: { authorization: TokenAuthorization; expiresAt: string | null }, | ||
| ): Promise<GenerateTokenResponse | undefined> { | ||
| const { data } = await client.PUT("/v2/databases/{db_id}/auth/generate", { | ||
| params: { path: { db_id: id } }, | ||
| body: { authorization: opts.authorization, expires_at: opts.expiresAt }, | ||
| }); | ||
| return data; | ||
| } | ||
|
|
||
| /** RFC 3339 timestamp `minutes` from now (defaults to the token TTL). */ | ||
| export function tokenExpiryFromNow(minutes = TOKEN_TTL_MINUTES): string { | ||
| return new Date(Date.now() + minutes * 60 * 1000).toISOString(); | ||
| } | ||
|
|
||
| /** Fetch live status metrics for the given database IDs. */ | ||
| export async function fetchLiveStatus( | ||
| client: DbClient, | ||
| ids: string[], | ||
| ): Promise<Record<string, DBLiveStatus>> { | ||
| const { data } = await client.POST("/v1/live/live_db", { | ||
| body: { db_ids: ids }, | ||
| }); | ||
| return data?.live_metrics ?? {}; | ||
| } | ||
|
|
||
| /** "Active" when the database is live, otherwise "Idle". */ | ||
| export function liveStatusLabel(live: DBLiveStatus | undefined): string { | ||
| return live?.state === "Live" ? "Active" : "Idle"; | ||
| } | ||
|
|
||
| /** Primary region code from live metadata, or null when not live. */ | ||
| export function liveMainRegion(live: DBLiveStatus | undefined): string | null { | ||
| return live?.state === "Live" ? live.metadata.main : null; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.