lutaml-jsonschema 0.1.9 → 0.1.10
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/frontend/src/App.vue +16 -0
- data/frontend/src/components/AppSidebar.vue +34 -8
- data/frontend/src/components/DetailPanel.vue +61 -3
- data/frontend/src/components/SchemaBuilder.vue +48 -6
- data/frontend/src/composables/useJsonViewer.ts +3 -2
- data/frontend/src/views/HomeView.vue +30 -2
- data/lib/lutaml/jsonschema/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2817082f5751466a6e7bae6f77310304d3b58127a392c1e60341d561b65706a
|
|
4
|
+
data.tar.gz: 2c6f4b882f26600890a7a21a241e3a3709fa155df21097ca3c484513cef5e4ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f1ca6699188d725ac053c230043316a96dbdb1fda2cf1c880a48e848ab31c6d4270e19f37f392c10d3205eafb27f529f5443f00fa39d20ec5f7f06dc32daa64
|
|
7
|
+
data.tar.gz: 905835a47ca2348e8bd2cd5b5f2bc526a115782fbd3264c61b212e7c61192e87d6dbd1e9193f0d9598c9f4bf28916f410cf8fcee0919f1b747cf8f6119ac0516
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.10] - 2026-05-09
|
|
4
|
+
|
|
5
|
+
### Redoc-style UX improvements
|
|
6
|
+
|
|
7
|
+
- JSON viewer collapsed objects show key names (e.g. `{a, b, c}`), arrays show item count
|
|
8
|
+
- Sidebar auto-scrolls to active schema node on selection
|
|
9
|
+
- Search results sorted by relevance: exact name > starts with > contains > description
|
|
10
|
+
- Copy button shows positioned tooltip overlay instead of text swap
|
|
11
|
+
- Mobile sidebar backdrop overlay (click-to-close)
|
|
12
|
+
- DetailPanel overview shows definition-level metadata: required fields, composition badges, property range, additionalProperties
|
|
13
|
+
- Nullable badge for union types (e.g. `string,null`) in SchemaBuilder
|
|
14
|
+
- Focus trap in DetailPanel: focus moves to panel on open, Tab/Shift+Tab trapped, Escape closes
|
|
15
|
+
|
|
3
16
|
## [0.1.0] - 2026-05-05
|
|
4
17
|
|
|
5
18
|
- Initial release
|
data/frontend/src/App.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="app-layout">
|
|
3
3
|
<AppSidebar />
|
|
4
|
+
<div v-if="!uiStore.sidebarCollapsed" class="sidebar-overlay" @click="uiStore.toggleSidebar"></div>
|
|
4
5
|
<div class="main-content">
|
|
5
6
|
<AppHeader />
|
|
6
7
|
<div class="content-area">
|
|
@@ -100,4 +101,19 @@ function isInputFocused(): boolean {
|
|
|
100
101
|
padding: var(--space-6);
|
|
101
102
|
background: var(--bg-primary);
|
|
102
103
|
}
|
|
104
|
+
|
|
105
|
+
/* Mobile sidebar overlay — hidden on desktop, shown on mobile when sidebar open */
|
|
106
|
+
.sidebar-overlay {
|
|
107
|
+
display: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (max-width: 768px) {
|
|
111
|
+
.sidebar-overlay {
|
|
112
|
+
display: block;
|
|
113
|
+
position: fixed;
|
|
114
|
+
inset: 0;
|
|
115
|
+
background: rgba(0, 0, 0, 0.4);
|
|
116
|
+
z-index: 39;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
103
119
|
</style>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
<div v-else-if="debouncedQuery && !searchResults.length && searchQuery" class="search-no-results text-muted">
|
|
70
70
|
No results found
|
|
71
71
|
</div>
|
|
72
|
-
<div v-else class="schema-tree">
|
|
72
|
+
<div v-else class="schema-tree" ref="sidebarTreeRef">
|
|
73
73
|
<div
|
|
74
74
|
v-for="schema in schemaStore.schemas"
|
|
75
75
|
:key="schema.name"
|
|
@@ -150,13 +150,14 @@
|
|
|
150
150
|
</template>
|
|
151
151
|
|
|
152
152
|
<script setup lang="ts">
|
|
153
|
-
import { ref, computed, watch } from 'vue'
|
|
153
|
+
import { ref, computed, watch, nextTick } from 'vue'
|
|
154
154
|
import { useSchemaStore } from '../stores/schemaStore'
|
|
155
155
|
import { useUiStore } from '../stores/uiStore'
|
|
156
156
|
import type { SpaSearchEntry } from '../types'
|
|
157
157
|
|
|
158
158
|
const schemaStore = useSchemaStore()
|
|
159
159
|
const uiStore = useUiStore()
|
|
160
|
+
const sidebarTreeRef = ref<HTMLElement | null>(null)
|
|
160
161
|
|
|
161
162
|
const searchQuery = ref('')
|
|
162
163
|
const debouncedQuery = ref('')
|
|
@@ -177,16 +178,41 @@ watch(searchQuery, (q) => {
|
|
|
177
178
|
const searchResults = computed(() => {
|
|
178
179
|
const q = debouncedQuery.value.trim().toLowerCase()
|
|
179
180
|
if (!q) return []
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
const scored = schemaStore.searchIndex
|
|
182
|
+
.map(entry => {
|
|
183
|
+
let score = 0
|
|
184
|
+
const nameLower = entry.name.toLowerCase()
|
|
185
|
+
const titleLower = (entry.title || '').toLowerCase()
|
|
186
|
+
const descLower = (entry.description || '').toLowerCase()
|
|
187
|
+
const schemaLower = entry.schemaName.toLowerCase()
|
|
188
|
+
if (nameLower === q) score += 100
|
|
189
|
+
else if (nameLower.startsWith(q)) score += 50
|
|
190
|
+
else if (nameLower.includes(q)) score += 30
|
|
191
|
+
if (titleLower === q) score += 80
|
|
192
|
+
else if (titleLower.startsWith(q)) score += 40
|
|
193
|
+
else if (titleLower.includes(q)) score += 20
|
|
194
|
+
if (descLower.includes(q)) score += 10
|
|
195
|
+
if (schemaLower.includes(q)) score += 5
|
|
196
|
+
return { entry, score }
|
|
197
|
+
})
|
|
198
|
+
.filter(r => r.score > 0)
|
|
199
|
+
.sort((a, b) => b.score - a.score)
|
|
200
|
+
.slice(0, 15)
|
|
201
|
+
.map(r => r.entry)
|
|
202
|
+
return scored
|
|
186
203
|
})
|
|
187
204
|
|
|
188
205
|
watch(searchResults, () => { activeResultIdx.value = -1 })
|
|
189
206
|
|
|
207
|
+
watch(() => schemaStore.selectedSchemaName, () => {
|
|
208
|
+
nextTick(() => {
|
|
209
|
+
const container = sidebarTreeRef.value
|
|
210
|
+
if (!container) return
|
|
211
|
+
const active = container.querySelector('.schema-node-header.active') as HTMLElement | null
|
|
212
|
+
if (active) active.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
190
216
|
function handleSearchKey(event: KeyboardEvent) {
|
|
191
217
|
if (event.key === 'ArrowDown') {
|
|
192
218
|
activeResultIdx.value = Math.min(activeResultIdx.value + 1, searchResults.value.length - 1)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="detail-panel-overlay" @click.self="uiStore.closeDetailPanel">
|
|
3
|
-
<aside class="detail-panel">
|
|
3
|
+
<aside class="detail-panel" ref="panelRef">
|
|
4
4
|
<div class="panel-header">
|
|
5
5
|
<div class="panel-title">
|
|
6
6
|
<h2 v-if="item">{{ itemTitle }}</h2>
|
|
7
7
|
</div>
|
|
8
|
-
<button class="btn btn-ghost" @click="uiStore.closeDetailPanel">
|
|
8
|
+
<button ref="closeBtnRef" class="btn btn-ghost" @click="uiStore.closeDetailPanel" aria-label="Close panel">
|
|
9
9
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
|
10
10
|
<path d="M5 5l8 8M13 5l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
11
11
|
</svg>
|
|
@@ -98,6 +98,32 @@
|
|
|
98
98
|
<span class="meta-label">Additional</span>
|
|
99
99
|
<span class="badge badge-locked-detail">Denied</span>
|
|
100
100
|
</div>
|
|
101
|
+
<div v-if="definitionItem && definitionItem.required?.length" class="meta-row">
|
|
102
|
+
<span class="meta-label">Required</span>
|
|
103
|
+
<div class="meta-tags">
|
|
104
|
+
<span v-for="r in definitionItem.required" :key="r" class="badge badge-required-sm">{{ r }}</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div v-if="definitionItem && (definitionItem.hasAllOf || definitionItem.hasAnyOf || definitionItem.hasOneOf)" class="meta-row">
|
|
108
|
+
<span class="meta-label">Composition</span>
|
|
109
|
+
<div class="meta-tags">
|
|
110
|
+
<span v-if="definitionItem.hasAllOf" class="badge badge-composition-detail">allOf</span>
|
|
111
|
+
<span v-if="definitionItem.hasAnyOf" class="badge badge-composition-detail">anyOf</span>
|
|
112
|
+
<span v-if="definitionItem.hasOneOf" class="badge badge-composition-detail">oneOf</span>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div v-if="definitionItem && (definitionItem.minProperties != null || definitionItem.maxProperties != null)" class="meta-row">
|
|
116
|
+
<span class="meta-label">Properties</span>
|
|
117
|
+
<span class="text-secondary">
|
|
118
|
+
<template v-if="definitionItem.minProperties != null && definitionItem.maxProperties != null">{{ definitionItem.minProperties }}..{{ definitionItem.maxProperties }}</template>
|
|
119
|
+
<template v-else-if="definitionItem.minProperties != null">≥ {{ definitionItem.minProperties }}</template>
|
|
120
|
+
<template v-else>≤ {{ definitionItem.maxProperties }}</template>
|
|
121
|
+
</span>
|
|
122
|
+
</div>
|
|
123
|
+
<div v-if="definitionItem && definitionItem.additionalProperties === false" class="meta-row">
|
|
124
|
+
<span class="meta-label">Additional</span>
|
|
125
|
+
<span class="badge badge-locked-detail">Denied</span>
|
|
126
|
+
</div>
|
|
101
127
|
</div>
|
|
102
128
|
</div>
|
|
103
129
|
|
|
@@ -241,7 +267,7 @@
|
|
|
241
267
|
</template>
|
|
242
268
|
|
|
243
269
|
<script setup lang="ts">
|
|
244
|
-
import { computed } from 'vue'
|
|
270
|
+
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
|
245
271
|
import { useSchemaStore, type SelectedItem } from '../stores/schemaStore'
|
|
246
272
|
import { useUiStore } from '../stores/uiStore'
|
|
247
273
|
import type { SpaProperty } from '../types'
|
|
@@ -249,6 +275,33 @@ import type { SpaProperty } from '../types'
|
|
|
249
275
|
const schemaStore = useSchemaStore()
|
|
250
276
|
const uiStore = useUiStore()
|
|
251
277
|
|
|
278
|
+
const panelRef = ref<HTMLElement | null>(null)
|
|
279
|
+
const closeBtnRef = ref<HTMLButtonElement | null>(null)
|
|
280
|
+
|
|
281
|
+
onMounted(() => {
|
|
282
|
+
closeBtnRef.value?.focus()
|
|
283
|
+
document.addEventListener('keydown', trapFocus)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
onUnmounted(() => {
|
|
287
|
+
document.removeEventListener('keydown', trapFocus)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
function trapFocus(e: KeyboardEvent) {
|
|
291
|
+
if (e.key !== 'Tab' || !panelRef.value) return
|
|
292
|
+
const focusable = panelRef.value.querySelectorAll<HTMLElement>(
|
|
293
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
294
|
+
)
|
|
295
|
+
if (!focusable.length) return
|
|
296
|
+
const first = focusable[0]
|
|
297
|
+
const last = focusable[focusable.length - 1]
|
|
298
|
+
if (e.shiftKey) {
|
|
299
|
+
if (document.activeElement === first) { e.preventDefault(); last.focus() }
|
|
300
|
+
} else {
|
|
301
|
+
if (document.activeElement === last) { e.preventDefault(); first.focus() }
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
252
305
|
const item = computed<SelectedItem | null>(() => schemaStore.selectedItem)
|
|
253
306
|
const schema = computed(() => schemaStore.selectedSchema)
|
|
254
307
|
|
|
@@ -379,6 +432,11 @@ const propertyItem = computed<SpaProperty | null>(() => {
|
|
|
379
432
|
if (item.value?.kind !== 'property') return null
|
|
380
433
|
return item.value.property
|
|
381
434
|
})
|
|
435
|
+
|
|
436
|
+
const definitionItem = computed(() => {
|
|
437
|
+
if (item.value?.kind !== 'definition') return null
|
|
438
|
+
return item.value.definition
|
|
439
|
+
})
|
|
382
440
|
</script>
|
|
383
441
|
|
|
384
442
|
<style scoped>
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<span v-if="field.prop.deprecated" class="deprecated-badge">deprecated</span>
|
|
26
26
|
<span v-if="field.prop.readOnly" class="readonly-badge">read-only</span>
|
|
27
27
|
<span v-if="field.prop.writeOnly" class="writeonly-badge">write-only</span>
|
|
28
|
+
<span v-if="isNullableType(field.prop.type)" class="nullable-badge">nullable</span>
|
|
28
29
|
<span v-if="field.prop.compositionSource" class="composition-badge">{{ field.prop.compositionSource }}</span>
|
|
29
30
|
|
|
30
31
|
<div class="field-control">
|
|
@@ -209,8 +210,9 @@
|
|
|
209
210
|
<div class="toolbar-actions">
|
|
210
211
|
<button class="btn btn-ghost btn-sm" @click="expandAllJson">Expand all</button>
|
|
211
212
|
<button class="btn btn-ghost btn-sm" @click="collapseAllJson">Collapse all</button>
|
|
212
|
-
<button class="btn btn-ghost btn-sm" @click="copyJson">
|
|
213
|
-
|
|
213
|
+
<button class="btn btn-ghost btn-sm copy-btn-wrap" @click="copyJson">
|
|
214
|
+
Copy
|
|
215
|
+
<span v-if="copied" class="copy-tooltip">Copied!</span>
|
|
214
216
|
</button>
|
|
215
217
|
</div>
|
|
216
218
|
</div>
|
|
@@ -405,6 +407,10 @@ function typeBadgeClass(prop: SpaProperty): string {
|
|
|
405
407
|
}
|
|
406
408
|
}
|
|
407
409
|
|
|
410
|
+
function isNullableType(type?: string): boolean {
|
|
411
|
+
return (type || '').split(',').map(s => s.trim()).includes('null')
|
|
412
|
+
}
|
|
413
|
+
|
|
408
414
|
function truncatedPattern(pattern: string): { text: string; truncated: boolean } {
|
|
409
415
|
if (pattern.length <= MAX_PATTERN_LEN) return { text: pattern, truncated: false }
|
|
410
416
|
if (expandedPatterns.value.has(pattern)) return { text: pattern, truncated: true }
|
|
@@ -583,6 +589,18 @@ async function copyJson() {
|
|
|
583
589
|
background: var(--color-orange-alpha);
|
|
584
590
|
}
|
|
585
591
|
|
|
592
|
+
.nullable-badge {
|
|
593
|
+
font-size: 10px;
|
|
594
|
+
font-weight: 600;
|
|
595
|
+
text-transform: uppercase;
|
|
596
|
+
color: var(--text-muted);
|
|
597
|
+
background: var(--bg-secondary);
|
|
598
|
+
border: 1px dashed var(--border-medium);
|
|
599
|
+
padding: 1px 5px;
|
|
600
|
+
border-radius: 2px;
|
|
601
|
+
flex-shrink: 0;
|
|
602
|
+
}
|
|
603
|
+
|
|
586
604
|
.composition-badge {
|
|
587
605
|
font-size: 10px;
|
|
588
606
|
font-weight: 600;
|
|
@@ -1048,13 +1066,10 @@ async function copyJson() {
|
|
|
1048
1066
|
display: none;
|
|
1049
1067
|
color: var(--text-muted);
|
|
1050
1068
|
font-size: var(--text-xs);
|
|
1069
|
+
font-style: italic;
|
|
1051
1070
|
margin-left: 4px;
|
|
1052
1071
|
}
|
|
1053
1072
|
|
|
1054
|
-
.json-block :deep(.jv-ellipsis::after) {
|
|
1055
|
-
content: ' … ';
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
1073
|
.json-block :deep(.jv-children.jv-collapsed) {
|
|
1059
1074
|
display: none;
|
|
1060
1075
|
}
|
|
@@ -1106,6 +1121,33 @@ async function copyJson() {
|
|
|
1106
1121
|
gap: var(--space-1);
|
|
1107
1122
|
}
|
|
1108
1123
|
|
|
1124
|
+
/* Copy button tooltip */
|
|
1125
|
+
.copy-btn-wrap {
|
|
1126
|
+
position: relative;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.copy-tooltip {
|
|
1130
|
+
position: absolute;
|
|
1131
|
+
bottom: calc(100% + 4px);
|
|
1132
|
+
left: 50%;
|
|
1133
|
+
transform: translateX(-50%);
|
|
1134
|
+
background: var(--bg-elevated);
|
|
1135
|
+
color: var(--text-primary);
|
|
1136
|
+
font-size: var(--text-xs);
|
|
1137
|
+
padding: 3px 8px;
|
|
1138
|
+
border-radius: var(--radius-sm);
|
|
1139
|
+
border: 1px solid var(--border-light);
|
|
1140
|
+
box-shadow: var(--shadow-md);
|
|
1141
|
+
white-space: nowrap;
|
|
1142
|
+
pointer-events: none;
|
|
1143
|
+
animation: tooltipFade var(--transition-slow);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
@keyframes tooltipFade {
|
|
1147
|
+
from { opacity: 0; transform: translateX(-50%) translateY(2px); }
|
|
1148
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1109
1151
|
.empty-hint {
|
|
1110
1152
|
padding: var(--space-8);
|
|
1111
1153
|
text-align: center;
|
|
@@ -56,7 +56,7 @@ function renderArray(arr: unknown[], maxExpand: number, level: number): string {
|
|
|
56
56
|
html += '</div></li>'
|
|
57
57
|
})
|
|
58
58
|
html += '</ul>'
|
|
59
|
-
html += `<span class="jv-ellipsis"
|
|
59
|
+
html += `<span class="jv-ellipsis">${arr.length} items</span>`
|
|
60
60
|
html += punct(']')
|
|
61
61
|
return html
|
|
62
62
|
}
|
|
@@ -76,7 +76,8 @@ function renderObject(obj: Record<string, unknown>, maxExpand: number, level: nu
|
|
|
76
76
|
html += '</div></li>'
|
|
77
77
|
})
|
|
78
78
|
html += '</ul>'
|
|
79
|
-
|
|
79
|
+
const previewKeys = keys.length <= 5 ? keys : [...keys.slice(0, 5), `+${keys.length - 5}`]
|
|
80
|
+
html += `<span class="jv-ellipsis">${previewKeys.map(k => escHtml(k)).join(', ')}</span>`
|
|
80
81
|
html += punct('}')
|
|
81
82
|
return html
|
|
82
83
|
}
|
|
@@ -136,8 +136,9 @@
|
|
|
136
136
|
<div v-if="schemaStore.selectedSchema.sourceJson" class="source-viewer">
|
|
137
137
|
<div class="source-toolbar">
|
|
138
138
|
<span class="text-muted">{{ sourceLineCount }} lines</span>
|
|
139
|
-
<button class="btn btn-ghost btn-sm" @click="copySource">
|
|
140
|
-
|
|
139
|
+
<button class="btn btn-ghost btn-sm copy-btn-wrap" @click="copySource">
|
|
140
|
+
Copy
|
|
141
|
+
<span v-if="sourceCopied" class="copy-tooltip">Copied!</span>
|
|
141
142
|
</button>
|
|
142
143
|
</div>
|
|
143
144
|
<div class="source-code-wrapper">
|
|
@@ -592,6 +593,33 @@ watch(() => schemaStore.selectedItemKey, (key) => {
|
|
|
592
593
|
font-size: var(--text-xs);
|
|
593
594
|
}
|
|
594
595
|
|
|
596
|
+
/* Copy button tooltip */
|
|
597
|
+
.copy-btn-wrap {
|
|
598
|
+
position: relative;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.copy-tooltip {
|
|
602
|
+
position: absolute;
|
|
603
|
+
bottom: calc(100% + 4px);
|
|
604
|
+
left: 50%;
|
|
605
|
+
transform: translateX(-50%);
|
|
606
|
+
background: var(--bg-elevated);
|
|
607
|
+
color: var(--text-primary);
|
|
608
|
+
font-size: var(--text-xs);
|
|
609
|
+
padding: 3px 8px;
|
|
610
|
+
border-radius: var(--radius-sm);
|
|
611
|
+
border: 1px solid var(--border-light);
|
|
612
|
+
box-shadow: var(--shadow-md);
|
|
613
|
+
white-space: nowrap;
|
|
614
|
+
pointer-events: none;
|
|
615
|
+
animation: tooltipFade var(--transition-slow);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
@keyframes tooltipFade {
|
|
619
|
+
from { opacity: 0; transform: translateX(-50%) translateY(2px); }
|
|
620
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
621
|
+
}
|
|
622
|
+
|
|
595
623
|
.source-code-wrapper {
|
|
596
624
|
display: flex;
|
|
597
625
|
max-height: 70vh;
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-jsonschema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|