diff --git a/CLAUDE.md b/CLAUDE.md index b808ebfa1..42586bfe2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,13 +16,18 @@ code in this repository. **Development:** - `yarn clean` - Clean compiled files and build artifacts -- `yarn generate:readme > &/dev/null` - Generate documentation from command +- `yarn generate:readme >/dev/null 2>&1` - Generate documentation from command definitions ## Development best practices - Follow the conventional commit format when writing commit messages - Make sure to re-generate the documentation before each commit +- Before wrapping up a task, run this checklist in order: + 1. `yarn lint` + 2. `yarn compile` + 3. `yarn test` + 4. `yarn generate:readme >/dev/null 2>&1` ## Architecture Overview @@ -40,7 +45,7 @@ container, etc.). Each command corresponds to a specific API operation. - `BaseCommand` - Authenticated commands with API client setup - `ListBaseCommand` - List operations with table formatting - `RenderBaseCommand` - Single resource display -- `ExecRenderBaseCommand` - Long-running operations with progress +- `ExecRenderBaseCommand` - Run an `exec()` step and render its result with Ink - `DeleteBaseCommand` - Delete operations with confirmation **Context System:** Context management in `src/lib/context/` allows commands to @@ -86,3 +91,6 @@ providers: - Provide examples using the `static examples` property when useful - Keep the command summary short; do not repeat the summary at the beginning of the description +- Do not assume `ExecRenderBaseCommand` provides progress handling by itself; it + executes first and then renders, so use dedicated process/progress rendering + patterns when real-time progress output is required diff --git a/docs/stack.md b/docs/stack.md index 727d5036b..af18d3b14 100644 --- a/docs/stack.md +++ b/docs/stack.md @@ -9,6 +9,8 @@ Manage container stacks * [`mw stack ls`](#mw-stack-ls) * [`mw stack ps`](#mw-stack-ps) * [`mw stack rm [STACK-ID]`](#mw-stack-rm-stack-id) +* [`mw stack set-update-schedule STACK-ID SCHEDULE`](#mw-stack-set-update-schedule-stack-id-schedule) +* [`mw stack unset-update-schedule STACK-ID`](#mw-stack-unset-update-schedule-stack-id) * [`mw stack up`](#mw-stack-up) ## `mw stack delete [STACK-ID]` @@ -248,6 +250,66 @@ FLAG DESCRIPTIONS scripts), you can use this flag to easily get the IDs of created resources for further processing. ``` +## `mw stack set-update-schedule STACK-ID SCHEDULE` + +Set the update schedule of a container stack + +``` +USAGE + $ mw stack set-update-schedule STACK-ID SCHEDULE [--token ] [-q] [--timezone ] + +ARGUMENTS + STACK-ID ID of the stack + SCHEDULE Cron expression for the update schedule + +FLAGS + -q, --quiet suppress process output and only display a machine-readable summary + --timezone= Timezone for the update schedule (for example UTC or Europe/Berlin) + +AUTHENTICATION FLAGS + --token= API token to use for authentication (overrides environment and config file). NOTE: watch out that + tokens passed via this flag might be logged in your shell history. + +DESCRIPTION + Set the update schedule of a container stack + +FLAG DESCRIPTIONS + -q, --quiet suppress process output and only display a machine-readable summary + + This flag controls if you want to see the process output or only a summary. When using mw non-interactively (e.g. in + scripts), you can use this flag to easily get the IDs of created resources for further processing. +``` + + +## `mw stack unset-update-schedule STACK-ID` + +Unset the update schedule of a container stack + +``` +USAGE + $ mw stack unset-update-schedule STACK-ID [--token ] [-q] + +ARGUMENTS + STACK-ID ID of the stack + +FLAGS + -q, --quiet suppress process output and only display a machine-readable summary + +AUTHENTICATION FLAGS + --token= API token to use for authentication (overrides environment and config file). NOTE: watch out that + tokens passed via this flag might be logged in your shell history. + +DESCRIPTION + Unset the update schedule of a container stack + +FLAG DESCRIPTIONS + -q, --quiet suppress process output and only display a machine-readable summary + + This flag controls if you want to see the process output or only a summary. When using mw non-interactively (e.g. in + scripts), you can use this flag to easily get the IDs of created resources for further processing. +``` + + ## `mw stack up` Deploys a docker-compose compatible file to a mittwald container stack diff --git a/release.config.cjs b/release.config.cjs index 24ccbed07..7b7d8d9e9 100644 --- a/release.config.cjs +++ b/release.config.cjs @@ -5,10 +5,8 @@ module.exports = { [ "@semantic-release/commit-analyzer", { - "releaseRules": [ - { "type": "chore", "scope": "deps", "release": "patch" }, - ] - } + releaseRules: [{ type: "chore", scope: "deps", release: "patch" }], + }, ], "@semantic-release/release-notes-generator", "@semantic-release/changelog", diff --git a/src/commands/stack/set-update-schedule.tsx b/src/commands/stack/set-update-schedule.tsx new file mode 100644 index 000000000..01dbe712e --- /dev/null +++ b/src/commands/stack/set-update-schedule.tsx @@ -0,0 +1,75 @@ +import { Args, Flags } from "@oclif/core"; +import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js"; +import assertSuccess from "../../lib/apiutil/assert_success.js"; +import { ReactNode } from "react"; +import { + makeProcessRenderer, + processFlags, +} from "../../rendering/process/process_flags.js"; +import { Success } from "../../rendering/react/components/Success.js"; + +type Result = { + stackId: string; +}; + +export default class SetUpdateSchedule extends ExecRenderBaseCommand< + typeof SetUpdateSchedule, + Result +> { + static description = "Set the update schedule of a container stack"; + + static args = { + "stack-id": Args.string({ + description: "ID of the stack", + required: true, + }), + schedule: Args.string({ + description: "Cron expression for the update schedule", + required: true, + }), + }; + + static flags = { + ...ExecRenderBaseCommand.baseFlags, + ...processFlags, + timezone: Flags.string({ + description: + "Timezone for the update schedule (for example UTC or Europe/Berlin)", + required: false, + }), + }; + + protected async exec(): Promise { + const p = makeProcessRenderer(this.flags, "Setting stack update schedule"); + + const stackId = this.args["stack-id"]; + + await p.runStep("updating stack schedule", async () => { + const response = await this.apiClient.container.setStackUpdateSchedule({ + stackId, + data: { + updateSchedule: { + cron: this.args.schedule, + timezone: this.flags.timezone, + }, + }, + }); + + assertSuccess(response); + }); + + await p.complete( + + Update schedule for stack {stackId} was successfully set. + , + ); + + return { stackId }; + } + + protected render({ stackId }: Result): ReactNode { + if (this.flags.quiet) { + return stackId; + } + } +} diff --git a/src/commands/stack/unset-update-schedule.tsx b/src/commands/stack/unset-update-schedule.tsx new file mode 100644 index 000000000..1b61bb645 --- /dev/null +++ b/src/commands/stack/unset-update-schedule.tsx @@ -0,0 +1,69 @@ +import { Args } from "@oclif/core"; +import { ExecRenderBaseCommand } from "../../lib/basecommands/ExecRenderBaseCommand.js"; +import assertSuccess from "../../lib/apiutil/assert_success.js"; +import { ReactNode } from "react"; +import { + makeProcessRenderer, + processFlags, +} from "../../rendering/process/process_flags.js"; +import { Success } from "../../rendering/react/components/Success.js"; + +type Result = { + stackId: string; +}; + +export default class UnsetUpdateSchedule extends ExecRenderBaseCommand< + typeof UnsetUpdateSchedule, + Result +> { + static description = "Unset the update schedule of a container stack"; + + static args = { + "stack-id": Args.string({ + description: "ID of the stack", + required: true, + }), + }; + + static flags = { + ...ExecRenderBaseCommand.baseFlags, + ...processFlags, + }; + + protected async exec(): Promise { + const p = makeProcessRenderer( + this.flags, + "Unsetting stack update schedule", + ); + + const stackId = this.args["stack-id"]; + + await p.runStep("removing stack schedule", async () => { + const response = await this.apiClient.container.setStackUpdateSchedule({ + stackId, + data: { + updateSchedule: null as unknown as { + cron: string; + timezone?: string; + }, + }, + }); + + assertSuccess(response); + }); + + await p.complete( + + Update schedule for stack {stackId} was successfully removed. + , + ); + + return { stackId }; + } + + protected render({ stackId }: Result): ReactNode { + if (this.flags.quiet) { + return stackId; + } + } +}