π File detail
utils/treeify.ts
π§© .tsπ 171 linesπΎ 5,033 bytesπ text
β Back to All Filesπ― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes TreeNode, TreeifyOptions, and treeify β mainly types, interfaces, or factory objects. Dependencies touch figures. It composes internal code from components and theme (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import figures from 'figures' import { color } from '../components/design-system/color.js' import type { Theme, ThemeName } from './theme.js' export type TreeNode = {
π€ Exports (heuristic)
TreeNodeTreeifyOptionstreeify
π External import roots
Package roots from from "β¦" (relative paths omitted).
figures
π₯οΈ Source preview
import figures from 'figures'
import { color } from '../components/design-system/color.js'
import type { Theme, ThemeName } from './theme.js'
export type TreeNode = {
[key: string]: TreeNode | string | undefined
}
export type TreeifyOptions = {
showValues?: boolean
hideFunctions?: boolean
useColors?: boolean
themeName?: ThemeName
treeCharColors?: {
treeChar?: keyof Theme // Color for tree characters (β β β)
key?: keyof Theme // Color for property names
value?: keyof Theme // Color for values
}
}
type TreeCharacters = {
branch: string
lastBranch: string
line: string
empty: string
}
const DEFAULT_TREE_CHARS: TreeCharacters = {
branch: figures.lineUpDownRight, // 'β'
lastBranch: figures.lineUpRight, // 'β'
line: figures.lineVertical, // 'β'
empty: ' ',
}
/**
* Custom treeify implementation with Ink theme color support
* Based on https://github.com/notatestuser/treeify
*/
export function treeify(obj: TreeNode, options: TreeifyOptions = {}): string {
const {
showValues = true,
hideFunctions = false,
themeName = 'dark',
treeCharColors = {},
} = options
const lines: string[] = []
const visited = new WeakSet<object>()
function colorize(text: string, colorKey?: keyof Theme): string {
if (!colorKey) return text
return color(colorKey, themeName)(text)
}
function growBranch(
node: TreeNode | string,
prefix: string,
_isLast: boolean,
depth: number = 0,
): void {
if (typeof node === 'string') {
lines.push(prefix + colorize(node, treeCharColors.value))
return
}
if (typeof node !== 'object' || node === null) {
if (showValues) {
const valueStr = String(node)
lines.push(prefix + colorize(valueStr, treeCharColors.value))
}
return
}
// Check for circular references
if (visited.has(node)) {
lines.push(prefix + colorize('[Circular]', treeCharColors.value))
return
}
visited.add(node)
const keys = Object.keys(node).filter(key => {
const value = node[key]
if (hideFunctions && typeof value === 'function') return false
return true
})
keys.forEach((key, index) => {
const value = node[key]
const isLastKey = index === keys.length - 1
const nodePrefix = depth === 0 && index === 0 ? '' : prefix
// Determine which tree character to use
const treeChar = isLastKey
? DEFAULT_TREE_CHARS.lastBranch
: DEFAULT_TREE_CHARS.branch
const coloredTreeChar = colorize(treeChar, treeCharColors.treeChar)
const coloredKey =
key.trim() === '' ? '' : colorize(key, treeCharColors.key)
let line =
nodePrefix + coloredTreeChar + (coloredKey ? ' ' + coloredKey : '')
// Check if we should add a colon (not for empty/whitespace keys)
const shouldAddColon = key.trim() !== ''
// Check for circular reference before recursing
if (value && typeof value === 'object' && visited.has(value)) {
const coloredValue = colorize('[Circular]', treeCharColors.value)
lines.push(
line + (shouldAddColon ? ': ' : line ? ' ' : '') + coloredValue,
)
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
lines.push(line)
// Calculate the continuation prefix for nested items
const continuationChar = isLastKey
? DEFAULT_TREE_CHARS.empty
: DEFAULT_TREE_CHARS.line
const coloredContinuation = colorize(
continuationChar,
treeCharColors.treeChar,
)
const nextPrefix = nodePrefix + coloredContinuation + ' '
growBranch(value, nextPrefix, isLastKey, depth + 1)
} else if (Array.isArray(value)) {
// Handle arrays
lines.push(
line +
(shouldAddColon ? ': ' : line ? ' ' : '') +
'[Array(' +
value.length +
')]',
)
} else if (showValues) {
// Add value if showValues is true
const valueStr =
typeof value === 'function' ? '[Function]' : String(value)
const coloredValue = colorize(valueStr, treeCharColors.value)
line += (shouldAddColon ? ': ' : line ? ' ' : '') + coloredValue
lines.push(line)
} else {
lines.push(line)
}
})
}
// Start growing the tree
const keys = Object.keys(obj)
if (keys.length === 0) {
return colorize('(empty)', treeCharColors.value)
}
// Special case for single empty/whitespace string key
if (
keys.length === 1 &&
keys[0] !== undefined &&
keys[0].trim() === '' &&
typeof obj[keys[0]] === 'string'
) {
const firstKey = keys[0]
const coloredTreeChar = colorize(
DEFAULT_TREE_CHARS.lastBranch,
treeCharColors.treeChar,
)
const coloredValue = colorize(obj[firstKey] as string, treeCharColors.value)
return coloredTreeChar + ' ' + coloredValue
}
growBranch(obj, '', true)
return lines.join('\n')
}