Files
windmill/cli/src/commands/gitsync-settings/utils.ts
T
Alexander Petricandellipsis-dev[bot] 919bf6bd5c feat: CLI improvements (#6446)
* feat: branch specific items for cli

* error on wmill.yaml parsing errors

* also search for wmill.yaml in parent dirs when git

* git_branches -> gitBranches

* Update cli/src/core/specific_items.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* sanitize branch name (regex + fs path)

* improve sanitatino

* robust relative paths

* hubpath

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-08-23 02:45:00 +01:00

189 lines
5.5 KiB
TypeScript

import { colors, log } from "../../../deps.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}`),
);
}
}
}