-
Notifications
You must be signed in to change notification settings - Fork 5
Added basic PZ commands #86
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
base: main
Are you sure you want to change the base?
Changes from all commits
876663f
337171d
54b9cbc
b2c3762
c9dc23d
e020c26
1be9bb4
02e23ab
65878d1
621118b
5f42010
e7c25ca
cd96e9f
74a48a4
674528e
b9a80f4
34b5040
f69b0f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export const PULL_ZONE_MANIFEST = "pullzone.json"; | ||
|
|
||
| export interface PullZoneManifest { | ||
| id: number; | ||
| name?: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import { createCoreClient } from "@bunny.net/openapi-client"; | ||
| 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 { saveManifest } from "../../core/manifest.ts"; | ||
| import { confirm, spinner } from "../../core/ui.ts"; | ||
| import { | ||
| PULL_ZONE_MANIFEST, | ||
| type PullZoneManifest, | ||
| } from "./constants.ts"; | ||
|
|
||
| interface CreateArgs { | ||
| name?: string; | ||
| origin?: string; | ||
| } | ||
|
|
||
| export const pzCreateCommand = defineCommand<CreateArgs>({ | ||
| command: "create <name> <origin>", | ||
| describe: "Create a new pull zone.", | ||
| examples: [ | ||
| ["$0 pz create my-zone https://origin.example.com", "Create a pull zone"], | ||
| ], | ||
|
|
||
| builder: (yargs) => | ||
| yargs | ||
| .positional("name", { type: "string", describe: "Pull zone name" }) | ||
| .positional("origin", { | ||
| type: "string", | ||
| describe: "Origin URL (https:// is prepended if missing)", | ||
| }), | ||
|
|
||
| handler: async ({ name, origin, profile, output, verbose, apiKey }) => { | ||
| if (!name || !origin) { | ||
| throw new UserError("Name and origin are required."); | ||
| } | ||
|
|
||
| const url = origin.match(/^https?:\/\//) ? origin : `https://${origin}`; | ||
|
|
||
| const config = resolveConfig(profile, apiKey, verbose); | ||
| const client = createCoreClient(clientOptions(config, verbose)); | ||
|
|
||
| // Create | ||
| const createSpin = spinner("Creating pull zone..."); | ||
| createSpin.start(); | ||
|
|
||
| const { data, error } = await client.POST("/pullzone", { | ||
| body: { Name: name, OriginUrl: url } as any, | ||
| }); | ||
|
|
||
| createSpin.stop(); | ||
|
|
||
| if (error) { | ||
| throw new UserError(`Failed to create pull zone: ${error}`); | ||
| } | ||
|
|
||
|
Comment on lines
+48
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The POST body is cast with Context Used: CLAUDE.md (source) Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| const created = data as { Id?: number; Name?: string | null } | undefined; | ||
| const createdId = created?.Id; | ||
|
|
||
| if (output === "json") { | ||
| logger.log(JSON.stringify(created, null, 2)); | ||
| return; | ||
| } | ||
|
|
||
| logger.success(`Pull zone "${name}" created.`); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be a nice DX improvement to share the URL with some kind of arrow to the origin so it's clear the zone is for.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am currently fixing stuff, and i am not sure what purpose would this serve as you already know which origin you use, as you have to define it in the command pz create [pullzoneName] [origin] I can print it out again, but devs probably know what they wrote second ago? |
||
|
|
||
| // Offer to select it | ||
| if (createdId) { | ||
| const shouldSelect = await confirm( | ||
| `Set "${name}" as the active context?`, | ||
| ); | ||
| if (shouldSelect) { | ||
| saveManifest<PullZoneManifest>(PULL_ZONE_MANIFEST, { | ||
| id: createdId, | ||
| name: created?.Name ?? undefined, | ||
| }); | ||
| logger.success(`Selected ${name}.`); | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import { createCoreClient } from "@bunny.net/openapi-client"; | ||
| 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 { loadManifest, removeManifest } from "../../core/manifest.ts"; | ||
| import { confirm, spinner } from "../../core/ui.ts"; | ||
| import { | ||
| PULL_ZONE_MANIFEST, | ||
| type PullZoneManifest, | ||
| } from "./constants.ts"; | ||
|
|
||
| interface DeleteArgs { | ||
| id?: number; | ||
| force?: boolean; | ||
| } | ||
|
|
||
| export const pzDeleteCommand = defineCommand<DeleteArgs>({ | ||
| command: "delete [id]", | ||
| describe: "Delete a pull zone.", | ||
| examples: [ | ||
| ["$0 pz delete", "Delete selected pull zone"], | ||
| ["$0 pz delete 12345", "Delete pull zone 12345"], | ||
| ["$0 pz delete --force", "Skip confirmation"], | ||
| ], | ||
|
|
||
| builder: (yargs) => | ||
| yargs | ||
| .positional("id", { | ||
| type: "number", | ||
| describe: "Pull zone ID (uses selected one if omitted)", | ||
| }) | ||
| .option("force", { | ||
| alias: "f", | ||
| type: "boolean", | ||
| default: false, | ||
| describe: "Skip confirmation", | ||
| }), | ||
|
|
||
| handler: async ({ id, force, profile, output, verbose, apiKey }) => { | ||
| const zoneId = id ?? loadManifest<PullZoneManifest>(PULL_ZONE_MANIFEST).id; | ||
| if (!zoneId) { | ||
| throw new UserError( | ||
| "No pull zone specified.", | ||
| 'Pass a pull zone ID or run "bunny pz link" first.', | ||
| ); | ||
| } | ||
|
|
||
| const config = resolveConfig(profile, apiKey, verbose); | ||
| const client = createCoreClient(clientOptions(config, verbose)); | ||
|
|
||
| const { data: zone, error: getError } = await client.GET("/pullzone/{id}", { | ||
| params: { path: { id: zoneId } }, | ||
| }); | ||
|
|
||
| if (getError) { | ||
| throw new UserError( | ||
| `Failed to fetch pull zone ${zoneId}: ${getError.message ?? getError}`, | ||
| ); | ||
| } | ||
|
|
||
| const label = zone?.Name | ||
| ? `${zone.Name} (${zoneId})` | ||
| : String(zoneId); | ||
|
|
||
| const ok = await confirm(`Delete pull zone ${label}?`, { force }); | ||
| if (!ok) { | ||
| logger.log("Delete cancelled."); | ||
| return; | ||
| } | ||
|
|
||
| const spin = spinner("Deleting pull zone..."); | ||
| spin.start(); | ||
|
|
||
| const { error } = await client.DELETE("/pullzone/{id}", { | ||
| params: { path: { id: zoneId } }, | ||
| }); | ||
|
|
||
| spin.stop(); | ||
|
|
||
| if (error) { | ||
| throw new UserError(`Failed to delete pull zone: ${error}`); | ||
| } | ||
|
|
||
| // Remove manifest only if it pointed at the deleted zone | ||
| const manifest = loadManifest<PullZoneManifest>(PULL_ZONE_MANIFEST); | ||
| if (manifest.id === zoneId) { | ||
| removeManifest(PULL_ZONE_MANIFEST); | ||
| } | ||
|
|
||
| if (output === "json") { | ||
| logger.log(JSON.stringify({ id: zoneId, deleted: true })); | ||
| return; | ||
| } | ||
|
|
||
| logger.success(`Pull zone ${label} deleted.`); | ||
| }, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { defineNamespace } from "../../core/define-namespace.ts"; | ||
| import { pzCreateCommand } from "./create.ts"; | ||
| import { pzDeleteCommand } from "./delete.ts"; | ||
| import { pzLinkCommand } from "./link.ts"; | ||
| import { pzListCommand } from "./list.ts"; | ||
| import { pzPurgeCommand } from "./purge.ts"; | ||
| import { pzShowCommand } from "./show.ts"; | ||
| import { pzUnlinkCommand } from "./unlink.ts"; | ||
|
|
||
| // TODO: implement rules and hostnames subcommands | ||
| // const rulesNamespace = defineNamespace("rules", "Manage pull zone edge rules.", [...]); | ||
| // const hostnamesNamespace = defineNamespace("hostnames", "Manage pull zone hostnames.", [...]); | ||
|
|
||
| export const pzNamespace = defineNamespace( | ||
| "pz", | ||
| "Manage pull zones.", | ||
| [ | ||
| pzListCommand, | ||
| pzCreateCommand, | ||
| pzDeleteCommand, | ||
| pzLinkCommand, | ||
| pzPurgeCommand, | ||
| pzShowCommand, | ||
| pzUnlinkCommand, | ||
| ], | ||
|
burstw0w marked this conversation as resolved.
Comment on lines
+1
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Context Used: CLAUDE.md (source) |
||
| ); | ||
|
burstw0w marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import type { components } from "@bunny.net/openapi-client/generated/core.d.ts"; | ||
| import { createCoreClient } from "@bunny.net/openapi-client"; | ||
| import prompts from "prompts"; | ||
| 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 { saveManifest } from "../../core/manifest.ts"; | ||
| import { spinner } from "../../core/ui.ts"; | ||
| import { | ||
| PULL_ZONE_MANIFEST, | ||
| type PullZoneManifest, | ||
| } from "./constants.ts"; | ||
|
|
||
| interface LinkArgs { | ||
| id?: number; | ||
| } | ||
|
|
||
| export const pzLinkCommand = defineCommand<LinkArgs>({ | ||
| command: "link [id]", | ||
| describe: "Link the current directory to a pull zone.", | ||
| examples: [ | ||
| ["$0 pz link", "Interactive selection"], | ||
| ["$0 pz link 12345", "Link by ID"], | ||
| ], | ||
|
|
||
| builder: (yargs) => | ||
| yargs.positional("id", { | ||
| type: "number", | ||
| describe: "Pull zone ID", | ||
| }), | ||
|
|
||
| handler: async ({ id, profile, output, verbose, apiKey }) => { | ||
| const config = resolveConfig(profile, apiKey, verbose); | ||
| const client = createCoreClient(clientOptions(config, verbose)); | ||
|
|
||
| if (id) { | ||
| const spin = spinner("Fetching pull zone..."); | ||
| spin.start(); | ||
|
|
||
| let zone: components["schemas"]["PullZoneModel"] | undefined; | ||
|
|
||
| try { | ||
| const { data } = await client.GET("/pullzone/{id}", { | ||
| params: { path: { id } }, | ||
| }); | ||
| zone = data as components["schemas"]["PullZoneModel"] | undefined; | ||
| } catch (err: unknown) { | ||
| spin.stop(); | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| throw new UserError(`Fetching failed: ${msg}`); | ||
| } | ||
|
|
||
| spin.stop(); | ||
|
|
||
| if (!zone) { | ||
| throw new UserError(`Pull zone ${id} not found.`); | ||
| } | ||
|
|
||
| saveManifest<PullZoneManifest>(PULL_ZONE_MANIFEST, { | ||
| id: zone.Id ?? id, | ||
| }); | ||
|
|
||
| if (output === "json") { | ||
| logger.log(JSON.stringify({ id: zone.Id ?? id })); | ||
| return; | ||
| } | ||
|
|
||
| logger.success(`Linked to ${zone.Name ?? zone.Id ?? id}.`); | ||
| return; | ||
| } | ||
|
|
||
| const spin = spinner("Fetching pull zones..."); | ||
| spin.start(); | ||
|
|
||
| const { data } = await client.GET("/pullzone"); | ||
|
|
||
| spin.stop(); | ||
|
|
||
| const zones = (data ?? []) as components["schemas"]["PullZoneModel"][]; | ||
|
|
||
| if (zones.length === 0) { | ||
| throw new UserError( | ||
| "No pull zones found.", | ||
| 'Run "bunny pz create" to create one.', | ||
| ); | ||
| } | ||
|
|
||
| const sorted = zones.sort((a, b) => | ||
| (a.Name ?? "").localeCompare(b.Name ?? ""), | ||
| ); | ||
|
|
||
| const { selected } = await prompts({ | ||
| type: "select", | ||
| name: "selected", | ||
| message: "Link to a pull zone:", | ||
| choices: sorted.map((zone) => ({ | ||
| title: zone.Name ?? String(zone.Id), | ||
| value: zone, | ||
| })), | ||
| }); | ||
|
|
||
| if (!selected) { | ||
| logger.log("Link cancelled."); | ||
| return; | ||
| } | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
|
|
||
| saveManifest<PullZoneManifest>(PULL_ZONE_MANIFEST, { | ||
| id: selected.Id, | ||
| }); | ||
|
|
||
| if (output === "json") { | ||
| logger.log(JSON.stringify({ id: selected.Id })); | ||
| return; | ||
| } | ||
|
|
||
| logger.success(`Linked to ${selected.Name ?? selected.Id}.`); | ||
| }, | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
components["schemas"]["EdgeRuleV2Model"]should be used inline instead of declaring here, at worst, we can Pick from here, although I'd rather not limit scope and use the input type as is, which allows us to spread on more options.