Skip to content

Commit ebb02bd

Browse files
authored
Merge pull request #31 from KvizadSaderah/feat/yaml-typespec-support
Feat/yaml typespec support
2 parents 1e1f9ee + 0ed71d5 commit ebb02bd

11 files changed

Lines changed: 115 additions & 107 deletions

File tree

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,34 @@ gts/
130130

131131
## Key Features
132132

133-
- Full support for JSONC (JSON with Comments)
134-
- Automated GTS entities discovery in `*.json` or `*.gts` files
135-
- JSON / JSON schema validation with respect of GTS ids for schemas
136-
- JSONC support for comments and trailing commas
133+
- **Multiple File Format Support:**
134+
- **JSON**: Standard JSON format (`.json`, `.jsonc`, `.gts`)
135+
- **JSONC**: JSON with Comments - supports single-line comments, multi-line comments, and trailing commas
136+
- **YAML**: Full YAML support (`.yaml`, `.yml`) - automatically parsed and treated identically to JSON
137+
- **TypeSpec**: Pre-compiled TypeSpec schemas (see below)
138+
- Automated GTS entities discovery across all supported file formats
139+
- JSON / JSON Schema validation with respect to GTS IDs for schemas
137140
- Visual GTS entities layout editor and persistence
138141
- Invalid file detection and error reporting
139142

