@emeryld/rrroutes-export 1.0.19 → 1.0.21

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emeryld/rrroutes-export",
3
3
  "description": "Finalized leaves export helpers and CLI for RRRoutes",
4
- "version": "1.0.19",
4
+ "version": "1.0.21",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "dist/index.cjs",
@@ -378,6 +378,22 @@
378
378
  gap: 4px;
379
379
  }
380
380
 
381
+ .changelog-commit-separator {
382
+ margin-top: 12px;
383
+ padding-top: 10px;
384
+ border-top: 1px solid var(--accent);
385
+ }
386
+
387
+ .changelog-commit-separator:first-child {
388
+ margin-top: 0;
389
+ }
390
+
391
+ .changelog-commit-label {
392
+ color: var(--accent);
393
+ font-weight: 700;
394
+ font-size: 12px;
395
+ }
396
+
381
397
  .changelog-diff-line {
382
398
  display: flex;
383
399
  flex-wrap: wrap;
@@ -403,6 +419,45 @@
403
419
  color: var(--schema-accent);
404
420
  }
405
421
 
422
+ .event-title-row {
423
+ display: inline-flex;
424
+ flex-wrap: wrap;
425
+ gap: 8px;
426
+ align-items: center;
427
+ }
428
+
429
+ .event-action-added {
430
+ color: var(--ok);
431
+ font-weight: 700;
432
+ }
433
+
434
+ .event-action-removed {
435
+ color: var(--danger);
436
+ }
437
+
438
+ .event-action-modified {
439
+ color: var(--accent);
440
+ font-weight: 700;
441
+ }
442
+
443
+ .event-icon {
444
+ display: inline-flex;
445
+ width: 14px;
446
+ height: 14px;
447
+ color: var(--muted);
448
+ }
449
+
450
+ .event-icon svg {
451
+ width: 14px;
452
+ height: 14px;
453
+ display: block;
454
+ }
455
+
456
+ .event-name {
457
+ font-weight: 700;
458
+ color: var(--text);
459
+ }
460
+
406
461
  .schema-block {
407
462
  border-top: 1px solid var(--border);
408
463
  padding: 8px 0 2px;
@@ -1652,7 +1707,12 @@
1652
1707
 
1653
1708
  const routeChangelogBody = el('div', 'leaf-content')
1654
1709
  routeHistoryNewestFirst.forEach((event) => {
1655
- routeChangelogBody.appendChild(renderEventCard(event, commitBySha))
1710
+ routeChangelogBody.appendChild(
1711
+ renderEventCard(event, commitBySha, {
1712
+ showTitle: true,
1713
+ item: { eventKind: 'route', routeKey: leaf.key, event },
1714
+ }),
1715
+ )
1656
1716
  })
1657
1717
  routeChangelogDetails.appendChild(routeChangelogBody)
1658
1718
  routeChangelogSection.appendChild(routeChangelogDetails)
@@ -1714,21 +1774,96 @@
1714
1774
  return `${type}${schemaFlags(Boolean(entry.nullable), Boolean(entry.optional))}`
1715
1775
  }
1716
1776
 
