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>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "./log.ts";
|
||||
import { setClient } from "./client.ts";
|
||||
import * as wmill from "../../gen/services.gen.ts";
|
||||
import { GlobalUserInfo } from "../../gen/types.gen.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as log from "@std/log";
|
||||
import * as log from "./log.ts";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { getStore } from "./store.ts";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as log from "@std/log";
|
||||
import * as log from "./log.ts";
|
||||
import { yamlParseFile } from "../utils/yaml.ts";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import {
|
||||
getCurrentGitBranch,
|
||||
getOriginalBranchForWorkspaceForks,
|
||||
@@ -196,7 +196,7 @@ export async function readConfigFile(): Promise<SyncOptions> {
|
||||
|
||||
if (!wmillYamlPath) {
|
||||
log.warn(
|
||||
"No wmill.yaml found. Use 'wmill init' to bootstrap it. Using 'bun' as default typescript runtime."
|
||||
"No wmill.yaml found. Use 'wmill init' to bootstrap it."
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "./log.ts";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
@@ -459,11 +459,12 @@ export async function resolveWorkspace(
|
||||
// forked workspace, that we detect through the branch name (only when not using branchOverride)
|
||||
const res = await tryResolveWorkspace(opts);
|
||||
if (!res.isError) {
|
||||
const workspace = (res as { isError: false; value: Workspace }).value;
|
||||
if (branchOverride || !branch || !branch.startsWith(WM_FORK_PREFIX)) {
|
||||
return res.value;
|
||||
return workspace;
|
||||
} else {
|
||||
log.info(
|
||||
`Found an active workspace \`${res.value.name}\` but the branch name indicates this is a forked workspace. Ignoring active workspace and trying to resolve the correct workspace from the branch name \`${branch}\``
|
||||
`Found an active workspace \`${workspace.name}\` but the branch name indicates this is a forked workspace. Ignoring active workspace and trying to resolve the correct workspace from the branch name \`${branch}\``
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -486,13 +487,41 @@ export async function resolveWorkspace(
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to active workspace (lowest priority)
|
||||
// Fall back to active workspace
|
||||
const activeWorkspace = await getActiveWorkspace(opts);
|
||||
if (activeWorkspace) {
|
||||
(opts as any).__secret_workspace = activeWorkspace;
|
||||
return activeWorkspace;
|
||||
}
|
||||
|
||||
// Last resort: auto-configure from Windmill environment variables
|
||||
// (set by the worker for bash/script execution)
|
||||
const envWorkspace = process.env["WM_WORKSPACE"];
|
||||
const envToken = process.env["WM_TOKEN"];
|
||||
const envBaseUrl =
|
||||
process.env["BASE_INTERNAL_URL"] ?? process.env["BASE_URL"];
|
||||
|
||||
if (envWorkspace && envToken && envBaseUrl) {
|
||||
let normalizedBaseUrl: string;
|
||||
try {
|
||||
normalizedBaseUrl = new URL(envBaseUrl).toString();
|
||||
} catch {
|
||||
log.info(colors.red(`Invalid BASE_INTERNAL_URL: ${envBaseUrl}`));
|
||||
return process.exit(-1);
|
||||
}
|
||||
log.debug(
|
||||
`Using workspace from environment variables: ${envWorkspace} on ${normalizedBaseUrl}`
|
||||
);
|
||||
const ws: Workspace = {
|
||||
name: envWorkspace,
|
||||
workspaceId: envWorkspace,
|
||||
remote: normalizedBaseUrl,
|
||||
token: envToken,
|
||||
};
|
||||
(opts as any).__secret_workspace = ws;
|
||||
return ws;
|
||||
}
|
||||
|
||||
// If everything failed, show error
|
||||
log.info(colors.red.bold("No workspace given and no default set."));
|
||||
return process.exit(-1);
|
||||
@@ -532,7 +561,8 @@ export async function tryResolveVersion(
|
||||
|
||||
const workspaceRes = await tryResolveWorkspace(opts);
|
||||
if (workspaceRes.isError) return undefined;
|
||||
const version = await fetchVersion(workspaceRes.value.remote);
|
||||
const workspace = (workspaceRes as { isError: false; value: Workspace }).value;
|
||||
const version = await fetchVersion(workspace.remote);
|
||||
|
||||
try {
|
||||
return Number.parseInt(
|
||||
|
||||
24
cli/src/core/log.ts
Normal file
24
cli/src/core/log.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
let logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR" = "INFO";
|
||||
|
||||
const levels = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
|
||||
|
||||
export function setup(level: "DEBUG" | "INFO" | "WARN" | "ERROR") {
|
||||
logLevel = level;
|
||||
}
|
||||
|
||||
export function debug(msg: unknown) {
|
||||
if (levels[logLevel] <= levels.DEBUG)
|
||||
console.log(`\x1b[90m${String(msg)}\x1b[39m`);
|
||||
}
|
||||
|
||||
export function info(msg: unknown) {
|
||||
console.log(`\x1b[34m${String(msg)}\x1b[39m`);
|
||||
}
|
||||
|
||||
export function warn(msg: unknown) {
|
||||
console.log(`\x1b[33m${String(msg)}\x1b[39m`);
|
||||
}
|
||||
|
||||
export function error(msg: unknown) {
|
||||
console.log(`\x1b[31m${String(msg)}\x1b[39m`);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GlobalOptions } from "../types.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as getPort from "get-port";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "./log.ts";
|
||||
import * as open from "open";
|
||||
import { Secret } from "@cliffy/prompt/secret";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
|
||||
@@ -2,9 +2,9 @@ import process from "node:process";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "./log.ts";
|
||||
import { yamlParseFile } from "../utils/yaml.ts";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import * as wmill from "../../gen/services.gen.ts";
|
||||
import { AIConfig, Config, GlobalSetting } from "../../gen/types.gen.ts";
|
||||
import { compareInstanceObjects, InstanceSyncOptions } from "../commands/instance/instance.ts";
|
||||
|
||||
Reference in New Issue
Block a user