@oh-my-pi-zen/pi-utils 16.3.6-zen.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/README.md +36 -0
  3. package/dist/types/abortable.d.ts +32 -0
  4. package/dist/types/async.d.ts +6 -0
  5. package/dist/types/binary.d.ts +17 -0
  6. package/dist/types/cli.d.ts +117 -0
  7. package/dist/types/color.d.ts +102 -0
  8. package/dist/types/dirs.d.ts +260 -0
  9. package/dist/types/env.d.ts +74 -0
  10. package/dist/types/fetch-retry.d.ts +94 -0
  11. package/dist/types/format.d.ts +37 -0
  12. package/dist/types/frontmatter.d.ts +25 -0
  13. package/dist/types/fs-error.d.ts +31 -0
  14. package/dist/types/glob.d.ts +28 -0
  15. package/dist/types/index.d.ts +35 -0
  16. package/dist/types/json-parse.d.ts +48 -0
  17. package/dist/types/json.d.ts +4 -0
  18. package/dist/types/logger.d.ts +83 -0
  19. package/dist/types/loop-phase.d.ts +10 -0
  20. package/dist/types/mermaid-ascii.d.ts +11 -0
  21. package/dist/types/mime.d.ts +29 -0
  22. package/dist/types/module-timer.d.ts +1 -0
  23. package/dist/types/path-tree.d.ts +76 -0
  24. package/dist/types/path.d.ts +2 -0
  25. package/dist/types/peek-file.d.ts +29 -0
  26. package/dist/types/postmortem.d.ts +37 -0
  27. package/dist/types/procmgr.d.ts +29 -0
  28. package/dist/types/prompt.d.ts +18 -0
  29. package/dist/types/ptree.d.ts +108 -0
  30. package/dist/types/ring.d.ts +93 -0
  31. package/dist/types/runtime-install.d.ts +68 -0
  32. package/dist/types/sanitize-text.d.ts +29 -0
  33. package/dist/types/snowflake.d.ts +25 -0
  34. package/dist/types/stream.d.ts +68 -0
  35. package/dist/types/tab-spacing.d.ts +24 -0
  36. package/dist/types/temp.d.ts +17 -0
  37. package/dist/types/timing-buffer.d.ts +22 -0
  38. package/dist/types/tls-fetch.d.ts +37 -0
  39. package/dist/types/type-guards.d.ts +3 -0
  40. package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
  41. package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
  42. package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
  43. package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
  44. package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
  45. package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
  46. package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
  47. package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
  48. package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
  49. package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
  50. package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
  51. package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
  52. package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
  53. package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
  54. package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
  55. package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
  56. package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
  57. package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
  58. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
  59. package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
  60. package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
  61. package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
  62. package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
  63. package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
  64. package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
  65. package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
  66. package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
  67. package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
  68. package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
  69. package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
  70. package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
  71. package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
  72. package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
  73. package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
  74. package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
  75. package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
  76. package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
  77. package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
  78. package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
  79. package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
  80. package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
  81. package/dist/types/which.d.ts +37 -0
  82. package/dist/types/worker-host.d.ts +47 -0
  83. package/package.json +62 -0
  84. package/src/abortable.ts +109 -0
  85. package/src/async.ts +50 -0
  86. package/src/binary.ts +50 -0
  87. package/src/cli.ts +457 -0
  88. package/src/color.ts +302 -0
  89. package/src/dirs.ts +925 -0
  90. package/src/env.ts +226 -0
  91. package/src/fetch-retry.ts +355 -0
  92. package/src/format.ts +113 -0
  93. package/src/frontmatter.ts +162 -0
  94. package/src/fs-error.ts +56 -0
  95. package/src/glob.ts +189 -0
  96. package/src/index.ts +55 -0
  97. package/src/json-parse.ts +612 -0
  98. package/src/json.ts +10 -0
  99. package/src/logger.ts +672 -0
  100. package/src/loop-phase.ts +49 -0
  101. package/src/mermaid-ascii.ts +31 -0
  102. package/src/mime.ts +159 -0
  103. package/src/module-timer.ts +148 -0
  104. package/src/path-tree.ts +147 -0
  105. package/src/path.ts +28 -0
  106. package/src/peek-file.ts +188 -0
  107. package/src/postmortem.ts +222 -0
  108. package/src/procmgr.ts +195 -0
  109. package/src/prompt.ts +562 -0
  110. package/src/ptree.ts +395 -0
  111. package/src/ring.ts +169 -0
  112. package/src/runtime-install.ts +372 -0
  113. package/src/sanitize-text.ts +96 -0
  114. package/src/snowflake.ts +121 -0
  115. package/src/stream.ts +432 -0
  116. package/src/tab-spacing.ts +336 -0
  117. package/src/temp.ts +134 -0
  118. package/src/timing-buffer.ts +47 -0
  119. package/src/tls-fetch.ts +178 -0
  120. package/src/type-guards.ts +11 -0
  121. package/src/vendor/mermaid-ascii/NOTICE +33 -0
  122. package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
  123. package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
  124. package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
  125. package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
  126. package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
  127. package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
  128. package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
  129. package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
  130. package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
  131. package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
  132. package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
  133. package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +215 -0
  134. package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
  135. package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
  136. package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
  137. package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
  138. package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
  139. package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
  140. package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
  141. package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
  142. package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
  143. package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
  144. package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
  145. package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
  146. package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
  147. package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
  148. package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
  149. package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
  150. package/src/vendor/mermaid-ascii/class/types.ts +121 -0
  151. package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
  152. package/src/vendor/mermaid-ascii/er/types.ts +91 -0
  153. package/src/vendor/mermaid-ascii/index.ts +14 -0
  154. package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
  155. package/src/vendor/mermaid-ascii/parser.ts +645 -0
  156. package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
  157. package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
  158. package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
  159. package/src/vendor/mermaid-ascii/types.ts +164 -0
  160. package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
  161. package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
  162. package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
  163. package/src/which.ts +232 -0
  164. package/src/worker-host.ts +92 -0
