mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
4fedfdfd11
* 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>
190 lines
5.6 KiB
TypeScript
190 lines
5.6 KiB
TypeScript
import { colors } from "@cliffy/ansi/colors";
|
|
import * as log from "../../core/log.ts";
|
|
import { deepEqual, selectRepository } from "../../utils/utils.ts";
|
|
import { SyncOptions, getEffectiveSettings, DEFAULT_SYNC_OPTIONS } from "../../core/conf.ts";
|
|
import { GitSyncRepository, GIT_SYNC_FIELDS } from "./types.ts";
|
|
import { GitSyncSettingsConverter } from "./converter.ts";
|
|
|
|
// Helper for consistent output handling between JSON and regular modes
|
|
export function outputResult(opts: { jsonOutput?: boolean }, result: {
|
|
success: boolean;
|
|
message?: string;
|
|
error?: string;
|
|
[key: string]: any;
|
|
}): void {
|
|
if (opts.jsonOutput) {
|
|
console.log(JSON.stringify(result));
|
|
} else if (result.success && result.message) {
|
|
log.info(colors.green(result.message));
|
|
} else if (!result.success && result.error) {
|
|
log.error(colors.red(result.error));
|
|
}
|
|
}
|
|
|
|
// Helper to normalize repository path by removing $res: prefix
|
|
export function normalizeRepoPath(path: string): string {
|
|
return path.replace(/^\$res:/, "");
|
|
}
|
|
|
|
// Helper to get or create branch configuration
|
|
export function getOrCreateBranchConfig(config: SyncOptions, branchName: string): {
|
|
config: SyncOptions;
|
|
branchKey: string;
|
|
} {
|
|
if (!config.gitBranches) {
|
|
config.gitBranches = {};
|
|
}
|
|
|
|
if (!config.gitBranches[branchName]) {
|
|
config.gitBranches[branchName] = {};
|
|
}
|
|
|
|
return {
|
|
config,
|
|
branchKey: branchName
|
|
};
|
|
}
|
|
|
|
// Helper to apply backend settings to branch configuration
|
|
export function applyBackendSettingsToBranch(
|
|
config: SyncOptions,
|
|
branchName: string,
|
|
backendSettings: SyncOptions
|
|
): SyncOptions {
|
|
const { config: updatedConfig } = getOrCreateBranchConfig(config, branchName);
|
|
|
|
// Get the base settings (top-level + defaults) to compare against
|
|
const { gitBranches, ...topLevelSettings } = config;
|
|
const baseSettings: Partial<SyncOptions> = { ...DEFAULT_SYNC_OPTIONS, ...topLevelSettings };
|
|
|
|
// Only store fields that differ from the base settings
|
|
Object.keys(backendSettings).forEach(key => {
|
|
if (key !== 'gitBranches' && backendSettings[key as keyof SyncOptions] !== undefined) {
|
|
const backendValue = backendSettings[key as keyof SyncOptions];
|
|
const baseValue = baseSettings[key as keyof SyncOptions];
|
|
|
|
// Only store if different from base
|
|
const isDifferent = GitSyncSettingsConverter.isDifferent(backendValue, baseValue);
|
|
|
|
if (isDifferent) {
|
|
if (!updatedConfig.gitBranches![branchName].overrides) {
|
|
updatedConfig.gitBranches![branchName].overrides = {};
|
|
}
|
|
(updatedConfig.gitBranches![branchName].overrides as any)[key] = backendValue;
|
|
}
|
|
}
|
|
});
|
|
|
|
return updatedConfig;
|
|
}
|
|
|
|
// Select repository interactively if multiple exist
|
|
export async function selectAndLogRepository(
|
|
repositories: GitSyncRepository[],
|
|
repository?: string,
|
|
suppressLogs?: boolean,
|
|
): Promise<GitSyncRepository> {
|
|
let selectedRepo: GitSyncRepository;
|
|
|
|
if (repository) {
|
|
const found = repositories.find(
|
|
(r: GitSyncRepository) =>
|
|
r.git_repo_resource_path === repository ||
|
|
r.git_repo_resource_path === `$res:${repository}`,
|
|
);
|
|
if (!found) {
|
|
throw new Error(`Repository ${repository} not found`);
|
|
}
|
|
selectedRepo = found;
|
|
const repoPath = selectedRepo.git_repo_resource_path.replace(/^\$res:/, "");
|
|
if (!suppressLogs) {
|
|
log.info(colors.cyan(`Using repository: ${colors.bold(repoPath)}`));
|
|
}
|
|
} else {
|
|
selectedRepo = await selectRepository(repositories);
|
|
}
|
|
|
|
return selectedRepo;
|
|
}
|
|
|
|
// Generate structured diff showing field changes
|
|
export function generateStructuredDiff(
|
|
current: any,
|
|
backend: any,
|
|
): { [key: string]: { from: any; to: any } } {
|
|
const diff: { [key: string]: { from: any; to: any } } = {};
|
|
|
|
// Get all unique keys from both objects
|
|
const allKeys = new Set([...Object.keys(current), ...Object.keys(backend)]);
|
|
|
|
for (const key of allKeys) {
|
|
const currentValue = current[key];
|
|
const backendValue = backend[key];
|
|
|
|
if (!deepEqual(currentValue, backendValue)) {
|
|
diff[key] = {
|
|
from: currentValue,
|
|
to: backendValue,
|
|
};
|
|
}
|
|
}
|
|
|
|
return diff;
|
|
}
|
|
|
|
// Helper to generate changes between two SyncOptions objects (normalizes automatically)
|
|
export function generateChanges(
|
|
current: SyncOptions,
|
|
new_: SyncOptions,
|
|
): { [key: string]: { from: any; to: any } } {
|
|
const changes: { [key: string]: { from: any; to: any } } = {};
|
|
|
|
// Normalize both inputs for consistent comparison
|
|
const normalizedCurrent = GitSyncSettingsConverter.normalize(current);
|
|
const normalizedNew = GitSyncSettingsConverter.normalize(new_);
|
|
|
|
for (const field of GIT_SYNC_FIELDS) {
|
|
const currentValue = (normalizedCurrent as any)[field];
|
|
const newValue = (normalizedNew as any)[field];
|
|
if (!deepEqual(currentValue, newValue)) {
|
|
changes[field] = {
|
|
from: currentValue,
|
|
to: newValue,
|
|
};
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
// Helper to display changes in human-readable format
|
|
export function displayChanges(
|
|
changes: { [key: string]: { from: any; to: any } },
|
|
): void {
|
|
for (const [field, change] of Object.entries(changes)) {
|
|
if (
|
|
Array.isArray(change.from) ||
|
|
Array.isArray(change.to)
|
|
) {
|
|
console.log(colors.yellow(` ${field}:`));
|
|
console.log(
|
|
colors.red(
|
|
` - ${JSON.stringify(change.from)}`,
|
|
),
|
|
);
|
|
console.log(
|
|
colors.green(
|
|
` + ${JSON.stringify(change.to)}`,
|
|
),
|
|
);
|
|
} else {
|
|
console.log(
|
|
colors.yellow(` ${field}: `) +
|
|
colors.red(`${change.from}`) +
|
|
" → " +
|
|
colors.green(`${change.to}`),
|
|
);
|
|
}
|
|
}
|
|
}
|