143+
### TypeSpec Support
144+
145+
TypeSpec (`.tsp`) schemas must be pre-compiled to JSON Schema before use with GTS Viewer.
146+
147+
**Setup:**
148+
```bash
149+
# Install TypeSpec compiler
150+
npm install -g @typespec/compiler @typespec/json-schema
151+
152+
# Compile TypeSpec to JSON Schema
153+
tsp compile --emit @typespec/json-schema your-schemas/
154+
```
155+
156+
**Usage:**
157+
Point GTS Viewer to the generated JSON Schema output directory (e.g., `tsp-output/@typespec/json-schema/`). The viewer will automatically discover and load the compiled schemas.
158+
159+
See [gts-spec TypeSpec examples](https://github.com/globaltypesystem/gts-spec/tree/main/examples/typespec) for sample TypeSpec definitions.
160+
140161
## Building
141162

142163
```

apps/electron/src/main/main.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ let layoutStorage: HomeFolderLayoutStorage
99

1010
// Lazy-load ESM module from CJS using dynamic import
1111
let parseJSONCFn: ((s: string) => any) | null = null
12+
let parseYAMLFn: ((s: string) => any) | null = null
1213
async function ensureSharedLoaded() {
1314
if (!parseJSONCFn) {
1415
const mod: any = await import('@gts-viewer/shared')
1516
parseJSONCFn = mod.parseJSONC
17+
parseYAMLFn = mod.parseYAML
1618
}
1719
}
1820

@@ -89,10 +91,11 @@ ipcMain.handle('read-directory', async (_, directoryPath: string) => {
8991

9092
if (entry.isDirectory()) {
9193
await readDirectory(fullPath)
92-
} else if (entry.isFile() && (entry.name.endsWith('.json') || entry.name.endsWith('.jsonc') || entry.name.endsWith('.gts'))) {
94+
} else if (entry.isFile() && (entry.name.endsWith('.json') || entry.name.endsWith('.jsonc') || entry.name.endsWith('.gts') || entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
9395
try {
9496
const content = await fs.readFile(fullPath, 'utf-8')
95-
const jsonContent = parseJSONCFn!(content)
97+
const isYaml = entry.name.endsWith('.yaml') || entry.name.endsWith('.yml')
98+
const jsonContent = isYaml ? parseYAMLFn!(content) : parseJSONCFn!(content)
9699
const relativePath = path.relative(directoryPath, fullPath)
97100
const isSchema = entry.name.includes('schema') ||
98101
jsonContent.$schema ||

apps/server/src/server.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { readdir, readFile } from 'node:fs/promises'
66
import { join, basename } from 'node:path'
77
import { openSqlite } from './db.js'
88
import type { LayoutSaveRequest, LayoutSnapshot, GlobalSettings } from '@gts/shared'
9-
import { JsonRegistry, getGtsConfig, GtsConfig, DEFAULT_GTS_CONFIG, parseJSONC, decodeGtsId, isGtsCandidateFileName } from '@gts/shared'
9+
import { JsonRegistry, getGtsConfig, GtsConfig, DEFAULT_GTS_CONFIG, parseJSONC, parseYAML, decodeGtsId, isGtsCandidateFileName } from '@gts/shared'
1010
import { randomUUID } from 'crypto'
1111
import type { Server } from 'node:http'
1212
import type { ServerConfig } from './config.js'
@@ -48,14 +48,15 @@ async function scanDirectory(dir: string, files: Array<{ path: string; name: str
4848
} else if (entry.isFile() && isGtsCandidateFileName(entry.name)) {
4949
try {
5050
const content = await readFile(fullPath, 'utf-8')
51-
const parsed = parseJSONC(content)
51+
const isYaml = entry.name.endsWith('.yaml') || entry.name.endsWith('.yml')
52+
const parsed = isYaml ? parseYAML(content) : parseJSONC(content)
5253
files.push({
5354
path: fullPath,
5455
name: basename(fullPath),
5556
content: parsed
5657
})
5758
} catch (error) {
58-
// Skip invalid JSON files
59+
// Skip invalid files
5960
}
6061
}
6162
}

apps/vscode-extension/src/extension.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function getNonce(): string {
2222
return text
2323
}
2424

25-
async function scanAndPost(includeGlob: string = '**/*.{json,jsonc,gts}', isInitialScan: boolean = false, refreshFilePath?: string | null) {
25+
async function scanAndPost(includeGlob: string = '**/*.{json,jsonc,gts,yaml,yml}', isInitialScan: boolean = false, refreshFilePath?: string | null) {
2626
const hasViewer = viewerPanel !== null
2727

2828
try {
@@ -253,7 +253,7 @@ export async function activate(context: vscode.ExtensionContext) {
253253
*/
254254
async function performInitialScan() {
255255
try {
256-
const include = '**/*.{json,jsonc,gts}'
256+
const include = '**/*.{json,jsonc,gts,yaml,yml}'
257257
const exclude = '**/{node_modules,.gts-viewer,dist,.git}/**'
258258
const uris = await vscode.workspace.findFiles(include, exclude, 10000)
259259

@@ -326,7 +326,7 @@ function handleFileChange(doc: vscode.TextDocument, delayMsec: number = 500) {
326326
if (changeTimer) clearTimeout(changeTimer)
327327
console.log(`[GTS] File changed 2: ${doc.uri.fsPath}`)
328328
changeTimer = setTimeout(() => {
329-
scanAndPost('**/*.{json,jsonc,gts}', false, doc.uri.fsPath)
329+
scanAndPost('**/*.{json,jsonc,gts,yaml,yml}', false, doc.uri.fsPath)
330330
}, delayMsec)
331331
}
332332

@@ -414,7 +414,7 @@ function openViewer(context: vscode.ExtensionContext, resource?: vscode.Uri) {
414414

415415
case 'scanWorkspaceJson': {
416416
try {
417-
const include: string = message.options?.include || '**/*.{json,jsonc,gts}'
417+
const include: string = message.options?.include || '**/*.{json,jsonc,gts,yaml,yml}'
418418
const isInitialScan = !hasPerformedInitialScan
419419
if (isInitialScan) {
420420
hasPerformedInitialScan = true

apps/web/src/hooks/useJsonFiles.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useRef } from 'react'
2-
import { JsonRegistry, parseJSONC } from '@gts/shared'
2+
import { JsonRegistry, parseJSONC, parseYAML } from '@gts/shared'
33
import { Scanner } from '../../../../packages/fs-adapters/types'
44
// Use the smart scanner that automatically chooses the best implementation
55
import { WebSmartScanner } from '../../../../packages/fs-adapters/fs-adapter-web/src/index'
@@ -59,23 +59,24 @@ export function useJsonObjsWithScanner(createScanner: () => Scanner) {
5959
const scanner = scannerRef.current
6060
if (!scanner) return
6161

62-
const docs = await scanner.list({ glob: '**/*.{json,jsonc,gts}' })
62+
const docs = await scanner.list({ glob: '**/*.{json,jsonc,gts,yaml,yml}' })
6363
const files: Array<{ path: string; name: string; content: any }> = []
6464

6565
for (const d of docs) {
6666
try {
6767
const text = await scanner.read(d.path)
68+
const isYaml = d.name.endsWith('.yaml') || d.name.endsWith('.yml')
6869
try {
69-
const content = parseJSONC(text)
70+
const content = isYaml ? parseYAML(text) : parseJSONC(text)
7071
files.push({ path: d.path, name: d.name, content })
7172
} catch (e) {
7273
if (text.includes('gts.')) {
73-
// Still push malformed JSONC to show proper error messages
74+
// Still push malformed files to show proper error messages
7475
files.push({ path: d.path, name: d.name, content: text })
7576
}
7677
}
7778
} catch (e) {
78-
// Skip unreadable/invalid JSONC
79+
// Skip unreadable/invalid files
7980
}
8081
}
8182

@@ -129,7 +130,7 @@ export function useJsonObjsWithScanner(createScanner: () => Scanner) {
129130

130131
// Start new watcher
131132
watcherRef.current = scanner.watch(
132-
{ glob: '**/*.{json,jsonc,gts}' },
133+
{ glob: '**/*.{json,jsonc,gts,yaml,yml}' },
133134
(change) => {
134135
console.log('File change detected:', change)
135136
// Reload data when files change

apps/web/src/hooks/useJsonFilesVscode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function useJsonObjsVscode() {
122122

123123
// Kick off scan for .json, .jsonc and .gts files
124124
try {
125-
(window as any).__GTS_APP_API__?.scanWorkspaceJson?.({ include: '**/*.{json,jsonc,gts}' })
125+
(window as any).__GTS_APP_API__?.scanWorkspaceJson?.({ include: '**/*.{json,jsonc,gts,yaml,yml}' })
126126
} catch (e) {
127127
setError(e instanceof Error ? e.message : 'Failed to initiate scan')
128128
setLoading(false)
@@ -145,7 +145,7 @@ export function useJsonObjsVscode() {
145145
setError(null)
146146
setProgress(null)
147147
try {
148-
(window as any).__GTS_APP_API__?.scanWorkspaceJson?.({ include: '**/*.{json,jsonc,gts}' })
148+
(window as any).__GTS_APP_API__?.scanWorkspaceJson?.({ include: '**/*.{json,jsonc,gts,yaml,yml}' })
149149
} catch (e) {
150150
setError(e instanceof Error ? e.message : 'Failed to initiate scan')
151151
setLoading(false)

0 commit comments

Comments
 (0)