/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import { formatPath, type ProjectionNode, type VariableDefinition } from "./session.js"; import type { FieldInfo, ArgInfo, TypeInfo, WalkerResult, InputWalkerResult, SearchMatcher, } from "./walker.js"; import { filterDataCloudFields, parseSearchTerms, parseSearchRegex } from "./walker.js"; export { parseSearchTerms, parseSearchRegex, type SearchMatcher }; export interface TypeAnnotations { /** Per-field comment lines keyed by field name. */ fieldComments?: Map; /** For filter types: example JSON expressions. */ filterExamples?: string[]; /** For mutation inputs: nested input types to inline after the parent. */ inlinedInputs?: { info: TypeInfo; annotations: TypeAnnotations }[]; /** For mutation inputs: sample JSON variable. */ sampleInput?: string; } export interface AliasEntry { alias: string; fieldName: string; argCount: number; isActive: boolean; } function resolveSearchMatcher( searchPattern?: string, regexPattern?: string, ): SearchMatcher | undefined { if (regexPattern) return parseSearchRegex(regexPattern); if (searchPattern) return parseSearchTerms(searchPattern); return undefined; } export function formatDirectoryListing( result: WalkerResult, opts: { searchPattern?: string; regexPattern?: string; maxFields?: number; long?: boolean; all?: boolean; hasArgs?: boolean; aliases?: AliasEntry[]; selectedFields?: Set; optionalFields?: Set; fieldLongInfo?: Map; dataCloud?: boolean; } = {}, ): string { const { searchPattern, regexPattern, maxFields = 20, long = false, all = false, hasArgs = false, aliases = [], selectedFields, optionalFields, fieldLongInfo, dataCloud = false, } = opts; const matcher = resolveSearchMatcher(searchPattern, regexPattern); const isFiltering = !!matcher; const lines: string[] = []; if (result.isLeaf) { lines.push("Leaf node. Use `select ` from the parent directory to project it."); return lines.join("\n"); } if (hasArgs) { if (long) { lines.push( ` ${"@args/".padEnd(28)} args (${result.args.length} argument${result.args.length !== 1 ? "s" : ""})`, ); } else { lines.push(" @args/"); } } for (const a of aliases) { const entry = `${a.alias}/`; if (long) { const active = a.isActive ? "*" : " "; const argPart = a.argCount > 0 ? ` [${a.argCount} arg${a.argCount !== 1 ? "s" : ""}]` : ""; lines.push(`${active} ${entry.padEnd(28)} alias ${a.fieldName}${argPart}`); } else { lines.push(` ${entry}`); } } const dataCloudHidden = dataCloud ? 0 : result.fields.length - filterDataCloudFields(result.fields, false).length; let fields = filterDataCloudFields(result.fields, dataCloud); if (matcher) { fields = fields.filter((f) => matcher.test(f.name)); } const displayFields = isFiltering || all ? fields : fields.slice(0, maxFields); const fragments = matcher ? result.possibleTypes.filter((typeName) => matcher.test(typeName)) : result.possibleTypes; for (const field of displayFields) { lines.push( formatFieldLine( field, long, selectedFields?.has(field.name), fieldLongInfo?.get(field.name), optionalFields?.has(field.name), ), ); } if (!isFiltering && !all && fields.length > maxFields) { lines.push( ` ... ${fields.length - maxFields} more (use -a to show all, or --search to filter)`, ); } if (dataCloudHidden > 0) { lines.push(` (${dataCloudHidden} Data Cloud fields hidden — use --data-cloud to show)`); } if (result.mutationHiddenFields.length > 0 && long) { let hiddenFields = result.mutationHiddenFields; if (matcher) { hiddenFields = hiddenFields.filter((f) => matcher.test(f.name)); } for (const field of hiddenFields) { const name = `${field.name}/`; lines.push(` ${name.padEnd(28)} dir ${field.typeName} [query-only]`); } } for (const typeName of fragments) { if (long) { lines.push(` ${`on:${typeName}/`.padEnd(28)} frag ${typeName}`); } else { lines.push(` on:${typeName}/`); } } return lines.join("\n"); } export function formatArgs(args: ArgInfo[]): string { if (args.length === 0) return ""; const lines = ["Args:"]; for (const arg of args) { let line = ` ${arg.name}: ${arg.typeName}`; if (arg.enumValues) { line += ` -- ${arg.enumValues.join(" | ")}`; } if (arg.description && !arg.enumValues) { const shortDesc = arg.description.length > 50 ? arg.description.slice(0, 47) + "..." : arg.description; line += ` -- ${shortDesc}`; } lines.push(line); } return lines.join("\n"); } export function formatArgsDirectoryListing( args: ArgInfo[], long: boolean, currentValues?: Record, searchPattern?: string, regexPattern?: string, ): string { if (args.length === 0) return "No arguments available."; let filtered = args; const matcher = resolveSearchMatcher(searchPattern, regexPattern); if (matcher) { filtered = filtered.filter((a) => matcher.test(a.name)); } const lines: string[] = []; for (const arg of filtered) { const isLeaf = arg.typeKind === "SCALAR" || arg.typeKind === "ENUM"; const suffix = isLeaf ? "" : "/"; const hasValue = currentValues?.[arg.name] !== undefined; const mark = hasValue ? "*" : " "; const name = arg.name + suffix; if (!long) { lines.push(`${mark} ${name}`); continue; } const valStr = hasValue ? ` = ${truncate(currentValues[arg.name], 40)}` : ""; let enumHint = ""; if (arg.enumValues && arg.enumValues.length > 0) { enumHint = ` [${arg.enumValues.join(", ")}]`; } const reqTag = arg.isNonNull ? " [required]" : ""; lines.push(`${mark} ${name.padEnd(24)} ${arg.typeName}${reqTag}${valStr}${enumHint}`); } return lines.join("\n"); } export function formatInputDirectoryListing( inputResult: InputWalkerResult, long: boolean, currentValues?: Record, listElementCount?: number, searchPattern?: string, regexPattern?: string, ): string { const lines: string[] = []; if (inputResult.isList) { if (listElementCount === undefined || listElementCount === 0) { lines.push(" (empty list — use `mkdir` to add an element)"); } else { for (let i = 0; i < listElementCount; i++) { const val = currentValues?.[String(i)]; const hasVal = val !== undefined; const mark = hasVal ? "*" : " "; const name = String(i) + "/"; if (long) { const valStr = hasVal ? ` = ${truncate(JSON.stringify(val), 40)}` : ""; lines.push(`${mark} ${name.padEnd(24)}${valStr}`); } else { lines.push(`${mark} ${name}`); } } } return lines.join("\n"); } if (inputResult.inputFields.length === 0 && inputResult.enumValues.length > 0) { lines.push(`Enum values: ${inputResult.enumValues.map((e) => e.name).join(", ")}`); return lines.join("\n"); } let fields = inputResult.inputFields; const matcher = resolveSearchMatcher(searchPattern, regexPattern); if (matcher) { fields = fields.filter((f) => matcher.test(f.name)); } for (const field of fields) { const isLeaf = field.typeKind === "SCALAR" || field.typeKind === "ENUM"; const suffix = isLeaf ? "" : "/"; const val = currentValues?.[field.name]; const hasVal = val !== undefined; const mark = hasVal ? "*" : " "; const name = field.name + suffix; if (!long) { lines.push(`${mark} ${name}`); continue; } const valStr = hasVal ? ` = ${truncate(JSON.stringify(val), 40)}` : ""; let enumHint = ""; if (field.enumValues && field.enumValues.length > 0) { enumHint = ` [${field.enumValues.join(", ")}]`; } const reqTag = field.isNonNull ? " [required]" : ""; lines.push(`${mark} ${name.padEnd(24)} ${field.typeName}${reqTag}${valStr}${enumHint}`); } return lines.join("\n"); } export function formatVariablesListing(variables: VariableDefinition[], long: boolean): string { if (variables.length === 0) return " (no variables defined — use `define $name Type` to create one)"; const lines: string[] = []; for (const v of variables) { const isScalar = !v.type.replace(/[![\]]/g, "").match(/Input|Where|OrderBy|Filter/i); const suffix = isScalar ? "" : "/"; const hasValue = v.runtimeValue !== undefined || v.defaultValue !== undefined; const mark = hasValue ? "*" : " "; const name = `$${v.name}${suffix}`; if (!long) { lines.push(`${mark} ${name}`); continue; } let detail = name.padEnd(24); detail += ` ${v.type}`; if (v.defaultValue !== undefined) detail += ` default=${truncate(v.defaultValue, 30)}`; if (v.runtimeValue !== undefined) detail += ` value=${truncate(v.runtimeValue, 30)}`; lines.push(`${mark} ${detail}`); } return lines.join("\n"); } function truncate(s: string, max: number): string { return s.length <= max ? s : s.slice(0, max - 3) + "..."; } export function formatNavigationPath(pathSegments: string[]): string { return `Path: ${formatPath(pathSegments)}`; } export function formatInstanceSummary( currentPath: string[], instances: ProjectionNode[], activeInstanceId?: string, ): string { if (currentPath.length === 0 || instances.length === 0) { return "Projection instances: none"; } const items = instances.map((instance) => { const marker = instance.id === activeInstanceId ? "*" : " "; if (instance.kind === "fragment") { return `${marker} [${instance.onType}] (${instance.id})`; } const aliasPart = instance.alias ? `${instance.alias}: ` : ""; const argCount = Object.keys(instance.args).length; return `${marker} ${aliasPart}${instance.fieldName} (${instance.id}${argCount > 0 ? `, ${argCount} arg${argCount === 1 ? "" : "s"}` : ""})`; }); return ["Projection instances:", ...items.map((item) => ` ${item}`)].join("\n"); } export function formatTypeInfo( info: TypeInfo, annotations?: TypeAnnotations, opts: { dataCloud?: boolean } = {}, ): string { const lines: string[] = []; switch (info.kind) { case "OBJECT": case "INTERFACE": { const keyword = info.kind === "INTERFACE" ? "interface" : "type"; const impl = info.interfaces.length > 0 ? ` implements ${info.interfaces.join(" & ")}` : ""; if (info.description) { lines.push(`"""${info.description}"""`); } lines.push(`${keyword} ${info.name}${impl} {`); const displayFields = filterDataCloudFields(info.fields, !!opts.dataCloud); const hiddenCount = info.fields.length - displayFields.length; for (const field of displayFields) { if (field.description) { lines.push(` """${field.description}"""`); } let fieldLine = ` ${field.name}`; if (field.args.length > 0) { const argParts = field.args.map((a) => `${a.name}: ${a.typeName}`); fieldLine += `(${argParts.join(", ")})`; } fieldLine += `: ${field.typeName}`; const comments = annotations?.fieldComments?.get(field.name); if (comments && comments.length > 0) { fieldLine += ` # ${comments.join(" | ")}`; } lines.push(fieldLine); } lines.push("}"); if (hiddenCount > 0) { lines.push(` (${hiddenCount} Data Cloud fields hidden — use --data-cloud to show)`); } if (info.possibleTypes.length > 0) { lines.push(""); lines.push(`Possible types: ${info.possibleTypes.join(", ")}`); } break; } case "INPUT_OBJECT": { formatInputObject(info, annotations, lines); break; } case "ENUM": { if (info.description) { lines.push(`"""${info.description}"""`); } lines.push(`enum ${info.name} {`); for (const val of info.enumValues) { if (val.description) { lines.push(` """${val.description}"""`); } lines.push(` ${val.name}`); } lines.push("}"); break; } case "UNION": { if (info.description) { lines.push(`"""${info.description}"""`); } lines.push(`union ${info.name} = ${info.possibleTypes.join(" | ")}`); break; } case "SCALAR": { if (info.description) { lines.push(`"""${info.description}"""`); } lines.push(`scalar ${info.name}`); break; } } return lines.join("\n"); } function formatInputObject( info: TypeInfo, annotations: TypeAnnotations | undefined, lines: string[], ): void { if (info.description) { lines.push(`"""${info.description}"""`); } lines.push(`input ${info.name} {`); for (const field of info.inputFields) { if (field.description) { lines.push(` """${field.description}"""`); } let fieldLine = ` ${field.name}: ${field.typeName}`; const comments = annotations?.fieldComments?.get(field.name); if (comments && comments.length > 0) { fieldLine += ` # ${comments.join(" | ")}`; } lines.push(fieldLine); } lines.push("}"); if (annotations?.filterExamples && annotations.filterExamples.length > 0) { lines.push(""); for (const ex of annotations.filterExamples) { lines.push(`# example: ${ex}`); } } if (annotations?.inlinedInputs) { for (const { info: nested, annotations: nestedAnn } of annotations.inlinedInputs) { lines.push(""); formatInputObject(nested, nestedAnn, lines); if (nestedAnn.sampleInput) { lines.push(""); lines.push("# sample:"); for (const sampleLine of nestedAnn.sampleInput.split("\n")) { lines.push(`# ${sampleLine}`); } } } } } export function formatValidationErrors(errors: { message: string }[]): string { if (errors.length === 0) return "Query is valid."; const lines = [`${errors.length} validation error${errors.length === 1 ? "" : "s"}:`]; errors.forEach((e, i) => { lines.push(` ${i + 1}. ${e.message}`); }); return lines.join("\n"); } export interface FieldLongInfo { required: boolean; createable: boolean; updateable: boolean; defaultedOnCreate: boolean; filterable: boolean; sortable: boolean; referenceTargets?: string[]; referenceNameFields?: string[]; childRelTarget?: string; picklistValues?: string[]; label?: string; dataType?: string; nameField?: boolean; compound?: boolean; compoundFieldName?: string; extraTypeInfo?: string; calculated?: boolean; custom?: boolean; inlineHelpText?: string; precision?: number; scale?: number; } function formatFieldLine( field: FieldInfo, long: boolean, selected?: boolean, fieldLong?: FieldLongInfo | null, hasOptional?: boolean, ): string { const isDirectory = field.typeKind !== "SCALAR" && field.typeKind !== "ENUM"; const suffix = isDirectory ? "/" : ""; const optionalMark = hasOptional ? "?" : ""; const selectedMark = selected ? "*" : " "; const base = `${selectedMark}${optionalMark}${optionalMark ? "" : " "}${field.name}${suffix}`; if (!long) { return base; } const kind = isDirectory ? "dir " : "leaf"; const argHint = field.args.length > 0 ? ` args=${field.args.map((arg) => arg.name).join(",")}` : ""; let annotations = ""; if (fieldLong) { // Label (when different from API name) const labelPart = fieldLong.label && fieldLong.label !== field.name ? ` "${fieldLong.label}"` : ""; const tags: string[] = []; if (fieldLong.required && !fieldLong.defaultedOnCreate) tags.push("required"); else if (fieldLong.required && fieldLong.defaultedOnCreate) tags.push("defaulted"); if (fieldLong.nameField) tags.push("name-field"); if (!fieldLong.createable && !fieldLong.updateable) tags.push("read-only"); else if (fieldLong.createable && !fieldLong.updateable) tags.push("create-only"); else if (!fieldLong.createable) tags.push("no-create"); if (fieldLong.defaultedOnCreate && fieldLong.createable) tags.push("default-on-create"); if (!fieldLong.filterable) tags.push("no-filter"); if (!fieldLong.sortable) tags.push("no-sort"); if (fieldLong.calculated) tags.push("formula"); if (fieldLong.custom) tags.push("custom"); if (fieldLong.compound) tags.push("compound"); if (fieldLong.compoundFieldName) tags.push(`child of ${fieldLong.compoundFieldName}`); const tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : ""; let extra = ""; if (fieldLong.referenceTargets && fieldLong.referenceTargets.length > 0) { const nameHint = fieldLong.referenceNameFields && fieldLong.referenceNameFields.length > 0 ? ` (${fieldLong.referenceNameFields.join(", ")})` : ""; extra += ` -> ${fieldLong.referenceTargets.join(", ")}${nameHint}`; } if (fieldLong.childRelTarget) { extra += ` child -> ${fieldLong.childRelTarget}`; } if (fieldLong.picklistValues && fieldLong.picklistValues.length > 0) { if (fieldLong.picklistValues.length <= 8) { extra += ` [${fieldLong.picklistValues.join(", ")}]`; } else { extra += ` [${fieldLong.picklistValues.slice(0, 6).join(", ")}, ... +${fieldLong.picklistValues.length - 6}]`; } } if (fieldLong.extraTypeInfo) { extra += ` (${fieldLong.extraTypeInfo})`; } if (fieldLong.inlineHelpText) { extra += ` -- ${fieldLong.inlineHelpText}`; } annotations = `${labelPart}${tagStr}${extra}`; } return `${base.padEnd(28)} ${kind} ${field.typeName}${argHint}${annotations}`; }