@@ -0,0 +1,140 @@
1
+ // ============================================================================
2
+ // XY Chart — shared color palette
3
+ //
4
+ // Generates monochromatic shades from the theme accent color.
5
+ // Series 0 = accent (or blue fallback). Series 1+ are darker/lighter
6
+ // shades of the same hue with subtle hue drift to stay in the same
7
+ // color family (like navy ↔ cyan from blue).
8
+ //
9
+ // Used by both the SVG and ASCII renderers.
10
+ // ============================================================================
11
+
12
+ /** Default accent for charts when the theme doesn't provide one. */
13
+ export const CHART_ACCENT_FALLBACK = '#3b82f6' // blue-500
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // HSL ↔ Hex conversion
17
+ // ---------------------------------------------------------------------------
18
+
19
+ function hexToHsl(hex: string): [number, number, number] {
20
+ const h = hex.replace('#', '')
21
+ const ri = parseInt(h.substring(0, 2), 16) / 255
22
+ const gi = parseInt(h.substring(2, 4), 16) / 255
23
+ const bi = parseInt(h.substring(4, 6), 16) / 255
24
+
25
+ const max = Math.max(ri, gi, bi)
26
+ const min = Math.min(ri, gi, bi)
27
+ const l = (max + min) / 2
28
+
29
+ if (max === min) return [0, 0, l * 100]
30
+
31
+ const d = max - min
32
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
33
+
34
+ let hue: number
35
+ if (max === ri) hue = ((gi - bi) / d + (gi < bi ? 6 : 0)) / 6
36
+ else if (max === gi) hue = ((bi - ri) / d + 2) / 6
37
+ else hue = ((ri - gi) / d + 4) / 6
38
+
39
+ return [hue * 360, s * 100, l * 100]
40
+ }
41
+
42
+ function hslToHex(h: number, s: number, l: number): string {
43
+ const si = s / 100
44
+ const li = l / 100
45
+
46
+ const c = (1 - Math.abs(2 * li - 1)) * si
47
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
48
+ const m = li - c / 2
49
+
50
+ let r: number, g: number, b: number
51
+ if (h < 60) { r = c; g = x; b = 0 }
52
+ else if (h < 120) { r = x; g = c; b = 0 }
53
+ else if (h < 180) { r = 0; g = c; b = x }
54
+ else if (h < 240) { r = 0; g = x; b = c }
55
+ else if (h < 300) { r = x; g = 0; b = c }
56
+ else { r = c; g = 0; b = x }
57
+
58
+ const toHex = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, '0')
59
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Hex ↔ RGB conversion
64
+ // ---------------------------------------------------------------------------
65
+
66
+ function hexToRgb(hex: string): [number, number, number] {
67
+ const h = hex.replace('#', '')
68
+ return [
69
+ parseInt(h.substring(0, 2), 16),
70
+ parseInt(h.substring(2, 4), 16),
71
+ parseInt(h.substring(4, 6), 16),
72
+ ]
73
+ }
74
+
75
+ function rgbToHex(r: number, g: number, b: number): string {
76
+ const toHex = (v: number) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')
77
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Public API
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /** Check whether a string is a valid 6-digit hex color (e.g. "#3b82f6"). */
85
+ export function isValidHex(color: string): boolean {
86
+ return /^#[0-9a-fA-F]{6}$/.test(color)
87
+ }
88
+
89
+ /**
90
+ * Detect whether a background color is dark (lightness < 50%).
91
+ */
92
+ export function isDarkBackground(bgHex: string): boolean {
93
+ return hexToHsl(bgHex)[2] < 50
94
+ }
95
+
96
+ /**
97
+ * Mix two hex colors in RGB space.
98
+ * `ratio` controls how much of `fgHex` shows: 0 = pure bg, 1 = pure fg.
99
+ * Equivalent to alpha-compositing fg over bg at the given opacity.
100
+ */
101
+ export function mixHexColors(bgHex: string, fgHex: string, ratio: number): string {
102
+ const [br, bg, bb] = hexToRgb(bgHex)
103
+ const [fr, fg, fb] = hexToRgb(fgHex)
104
+ const inv = 1 - ratio
105
+ return rgbToHex(br * inv + fr * ratio, bg * inv + fg * ratio, bb * inv + fb * ratio)
106
+ }
107
+
108
+ /**
109
+ * Get the hex color for a series index.
110
+ * Index 0 returns the accent color as-is.
111
+ * Index 1+ alternate between darker and lighter shades of the same hue
112
+ * with subtle hue drift (±8-12° per tier) to stay in the same family.
113
+ *
114
+ * When `bgColor` is provided, shade direction adapts to the background:
115
+ * - Light bg: odd = darker, even = lighter (default)
116
+ * - Dark bg: odd = lighter, even = darker (so shades stay visible)
117
+ */
118
+ export function getSeriesColor(index: number, accentColor: string, bgColor?: string): string {
119
+ if (index === 0) return accentColor
120
+ // Fall back to defaults when inputs aren't valid hex (e.g. CSS variable refs like "var(--accent)")
121
+ const safeAccent = isValidHex(accentColor) ? accentColor : CHART_ACCENT_FALLBACK
122
+ const safeBg = bgColor && isValidHex(bgColor) ? bgColor : undefined
123
+ const [h, s] = hexToHsl(safeAccent)
124
+ const chartS = Math.max(55, Math.min(85, s))
125
+
126
+ const tier = Math.ceil(index / 2)
127
+ const oddIndex = index % 2 === 1
128
+
129
+ // On dark backgrounds, flip: odd = lighter, even = darker
130
+ const dark = safeBg && isDarkBackground(safeBg) ? !oddIndex : oddIndex
131
+ const l = dark
132
+ ? Math.max(25, 48 - tier * 13)
133
+ : Math.min(78, 55 + tier * 11)
134
+
135
+ // Subtle hue drift: darker shades shift slightly negative, lighter shift positive
136
+ const hShift = (dark ? -8 : 12) * tier
137
+ const newH = ((h + hShift) % 360 + 360) % 360
138
+
139
+ return hslToHex(newH, chartS, l)
140
+ }
@@ -0,0 +1,115 @@
1
+ import type { XYChart, XYAxis, XYChartSeries } from './types'
2
+
3
+ // ============================================================================
4
+ // XY Chart parser
5
+ //
6
+ // Parses Mermaid xychart-beta syntax into a typed XYChart structure.
7
+ //
8
+ // Supported directives:
9
+ // xychart-beta [horizontal]
10
+ // title "Chart Title"
11
+ // x-axis [label1, label2, ...] — categorical
12
+ // x-axis min --> max — numeric range
13
+ // x-axis "Axis Title" [label1, ...] — with title
14
+ // x-axis "Axis Title" min --> max — with title
15
+ // y-axis (same patterns)
16
+ // bar [val1, val2, ...]
17
+ // line [val1, val2, ...]
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Parse a Mermaid xychart-beta diagram from preprocessed lines.
22
+ * Lines should already be trimmed and comment-stripped.
23
+ */
24
+ export function parseXYChart(lines: string[]): XYChart {
25
+ const xAxis: XYAxis = {}
26
+ const yAxis: XYAxis = {}
27
+ const series: XYChartSeries[] = []
28
+ let title: string | undefined
29
+ let horizontal = false
30
+
31
+ for (const line of lines) {
32
+ // Header line — detect horizontal
33
+ if (/^xychart(-beta)?\b/i.test(line)) {
34
+ if (/\bhorizontal\b/i.test(line)) horizontal = true
35
+ continue
36
+ }
37
+
38
+ // Title
39
+ const titleMatch = line.match(/^title\s+"([^"]+)"/)
40
+ if (titleMatch) {
41
+ title = titleMatch[1]
42
+ continue
43
+ }
44
+
45
+ // x-axis with categories: x-axis "Title" [a, b, c] or x-axis [a, b, c]
46
+ const xCatMatch = line.match(/^x-axis\s+(?:"([^"]*)"\s*)?\[([^\]]+)\]/)
47
+ if (xCatMatch) {
48
+ if (xCatMatch[1]) xAxis.title = xCatMatch[1]
49
+ xAxis.categories = xCatMatch[2]!.split(',').map(s => s.trim())
50
+ continue
51
+ }
52
+
53
+ // x-axis with range: x-axis "Title" min --> max or x-axis min --> max
54
+ const xRangeMatch = line.match(/^x-axis\s+(?:"([^"]*)"\s+)?(-?\d+(?:\.\d+)?)\s*-->\s*(-?\d+(?:\.\d+)?)/)
55
+ if (xRangeMatch) {
56
+ if (xRangeMatch[1]) xAxis.title = xRangeMatch[1]
57
+ xAxis.range = { min: parseFloat(xRangeMatch[2]!), max: parseFloat(xRangeMatch[3]!) }
58
+ continue
59
+ }
60
+
61
+ // y-axis with range: y-axis "Title" min --> max or y-axis min --> max
62
+ const yRangeMatch = line.match(/^y-axis\s+(?:"([^"]*)"\s+)?(-?\d+(?:\.\d+)?)\s*-->\s*(-?\d+(?:\.\d+)?)/)
63
+ if (yRangeMatch) {
64
+ if (yRangeMatch[1]) yAxis.title = yRangeMatch[1]
65
+ yAxis.range = { min: parseFloat(yRangeMatch[2]!), max: parseFloat(yRangeMatch[3]!) }
66
+ continue
67
+ }
68
+
69
+ // y-axis with just title (no range)
70
+ const yTitleOnly = line.match(/^y-axis\s+"([^"]+)"\s*$/)
71
+ if (yTitleOnly) {
72
+ yAxis.title = yTitleOnly[1]
73
+ continue
74
+ }
75
+
76
+ // bar [...]
77
+ const barMatch = line.match(/^bar\s+\[([^\]]+)\]/)
78
+ if (barMatch) {
79
+ series.push({ type: 'bar', data: parseNumericArray(barMatch[1]!) })
80
+ continue
81
+ }
82
+
83
+ // line [...]
84
+ const lineMatch = line.match(/^line\s+\[([^\]]+)\]/)
85
+ if (lineMatch) {
86
+ series.push({ type: 'line', data: parseNumericArray(lineMatch[1]!) })
87
+ continue
88
+ }
89
+ }
90
+
91
+ // Auto-derive y-axis range from data if not specified
92
+ if (!yAxis.range && series.length > 0) {
93
+ const allValues = series.flatMap(s => s.data)
94
+ let min = Math.min(...allValues)
95
+ let max = Math.max(...allValues)
96
+ const span = max - min || 1
97
+ // Add 10% padding
98
+ min = min - span * 0.1
99
+ max = max + span * 0.1
100
+ // Floor to 0 if all values are positive and min is close to 0
101
+ if (min > 0 && min < span * 0.5) min = 0
102
+ yAxis.range = { min, max }
103
+ }
104
+
105
+ // Fallback y-axis range
106
+ if (!yAxis.range) {
107
+ yAxis.range = { min: 0, max: 100 }
108
+ }
109
+
110
+ return { title, horizontal, xAxis, yAxis, series }
111
+ }
112
+
113
+ function parseNumericArray(str: string): number[] {
114
+ return str.split(',').map(s => parseFloat(s.trim()))
115
+ }
@@ -0,0 +1,150 @@
1
+ // ============================================================================
2
+ // XY Chart types
3
+ //
4
+ // Models the parsed and positioned representations of a Mermaid xychart-beta
5
+ // diagram. Supports bar charts, line charts, and combinations with categorical
6
+ // or numeric x-axes.
7
+ // ============================================================================
8
+
9
+ /** Parsed XY chart — logical structure from mermaid text */
10
+ export interface XYChart {
11
+ /** Optional chart title */
12
+ title?: string
13
+ /** Chart orientation: vertical (default) or horizontal */
14
+ horizontal: boolean
15
+ /** X-axis configuration */
16
+ xAxis: XYAxis
17
+ /** Y-axis configuration */
18
+ yAxis: XYAxis
19
+ /** Data series (bar and/or line) */
20
+ series: XYChartSeries[]
21
+ }
22
+
23
+ /** Axis configuration — categorical (labels) or numeric (range) */
24
+ export interface XYAxis {
25
+ /** Optional axis title/label */
26
+ title?: string
27
+ /** Categorical labels (e.g., ["jan", "feb", "mar"]) — mutually exclusive with range */
28
+ categories?: string[]
29
+ /** Numeric range — mutually exclusive with categories */
30
+ range?: { min: number; max: number }
31
+ }
32
+
33
+ /** A single data series (bar or line) */
34
+ export interface XYChartSeries {
35
+ /** Series type */
36
+ type: 'bar' | 'line'
37
+ /** Data values — one per category, or evenly spaced across numeric range */
38
+ data: number[]
39
+ }
40
+
41
+ // ============================================================================
42
+ // Positioned XY chart — ready for SVG rendering
43
+ // ============================================================================
44
+
45
+ export interface PositionedXYChart {
46
+ width: number
47
+ height: number
48
+ /** Whether this is a horizontal (rotated) chart */
49
+ horizontal?: boolean
50
+ /** Title text and position (if present) */
51
+ title?: PositionedTitle
52
+ /** Positioned x-axis with tick marks and labels */
53
+ xAxis: PositionedAxis
54
+ /** Positioned y-axis with tick marks and labels */
55
+ yAxis: PositionedAxis
56
+ /** The plot area bounds (inside axes) */
57
+ plotArea: PlotArea
58
+ /** Positioned bar groups */
59
+ bars: PositionedBar[]
60
+ /** Positioned line polylines */
61
+ lines: PositionedLine[]
62
+ /** Horizontal grid lines for readability */
63
+ gridLines: GridLine[]
64
+ /** Legend items (shown when multiple series) */
65
+ legend: LegendItem[]
66
+ }
67
+
68
+ export interface LegendItem {
69
+ /** Display label */
70
+ label: string
71
+ /** Position of the swatch/icon */
72
+ x: number
73
+ y: number
74
+ /** Series type determines swatch shape (rect for bar, line+dot for line) */
75
+ type: 'bar' | 'line'
76
+ /** Series index within its type (for layout grouping) */
77
+ seriesIndex: number
78
+ /** Global color index across all series (for unified color assignment) */
79
+ colorIndex: number
80
+ }
81
+
82
+ export interface PositionedTitle {
83
+ text: string
84
+ x: number
85
+ y: number
86
+ }
87
+
88
+ export interface PositionedAxis {
89
+ /** Optional axis title text and position */
90
+ title?: { text: string; x: number; y: number; rotate?: number }
91
+ /** Tick positions along the axis */
92
+ ticks: AxisTick[]
93
+ /** Axis line: start and end coordinates */
94
+ line: { x1: number; y1: number; x2: number; y2: number }
95
+ }
96
+
97
+ export interface AxisTick {
98
+ /** Label text for this tick */
99
+ label: string
100
+ /** Position of the tick mark on the axis */
101
+ x: number
102
+ y: number
103
+ /** End of the tick mark (short perpendicular line) */
104
+ tx: number
105
+ ty: number
106
+ /** Label anchor position */
107
+ labelX: number
108
+ labelY: number
109
+ /** Text anchor for label */
110
+ textAnchor: 'start' | 'middle' | 'end'
111
+ }
112
+
113
+ export interface PlotArea {
114
+ x: number
115
+ y: number
116
+ width: number
117
+ height: number
118
+ }
119
+
120
+ export interface PositionedBar {
121
+ /** Bar rectangle in SVG coordinates */
122
+ x: number
123
+ y: number
124
+ width: number
125
+ height: number
126
+ /** Original data value */
127
+ value: number
128
+ /** Category label for this bar (e.g. "Jan") */
129
+ label?: string
130
+ /** Series index within bar type (for layout grouping) */
131
+ seriesIndex: number
132
+ /** Global color index across all series */
133
+ colorIndex: number
134
+ }
135
+
136
+ export interface PositionedLine {
137
+ /** Polyline points */
138
+ points: Array<{ x: number; y: number; value: number; label?: string }>
139
+ /** Series index within line type (for layout grouping) */
140
+ seriesIndex: number
141
+ /** Global color index across all series */
142
+ colorIndex: number
143
+ }
144
+
145
+ export interface GridLine {
146
+ x1: number
147
+ y1: number
148
+ x2: number
149
+ y2: number
150
+ }
package/src/which.ts ADDED
@@ -0,0 +1,232 @@
1
+ // OS-agnostic "which" helper with robust macOS toolchain lookup and flexible cache control.
2
+ //
3
+ // - Falls back to macOS Xcode/CLT toolchain directories if standard `Bun.which()` fails on Darwin.
4
+ // Resolves the active developer directory via $DEVELOPER_DIR / /var/db/xcode_select_link symlink
5
+ // to avoid spawning xcrun subprocesses.
6
+ // - Supports four cache modes (`none`, `fresh`, `ro`, `cached`) for control over discovery cost and determinism.
7
+ // - Computes a stable cache key from command + options to avoid redundant lookups within a process.
8
+ // - Returns path to resolved binary or null if not found.
9
+ //
10
+
11
+ import * as fs from "node:fs";
12
+ import * as os from "node:os";
13
+ import * as path from "node:path";
14
+
15
+ type CacheKey = string | bigint | number;
16
+
17
+ // Tools shipped by Xcode / Command Line Tools that callers actually look up.
18
+ // Keeps the set small so darwinWhich can fast-reject non-Xcode commands without
19
+ // touching the filesystem. Only needs entries for binaries that live *exclusively*
20
+ // in toolchain dirs (not on a typical $PATH).
21
+ const XCODE_BINS = new Set([
22
+ // Compilers & driver aliases
23
+ "clang",
24
+ "clang++",
25
+ "gcc",
26
+ "g++",
27
+ "cc",
28
+ "c++",
29
+ "cpp",
30
+ "c89",
31
+ "c99",
32
+ "swift",
33
+ "swiftc",
34
+ "swift-frontend",
35
+ // Language servers (LSP)
36
+ "clangd",
37
+ "sourcekit-lsp",
38
+ // Linker & archive tools
39
+ "ld",
40
+ "ld-classic",
41
+ "ar",
42
+ "ranlib",
43
+ "libtool",
44
+ "as",
45
+ "lipo",
46
+ "install_name_tool",
47
+ "codesign_allocate",
48
+ // Build utilities
49
+ "make",
50
+ "gnumake",
51
+ "m4",
52
+ "flex",
53
+ "bison",
54
+ "yacc",
55
+ "lex",
56
+ // VCS (CLT ships git)
57
+ "git",
58
+ "git-receive-pack",
59
+ "git-upload-pack",
60
+ "git-upload-archive",
61
+ "git-shell",
62
+ "scalar",
63
+ // Debugger
64
+ "lldb",
65
+ "lldb-dap",
66
+ // Binary inspection
67
+ "nm",
68
+ "otool",
69
+ "objdump",
70
+ "strings",
71
+ "strip",
72
+ "size",
73
+ "dsymutil",
74
+ "dwarfdump",
75
+ "lipo",
76
+ "vtool",
77
+ // Clang tooling
78
+ "clang-format",
79
+ "swift-format",
80
+ ]);
81
+
82
+ // Prefixes for versioned binaries (e.g. python3.9, pip3.12, pydoc3.9, 2to3-3.9)
83
+ const XCODE_BIN_PREFIXES = ["python", "pip", "pydoc", "2to3"];
84
+
85
+ function isXcodeBin(command: string): boolean {
86
+ if (XCODE_BINS.has(command)) return true;
87
+ for (const prefix of XCODE_BIN_PREFIXES) {
88
+ if (command.startsWith(prefix)) return true;
89
+ }
90
+ return false;
91
+ }
92
+
93
+ // Resolve the active Xcode developer directory once, without spawning any process.
94
+ // Priority: $DEVELOPER_DIR env → /var/db/xcode_select_link symlink → common fallback paths.
95
+ function getDeveloperDirs(): string | null {
96
+ // 1. Explicit env override
97
+ const envDir = process.env.DEVELOPER_DIR;
98
+ if (envDir && fs.existsSync(envDir)) {
99
+ return envDir;
100
+ }
101
+
102
+ // 2. xcode-select stores the active path as a symlink
103
+ try {
104
+ return fs.readlinkSync("/var/db/xcode_select_link");
105
+ } catch {
106
+ // symlink may not exist on minimal installs
107
+ }
108
+ // 3. Common locations
109
+ for (const candidate of ["/Applications/Xcode.app/Contents/Developer", "/Library/Developer/CommandLineTools"]) {
110
+ if (fs.existsSync(candidate)) {
111
+ return candidate;
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ // Build the list of extra toolchain bin directories to check on macOS.
118
+ // Computed lazily once from the resolved developer directory.
119
+ let macosToolPaths: Map<string, string> | undefined;
120
+ function getMacosToolPaths(): Map<string, string> {
121
+ if (macosToolPaths) return macosToolPaths;
122
+ const paths: string[] = [
123
+ // Always check Command Line Tools (may be independent of Xcode)
124
+ "/Library/Developer/CommandLineTools/usr/bin",
125
+ ];
126
+ const devDir = getDeveloperDirs();
127
+ if (devDir) {
128
+ paths.push(path.join(devDir, "usr/bin"), path.join(devDir, "Toolchains/XcodeDefault.xctoolchain/usr/bin"));
129
+ }
130
+
131
+ // Deduplicate (e.g. devDir may already be CommandLineTools)
132
+ macosToolPaths = new Map<string, string>();
133
+ for (const dir of Array.from(new Set(paths))) {
134
+ try {
135
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
136
+ if (entry.isFile() || entry.isSymbolicLink()) {
137
+ if (macosToolPaths.has(entry.name)) {
138
+ continue;
139
+ }
140
+ macosToolPaths.set(entry.name, path.join(dir, entry.name));
141
+ }
142
+ }
143
+ } catch {
144
+ // dir doesn't exist or isn't readable
145
+ }
146
+ }
147
+ return macosToolPaths;
148
+ }
149
+
150
+ // Map: cache key -> resolved binary path or null (not found)
151
+ const toolCache = new Map<CacheKey, string | null>();
152
+
153
+ /**
154
+ * Cache policy for which lookups.
155
+ */
156
+ export const enum WhichCachePolicy {
157
+ /**
158
+ * Use cached result if available.
159
+ */
160
+ Cached = 0,
161
+ /**
162
+ * Bypass cache and perform a new lookup.
163
+ */
164
+ Bypass,
165
+ /**
166
+ * Always update cache.
167
+ */
168
+ Fresh,
169
+ /**
170
+ * Read-only, serves from cache if present, but doesn't write.
171
+ */
172
+ ReadOnly,
173
+ }
174
+
175
+ // Extension: additional cache policy for tool path lookup
176
+ export interface WhichOptions extends Bun.WhichOptions {
177
+ /**
178
+ * Cache policy for the lookup.
179
+ * Defaults to `WhichCachePolicy.Fresh`.
180
+ */
181
+ cache?: WhichCachePolicy;
182
+ }
183
+
184
+ // Darwin-specific "which" shim: consult Xcode/CLT toolchain directories after $PATH.
185
+ // Uses cached directory listings instead of per-command existsSync or xcrun subprocesses.
186
+ function darwinWhich(command: string, _options?: Bun.WhichOptions): string | null {
187
+ const regular = Bun.which(command);
188
+ if (regular) return regular;
189
+ if (isXcodeBin(command)) {
190
+ return getMacosToolPaths().get(command) ?? null;
191
+ }
192
+ return null;
193
+ }
194
+
195
+ // Which function that incorporates Darwin Xcode logic if platform reports as 'darwin'
196
+ export const whichFresh = os.platform() === "darwin" ? darwinWhich : Bun.which;
197
+
198
+ // Derive stable cache key from command and lookup options
199
+ function cacheKey(command: string, options?: Bun.WhichOptions): CacheKey {
200
+ if (!options) return command;
201
+ if (!options.cwd && !options.PATH) return command;
202
+ let h = Bun.hash(command);
203
+ if (options.cwd) h = Bun.hash(options.cwd, h);
204
+ if (options.PATH) h = Bun.hash(options.PATH, h);
205
+ return h;
206
+ }
207
+
208
+ /**
209
+ * Locate binary on PATH (with flexible caching).
210
+ *
211
+ * @param command - Binary name to resolve
212
+ * @param options - Bun.WhichOptions plus `cache` control
213
+ * @returns Filesystem path if found, else null
214
+ */
215
+ export function $which(command: string, options?: WhichOptions): string | null {
216
+ const cachePolicy = options?.cache ?? WhichCachePolicy.Cached;
217
+ let key: CacheKey | undefined;
218
+
219
+ if (cachePolicy !== WhichCachePolicy.Bypass) {
220
+ key = cacheKey(command, options);
221
+ if (cachePolicy !== WhichCachePolicy.Fresh) {
222
+ const cached = toolCache.get(key);
223
+ if (cached !== undefined) return cached;
224
+ }
225
+ }
226
+
227
+ const result = whichFresh(command, options);
228
+ if (key != null && cachePolicy !== WhichCachePolicy.ReadOnly) {
229
+ toolCache.set(key, result);
230
+ }
231
+ return result;
232
+ }