// deno-lint-ignore-file no-explicit-any import { Diff, SEP, colors, log, path, yamlParseContent, yamlStringify, } from "./deps.ts"; import { pushApp } from "./apps.ts"; import { pushFolder } from "./folder.ts"; import { pushFlow } from "./flow.ts"; import { pushResource } from "./resource.ts"; import { pushResourceType } from "./resource-type.ts"; import { pushVariable } from "./variable.ts"; import { yamlOptions } from "./sync.ts"; import { showDiffs } from "./main.ts"; import { deepEqual, isFileResource } from "./utils.ts"; import { pushSchedule } from "./schedule.ts"; import { pushWorkspaceUser } from "./user.ts"; import { pushGroup } from "./user.ts"; import { pushWorkspaceSettings, pushWorkspaceKey } from "./settings.ts"; export interface DifferenceCreate { type: "CREATE"; path: (string | number)[]; value: any; } export interface DifferenceRemove { type: "REMOVE"; path: (string | number)[]; oldValue: any; } export interface DifferenceChange { type: "CHANGE"; path: (string | number)[]; value: any; oldValue: any; } export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange; export type GlobalOptions = { baseUrl: string | undefined; workspace: string | undefined; token: string | undefined; }; export function isSuperset( subset: Record, superset: Record ): boolean { return Object.keys(subset).every((key) => { const eq = deepEqual(subset[key], superset[key]); if (!eq && showDiffs) { const sub = subset[key]; const supers = superset[key]; if (!supers) { log.info(`Key ${key} not found in remote`); } else { log.info(`Found diff for ${key}:`); showDiff( yamlStringify(sub, yamlOptions), yamlStringify(supers, yamlOptions) ); } } return eq; }); } export function showDiff(local: string, remote: string) { let finalString = ""; if (local?.length > 20000 || remote?.length > 20000) { log.info("Diff too large to display"); return; } for (const part of Diff.diffLines(local, remote)) { if (part.removed) { // print red if removed without newline finalString += `\x1b[31m${part.value}\x1b[0m`; } else if (part.added) { // print green if added finalString += `\x1b[32m${part.value}\x1b[0m`; } else { let lines = part.value.split("\n"); if (lines.length > 4) { lines = lines.slice(0, 2); lines.push("..."); lines = lines.concat(part.value.split("\n").slice(-2)); } // print white if unchanged finalString += `\x1b[37m${lines.join("\n")}\x1b[0m`; } } log.info(finalString); } export function showConflict(path: string, local: string, remote: string) { log.info(colors.yellow(`- ${path}`)); showDiff(local, remote); log.info("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m"); log.info("\n"); } export async function pushObj( workspace: string, p: string, befObj: any, newObj: any, plainSecrets: boolean, alreadySynced: string[], message?: string ) { const typeEnding = getTypeStrFromPath(p); if (typeEnding === "app") { const appName = p.split(".app" + SEP)[0]; await pushApp(workspace, appName, appName + ".app", message); } else if (typeEnding === "folder") { await pushFolder(workspace, p, befObj, newObj); } else if (typeEnding === "variable") { await pushVariable(workspace, p, befObj, newObj, plainSecrets); } else if (typeEnding === "flow") { const flowName = p.split(".flow" + SEP)[0]; await pushFlow(workspace, flowName, flowName + ".flow", message); } else if (typeEnding === "resource") { if (!alreadySynced.includes(p)) { alreadySynced.push(p); await pushResource(workspace, p, befObj, newObj); } } else if (typeEnding === "resource-type") { await pushResourceType(workspace, p, befObj, newObj); } else if (typeEnding === "schedule") { await pushSchedule(workspace, p, befObj, newObj); } else if (typeEnding === "user") { await pushWorkspaceUser(workspace, p, befObj, newObj); } else if (typeEnding === "group") { await pushGroup(workspace, p, befObj, newObj); } else if (typeEnding === "settings") { await pushWorkspaceSettings(workspace, p, befObj, newObj); } else if (typeEnding === "encryption_key") { await pushWorkspaceKey(workspace, p, befObj, newObj); } else { throw new Error( `The item ${p} has an unrecognized type ending ${typeEnding}` ); } } export function parseFromPath(p: string, content: string): any { return p.endsWith(".yaml") ? yamlParseContent(p, content) : p.endsWith(".json") ? JSON.parse(content) : content; } export function parseFromFile(p: string): any { if (p.endsWith(".json")) { return JSON.parse(Deno.readTextFileSync(p)); } else if (p.endsWith(".yaml") || p.endsWith(".yml")) { return yamlParseContent(p, Deno.readTextFileSync(p)); } else { throw new Error("Could not read file " + p); } } export function getTypeStrFromPath( p: string ): | "script" | "variable" | "flow" | "resource" | "resource-type" | "folder" | "app" | "schedule" | "user" | "group" | "settings" | "encryption_key" { if (p.includes(".flow" + SEP)) { return "flow"; } if (p.includes(".app" + SEP)) { return "app"; } const parsed = path.parse(p); if ( parsed.ext == ".go" || parsed.ext == ".ts" || parsed.ext == ".sh" || parsed.ext == ".py" || parsed.ext == ".sql" || parsed.ext == ".gql" || parsed.ext == ".ps1" || parsed.ext == ".js" || parsed.ext == ".php" || parsed.ext == ".rs" || (parsed.ext == ".yml" && parsed.name.split(".").pop() == "playbook") ) { return "script"; } if (parsed.name === "folder.meta") { return "folder"; } const typeEnding = parsed.name.split(".").at(-1); if ( typeEnding === "script" || typeEnding === "variable" || typeEnding === "resource" || typeEnding === "resource-type" || typeEnding === "app" || typeEnding === "schedule" || typeEnding === "user" || typeEnding === "group" || typeEnding === "settings" || typeEnding === "encryption_key" ) { return typeEnding; } else { if (isFileResource(p)) { return "resource"; } throw new Error("Could not infer type of path " + JSON.stringify(parsed)); } } export function removeType(str: string, type: string) { if ( !str.endsWith("." + type + ".yaml") && !str.endsWith("." + type + ".json") ) { throw new Error(str + " does not end with ." + type + ".(yaml|json)"); } return str.slice(0, str.length - type.length - 6); } export function removePathPrefix(str: string, prefix: string) { if (!str.startsWith(prefix + "/")) { throw new Error(str + " does not start with " + prefix); } return str.slice(prefix.length + 1); }