1717
- function formatPathLabel(path, entry) {
1777
+ function splitTypeOptions(type) {
1778
+ if (typeof type !== 'string' || !type.includes('|')) return []
1779
+ return type
1780
+ .split('|')
1781
+ .map((item) => item.trim())
1782
+ .filter(Boolean)
1783
+ }
1784
+
1785
+ function hasDefinedLiteral(entry) {
1786
+ return (
1787
+ entry &&
1788
+ typeof entry === 'object' &&
1789
+ Object.prototype.hasOwnProperty.call(entry, 'literal') &&
1790
+ entry.literal !== undefined
1791
+ )
1792
+ }
1793
+
1794
+ function reduceEnumTypeDiff(beforeEntry, afterEntry) {
1795
+ if (!beforeEntry || !afterEntry) return null
1796
+ if (hasDefinedLiteral(beforeEntry) || hasDefinedLiteral(afterEntry)) {
1797
+ return null
1798
+ }
1799
+
1800
+ const beforeOptions = splitTypeOptions(beforeEntry.type)
1801
+ const afterOptions = splitTypeOptions(afterEntry.type)
1802
+ if (beforeOptions.length === 0 || afterOptions.length === 0) return null
1803
+
1804
+ const afterSet = new Set(afterOptions)
1805
+ const beforeSet = new Set(beforeOptions)
1806
+ const beforeOnly = beforeOptions.filter((option) => !afterSet.has(option))
1807
+ const afterOnly = afterOptions.filter((option) => !beforeSet.has(option))
1808
+ if (beforeOnly.length === 0 && afterOnly.length === 0) return null
1809
+
1810
+ const beforeType = `${beforeOnly.join('|') || 'none'}${schemaFlags(
1811
+ Boolean(beforeEntry.nullable),
1812
+ Boolean(beforeEntry.optional),
1813
+ )}`
1814
+ const afterType = `${afterOnly.join('|') || 'none'}${schemaFlags(
1815
+ Boolean(afterEntry.nullable),
1816
+ Boolean(afterEntry.optional),
1817
+ )}`
1818
+ return { beforeType, afterType }
1819
+ }
1820
+
1821
+ function formatPathLabel(path) {
1718
1822
  const basePath = typeof path === 'string' ? path : ''
1719
- return `${basePath}${schemaFlags(Boolean(entry?.nullable), Boolean(entry?.optional))}`
1823
+ return basePath
1720
1824
  }
1721
1825
 
1722
- function parseSignatureTypeLabel(signature) {
1826
+ function parseSignatureEntry(signature) {
1723
1827
  if (typeof signature !== 'string') return null
1724
1828
  const typeMatch = signature.match(/(?:^|,\s*)type=([^,]+)/)
1725
- const nullableMatch = signature.match(/(?:^|,\s*)nullable=(true|false)/)
1726
- const optionalMatch = signature.match(/(?:^|,\s*)optional=(true|false)/)
1727
- if (!typeMatch) return null
1728
- const type = typeMatch[1]
1729
- const nullable = nullableMatch?.[1] === 'true'
1730
- const optional = optionalMatch?.[1] === 'true'
1731
- return `${type}${schemaFlags(nullable, optional)}`
1829
+ if (typeMatch) {
1830
+ const nullableMatch = signature.match(
1831
+ /(?:^|,\s*)nullable=(true|false)/,
1832
+ )
1833
+ const optionalMatch = signature.match(
1834
+ /(?:^|,\s*)optional=(true|false)/,
1835
+ )
1836
+ return {
1837
+ type: typeMatch[1],
1838
+ nullable: nullableMatch?.[1] === 'true',
1839
+ optional: optionalMatch?.[1] === 'true',
1840
+ literal: undefined,
1841
+ }
1842
+ }
1843
+ try {
1844
+ const parsed = JSON.parse(signature)
1845
+ if (!parsed || typeof parsed !== 'object') return null
1846
+ if (typeof parsed.type !== 'string') return null
1847
+ return {
1848
+ type: parsed.type,
1849
+ nullable: Boolean(parsed.nullable),
1850
+ optional: Boolean(parsed.optional),
1851
+ literal: Object.prototype.hasOwnProperty.call(parsed, 'literal')
1852
+ ? parsed.literal
1853
+ : undefined,
1854
+ }
1855
+ } catch {
1856
+ return null
1857
+ }
1858
+ }
1859
+
1860
+ function parseSignatureTypeLabel(signature) {
1861
+ const entry = parseSignatureEntry(signature)
1862
+ if (!entry) return null
1863
+ return `${entry.type}${schemaFlags(
1864
+ Boolean(entry.nullable),
1865
+ Boolean(entry.optional),
1866
+ )}`
1732
1867
  }
1733
1868
 
1734
1869
  function formatSourceTypeLabel(signatures) {
@@ -1740,6 +1875,18 @@
1740
1875
  return labels.join(' | ')
1741
1876
  }
1742
1877
 
1878
+ function formatSourceChangedTypes(beforeSignatures, afterSignatures) {
1879
+ if (!Array.isArray(beforeSignatures) || !Array.isArray(afterSignatures)) {
1880
+ return null
1881
+ }
1882
+ if (beforeSignatures.length !== 1 || afterSignatures.length !== 1) {
1883
+ return null
1884
+ }
1885
+ const beforeEntry = parseSignatureEntry(beforeSignatures[0])
1886
+ const afterEntry = parseSignatureEntry(afterSignatures[0])
1887
+ return reduceEnumTypeDiff(beforeEntry, afterEntry)
1888
+ }
1889
+
1743
1890
  function schemaDiffOperation(op) {
1744
1891
  if (op === 'added') {
1745
1892
  return { symbol: '+', word: 'added', className: 'added' }
@@ -1795,8 +1942,9 @@
1795
1942
  `removed ${location}: ${JSON.stringify(delta.before || [])}`,
1796
1943
  )
1797
1944
  }
1798
- const beforeType = formatSourceTypeLabel(delta.before)
1799
- const afterType = formatSourceTypeLabel(delta.after)
1945
+ const reducedTypes = formatSourceChangedTypes(delta.before, delta.after)
1946
+ const beforeType = reducedTypes?.beforeType || formatSourceTypeLabel(delta.before)
1947
+ const afterType = reducedTypes?.afterType || formatSourceTypeLabel(delta.after)
1800
1948
  if (beforeType || afterType) {
1801
1949
  return buildSchemaDiffLine(
1802
1950
  delta.op,
@@ -1812,7 +1960,7 @@
1812
1960
  if ('path' in delta) {
1813
1961
  if (delta.op === 'added') {
1814
1962
  const afterType = formatEntryTypeLabel(delta.after)
1815
- const pathLabel = formatPathLabel(delta.path, delta.after)
1963
+ const pathLabel = formatPathLabel(delta.path)
1816
1964
  if (afterType) return buildSchemaDiffLine(delta.op, afterType, pathLabel)
1817
1965
  return buildLegacySchemaDiffLine(
1818
1966
  `added ${delta.path}: ${formatRouteSchemaEntry(delta.after)}`,
@@ -1820,17 +1968,20 @@
1820
1968
  }
1821
1969
  if (delta.op === 'removed') {
1822
1970
  const beforeType = formatEntryTypeLabel(delta.before)
1823
- const pathLabel = formatPathLabel(delta.path, delta.before)
1971
+ const pathLabel = formatPathLabel(delta.path)
1824
1972
  if (beforeType)
1825
1973
  return buildSchemaDiffLine(delta.op, beforeType, pathLabel)
1826
1974
  return buildLegacySchemaDiffLine(
1827
1975
  `removed ${delta.path}: ${formatRouteSchemaEntry(delta.before)}`,
1828
1976
  )
1829
1977
  }
1830
- const beforeType = formatEntryTypeLabel(delta.before) || 'unknown'
1831
- const afterType = formatEntryTypeLabel(delta.after) || 'unknown'
1978
+ const reducedTypes = reduceEnumTypeDiff(delta.before, delta.after)
1979
+ const beforeType =
1980
+ reducedTypes?.beforeType || formatEntryTypeLabel(delta.before) || 'unknown'
1981
+ const afterType =
1982
+ reducedTypes?.afterType || formatEntryTypeLabel(delta.after) || 'unknown'
1832
1983
  const pathLabel =
1833
- formatPathLabel(delta.path, delta.after || delta.before) || delta.path
1984
+ formatPathLabel(delta.path) || delta.path
1834
1985
  return buildSchemaDiffLine(delta.op, beforeType, pathLabel, afterType)
1835
1986
  }
1836
1987
  return null
@@ -1841,7 +1992,100 @@
1841
1992
  return `${diff.field}: ${JSON.stringify(diff.before)} -> ${JSON.stringify(diff.after)}`
1842
1993
  }
1843
1994
 
1844
- function renderEventCard(event, commitBySha) {
1995
+ const EVENT_ICON_SVGS = {
1996
+ route:
1997
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 13V3.5h11V13"/><path d="M8 3.5v9.5"/><path d="M5.5 6h.01M10.5 10h.01"/></svg>',
1998
+ source:
1999
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="8" cy="3.5" rx="4.8" ry="2.2"/><path d="M3.2 3.5v7c0 1.2 2.1 2.2 4.8 2.2s4.8-1 4.8-2.2v-7"/><path d="M3.2 7c0 1.2 2.1 2.2 4.8 2.2s4.8-1 4.8-2.2"/></svg>',
2000
+ schema:
2001
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.2L8 1.8l5.5 2.4L8 6.6 2.5 4.2Z"/><path d="M2.5 8L8 5.6 13.5 8 8 10.4 2.5 8Z"/><path d="M2.5 11.8 8 9.4l5.5 2.4L8 14.2l-5.5-2.4Z"/></svg>',
2002
+ config:
2003
+ '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12"/><path d="M2 8h12"/><path d="M2 12h12"/><circle cx="6" cy="4" r="1.6" fill="currentColor" stroke="none"/><circle cx="10.5" cy="8" r="1.6" fill="currentColor" stroke="none"/><circle cx="4.5" cy="12" r="1.6" fill="currentColor" stroke="none"/></svg>',
2004
+ }
2005
+
2006
+ function eventVisualByType(type) {
2007
+ switch (type) {
2008
+ case 'route_added':
2009
+ return {
2010
+ actionText: 'added route',
2011
+ actionClass: 'event-action-added',
2012
+ icon: 'route',
2013
+ }
2014
+ case 'route_removed':
2015
+ return {
2016
+ actionText: 'removed route',
2017
+ actionClass: 'event-action-removed',
2018
+ icon: 'route',
2019
+ }
2020
+ case 'schema_changed':
2021
+ return {
2022
+ actionText: 'modified schema',
2023
+ actionClass: 'event-action-modified',
2024
+ icon: 'schema',
2025
+ }
2026
+ case 'cfg_changed':
2027
+ return {
2028
+ actionText: 'modified config',
2029
+ actionClass: 'event-action-modified',
2030
+ icon: 'config',
2031
+ }
2032
+ case 'source_object_added':
2033
+ return {
2034
+ actionText: 'added source object',
2035
+ actionClass: 'event-action-added',
2036
+ icon: 'source',
2037
+ }
2038
+ case 'source_object_removed':
2039
+ return {
2040
+ actionText: 'removed source object',
2041
+ actionClass: 'event-action-removed',
2042
+ icon: 'source',
2043
+ }
2044
+ case 'source_schema_changed':
2045
+ return {
2046
+ actionText: 'modified schema',
2047
+ actionClass: 'event-action-modified',
2048
+ icon: 'schema',
2049
+ }
2050
+ default:
2051
+ return {
2052
+ actionText: 'modified',
2053
+ actionClass: 'event-action-modified',
2054
+ icon: 'schema',
2055
+ }
2056
+ }
2057
+ }
2058
+
2059
+ function resolveEventName(item) {
2060
+ if (!item || item.eventKind === 'source') {
2061
+ return item?.displayName || item?.sourceObjectId || 'source object'
2062
+ }
2063
+ const method =
2064
+ typeof item?.event?.method === 'string' ? item.event.method.trim() : ''
2065
+ const path =
2066
+ typeof item?.event?.path === 'string' ? item.event.path.trim() : ''
2067
+ if (method && path) return `${method.toUpperCase()} ${path}`
2068
+ if (path) return path
2069
+ return item?.routeKey || 'route'
2070
+ }
2071
+
2072
+ function renderEventIcon(iconType) {
2073
+ const icon = el('span', `event-icon event-icon-${iconType}`)
2074
+ icon.setAttribute('aria-hidden', 'true')
2075
+ icon.innerHTML = EVENT_ICON_SVGS[iconType] || EVENT_ICON_SVGS.schema
2076
+ return icon
2077
+ }
2078
+
2079
+ function renderEventTitle(item) {
2080
+ const row = el('div', 'event-title-row mono')
2081
+ const visual = eventVisualByType(item?.event?.type)
2082
+ row.appendChild(el('span', visual.actionClass, visual.actionText))
2083
+ row.appendChild(renderEventIcon(visual.icon))
2084
+ row.appendChild(el('span', 'event-name', `[${resolveEventName(item)}]`))
2085
+ return row
2086
+ }
2087
+
2088
+ function renderEventCard(event, commitBySha, options = {}) {
1845
2089
  const card = el('div', 'leaf-content')
1846
2090
  const commit = commitBySha.get(event.commitSha)
1847
2091
  const commitDateTime = formatCommitDateTime(commit?.authorDate)
@@ -1854,7 +2098,9 @@
1854
2098
  .filter(Boolean)
1855
2099
  .join(' - ')
1856
2100
  card.appendChild(el('div', 'meta', commitMeta))
1857
- card.appendChild(el('div', 'mono', event.type))
2101
+ if (options.showTitle && options.item) {
2102
+ card.appendChild(renderEventTitle(options.item))
2103
+ }
1858
2104
  if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1859
2105
  card.appendChild(
1860
2106
  el('div', 'meta', `schema changes: ${event.schemaDiff.length}`),
@@ -1907,12 +2153,41 @@
1907
2153
  .join(' - ')
1908
2154
  }
1909
2155
 
1910
- function renderChangelogEventLines(event, commitBySha, contextLabel) {
2156
+ function renderCommitSeparator(commitSha, commitBySha) {
2157
+ const box = el('div', 'changelog-commit-separator')
2158
+ const commit = commitBySha.get(commitSha)
2159
+ const commitDateTime = formatCommitDateTime(commit?.authorDate)
2160
+ const label = `commit ${String(commitSha || '').slice(0, 12)}`
2161
+ const meta = [commitDateTime, commit?.authorName || null, commit?.subject || null]
2162
+ .filter(Boolean)
2163
+ .join(' - ')
2164
+ box.appendChild(el('div', 'changelog-commit-label mono', label))
2165
+ if (meta) {
2166
+ box.appendChild(el('div', 'meta', meta))
2167
+ }
2168
+ return box
2169
+ }
2170
+
2171
+ function renderChangelogWithCommitSeparators(items, commitBySha, renderItem) {
2172
+ if (!Array.isArray(items) || items.length === 0) return
2173
+ let previousCommitSha = null
2174
+ items.forEach((item) => {
2175
+ const currentCommitSha = item?.event?.commitSha || null
2176
+ if (currentCommitSha !== previousCommitSha) {
2177
+ resultsEl.appendChild(renderCommitSeparator(currentCommitSha, commitBySha))
2178
+ previousCommitSha = currentCommitSha
2179
+ }
2180
+ resultsEl.appendChild(renderItem(item))
2181
+ })
2182
+ }
2183
+
2184
+ function renderChangelogEventLines(item, commitBySha) {
2185
+ const event = item.event
1911
2186
  const row = el('div', 'changelog-line-item')
1912
2187
  row.appendChild(
1913
2188
  el('div', 'meta', buildCommitMetaLine(event, commitBySha)),
1914
2189
  )
1915
- row.appendChild(el('div', 'mono', `${contextLabel} (${event.type})`))
2190
+ row.appendChild(renderEventTitle(item))
1916
2191
 
1917
2192
  if (Array.isArray(event.schemaDiff) && event.schemaDiff.length > 0) {
1918
2193
  event.schemaDiff.forEach((delta) => {
@@ -2115,34 +2390,26 @@
2115
2390
  commit,
2116
2391
  ]),
2117
2392
  )
2118
- if (state.viewerMode === VIEWER_MODE.changelog) {
2119
- renderCollection(
2120
- filtered,
2121
- (item) => {
2122
- const contextLabel =
2123
- item.eventKind === 'route'
2124
- ? `Route: ${item.routeKey}`
2125
- : `Source: ${item.displayName || item.sourceObjectId}`
2126
- return renderChangelogEventLines(
2127
- item.event,
2128
- commitBySha,
2129
- contextLabel,
2130
- )
2131
- },
2132
- 'No matches.',
2393
+ if (filtered.length === 0) {
2394
+ resultsEl.appendChild(el('div', 'empty', 'No matches.'))
2395
+ } else if (state.viewerMode === VIEWER_MODE.changelog) {
2396
+ renderChangelogWithCommitSeparators(filtered, commitBySha, (item) =>
2397
+ renderChangelogEventLines(item, commitBySha),
2133
2398
  )
2134
2399
  } else {
2135
- renderCollection(
2136
- filtered,
2137
- (item) => {
2138
- const details = el('details', 'leaf')
2139
- const title = `${item.eventKind === 'route' ? `Route: ${item.routeKey}` : `Source: ${item.displayName || item.sourceObjectId}`} (${item.event.type})`
2140
- details.appendChild(el('summary', '', title))
2141
- details.appendChild(renderEventCard(item.event, commitBySha))
2142
- return details
2143
- },
2144
- 'No matches.',
2145
- )
2400
+ renderChangelogWithCommitSeparators(filtered, commitBySha, (item) => {
2401
+ const details = el('details', 'leaf')
2402
+ const summary = el('summary')
2403
+ summary.appendChild(renderEventTitle(item))
2404
+ details.appendChild(summary)
2405
+ details.appendChild(
2406
+ renderEventCard(item.event, commitBySha, {
2407
+ showTitle: false,
2408
+ item,
2409
+ }),
2410
+ )
2411
+ return details
2412
+ })
2146
2413
  }
2147
2414
  }
2148
2415
  syncFilterStateToUrl()