Files
windmill/cli/src/commands/workers/workers.ts
T
Ruben Fiszel 4fedfdfd11 feat(cli): add consistent get/list/new subcommands for all item types (#8047)
* feat(cli): add consistent get/list/new subcommands for all item types

Make the CLI consistent so every item type (script, flow, app, resource,
resource-type, variable, schedule, folder, trigger) supports get/list/new
subcommands, enabling the CLI to be used as a full API client in bash
scripts with jq piping.

- Add --json flag to all list commands for machine-readable output
- Register explicit "list" subcommand alongside default action
- Add "get <path> [--json]" subcommand to fetch single items from API
- Rename "bootstrap" to "new" for script/flow, keep "bootstrap" as alias
- Add "new" subcommand for resource, resource-type, variable, schedule,
  folder, and trigger to create local template YAML files
- Update cli-commands skill documentation for wmill init
- Add integration tests for all new commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* feat: install wmill CLI in Docker images and use it for bash variable/resource access

- Install windmill-cli via bun in all Dockerfiles that include bun
- DockerfileCli: switch from node:slim to oven/bun:slim
- CLI: auto-configure from WM_WORKSPACE/WM_TOKEN/BASE_INTERNAL_URL env vars
  as last-resort fallback when no workspace is configured
- Frontend: replace curl-based bash snippets with wmill variable/resource get
- Add backend integration tests for wmill CLI in bash scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): install windmill-cli in backend test workflow

Ensures wmill is available on PATH for bash integration tests
that use `wmill variable get` and `wmill resource get`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(cli): replace @std/* Deno dependencies with Node.js equivalents

Replace @std/log with a lightweight custom logger (core/log.ts),
@std/path with node:path, and @std/yaml with the yaml npm package.
Also fix process hang on exit, add --node option to install_dev.sh,
and add missing hasRequiredPermissions to NpmProvider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* all

* all

* refactor(cli): replace @ayonli/jsext and @std/encoding with lightweight alternatives

Replace @ayonli/jsext (8.4MB) with tar-stream (32kB) for tar creation,
replace @std/encoding with Node.js Buffer.toString("hex"), and fix
@windmill-labs/shared-utils to use direct npm instead of JSR mirror.
Also resolve merge conflicts in sync.ts and fix pre-existing type errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): use singleQuote YAML output and pass yamlOptions in gitsync pull

The yaml library defaults to double quotes, but the codebase (and tests)
expect single-quoted strings. Add singleQuote: true to yamlOptions and
pass yamlOptions to gitsync-settings pull writeFile calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* all

* fix(cli): address code review feedback

- Install CLI from source in backend tests instead of npm
- Fix script bootstrap catch block to re-throw "File already exists"
- Add type-safe local variable after trigger kind validation
- Use created_by instead of policy.on_behalf_of for app get output
- Note --kind is recommended for faster trigger lookup in help text
- Document node symlink purpose in Dockerfiles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): use /usr/bin for wmill wrapper to ensure it's in PATH

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): install wmill to ~/.local/bin to avoid permission issues

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci(backend): switch to Blacksmith runner and add cargo caching

- Switch from ubicloud-standard-16 to blacksmith-16vcpu-ubuntu-2404 for faster NVMe-backed builds
- Add stickydisk for cargo target directory (persistent NVMe cache across runs)
- Add cache for cargo registry and git dependencies
- Upgrade DuckDB FFI cache from actions/cache@v3 to useblacksmith/cache@v1
- Enable CARGO_INCREMENTAL=1 to benefit from persistent target cache

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix ci

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:53:28 +00:00

107 lines
3.4 KiB
TypeScript

import { Command } from "@cliffy/command";
import { Table } from "@cliffy/table";
import * as log from "../../core/log.ts";
import * as wmill from "../../../gen/services.gen.ts";
import { pickInstance } from "../instance/instance.ts";
type GlobalOptions = {
instance?: string;
baseUrl?: string;
};
function toPercent(value: number | undefined): string {
return value != undefined ? `${(value * 100).toFixed(1)}%` : '?%';
}
async function displayWorkers(opts: GlobalOptions) {
const activeInstance = await pickInstance(opts, true);
if (activeInstance) {
const workerGroups = await wmill.listWorkerGroups();
const workers = await wmill.listWorkers({
pingSince: 10
});
const groupedWorkers = workerGroups.map(group => {
const groupWorkers = workers.filter(worker => worker.worker_group === group.name);
return {
groupName: group.name,
workers: groupWorkers
};
});
// Add workers that don't belong to any worker group
const ungroupedWorkers = workers.filter(worker =>
!workerGroups.some(group => group.name === worker.worker_group)
);
if (ungroupedWorkers.length > 0) {
ungroupedWorkers.forEach(worker => {
const groupName = worker.worker_group || "Ungrouped";
let group = groupedWorkers.find(g => g.groupName === groupName);
if (!group) {
group = { groupName, workers: [] };
groupedWorkers.push(group);
}
group.workers.push(worker);
});
}
// Sort groupedWorkers
groupedWorkers.sort((a, b) => {
// Always put 'default' first
if (a.groupName === 'default') return -1;
if (b.groupName === 'default') return 1;
// Then sort by number of workers (descending order)
return b.workers.length - a.workers.length;
});
groupedWorkers.forEach(group => {
log.info(`\nWorker Group: ${group.groupName} (${group.workers.length} workers)`);
if (group.workers.length === 0) {
log.info(" No workers in this group");
} else {
new Table()
.header(["Worker ID", "Host", "Queues", "Jobs", "Occupancy rate 15s/5m/30m/ever)", "Last job", "Last Ping"])
.padding(2)
.border(true)
.maxColWidth(30)
.body(group.workers.map(worker => [
worker.worker,
worker.worker_instance,
worker.custom_tags?.join(', ') || '',
worker.jobs_executed,
`${toPercent(worker.occupancy_rate_15s)}/${toPercent(worker.occupancy_rate_5m)}/${toPercent(worker.occupancy_rate_30m)}/${toPercent(worker.occupancy_rate)}`,
worker.last_job_id ? worker.last_job_id + ' ' +worker.last_job_workspace_id : '',
`${worker.last_ping}s ago`
]))
.render();
}
});
} else {
log.info("No active instance found");
log.info("Use 'wmill instance add' to add a new instance");
}
}
const command = new Command()
.description("List all workers grouped by worker groups")
.option(
"--instance [instance]",
"Name of the instance to push to, override the active instance"
)
.option(
"--base-url [baseUrl]",
"If used with --token, will be used as the base url for the instance"
)
.action(displayWorkers as any);
export default command;