[READ-ONLY] a fast, modern browser for the npm registry

chore: use oxlint + oxfmt

+1472 -2845
+1 -1
.github/workflows/autofix.yml
··· 19 19 - uses: actions/setup-node@v6 20 20 with: 21 21 node-version: lts/* 22 - cache: "pnpm" 22 + cache: 'pnpm' 23 23 24 24 - name: 📦 Install dependencies 25 25 run: pnpm install
+8
.oxfmtrc.json
··· 1 + { 2 + "$schema": "./node_modules/oxfmt/configuration_schema.json", 3 + "semi": false, 4 + "singleQuote": true, 5 + "arrowParens": "avoid", 6 + "quoteProps": "consistent", 7 + "experimentalSortPackageJson": false 8 + }
+26
.oxlintrc.json
··· 1 + { 2 + "$schema": "./node_modules/oxlint/configuration_schema.json", 3 + "plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"], 4 + "categories": { 5 + "correctness": "error", 6 + "suspicious": "warn", 7 + "perf": "warn" 8 + }, 9 + "rules": { 10 + "no-console": "warn", 11 + "no-await-in-loop": "off", 12 + "unicorn/no-array-sort": "off" 13 + }, 14 + "ignorePatterns": [ 15 + ".output/**", 16 + ".data/**", 17 + ".nuxt/**", 18 + ".nitro/**", 19 + ".cache/**", 20 + "dist/**", 21 + "node_modules/**", 22 + "coverage/**", 23 + "playwright-report/**", 24 + "test-results/**" 25 + ] 26 + }
+3
.vscode/extensions.json
··· 1 + { 2 + "recommendations": ["oxc.oxc-vscode", "Vue.volar"] 3 + }
+7 -7
README.md
··· 31 31 32 32 npmx.dev supports npm permalink patterns: 33 33 34 - | Pattern | Example | 35 - |---------|---------| 36 - | `/package/<name>` | [`/package/nuxt`](https://npmx.dev/package/nuxt) | 37 - | `/package/@scope/name` | [`/package/@nuxt/kit`](https://npmx.dev/package/@nuxt/kit) | 34 + | Pattern | Example | 35 + | ----------------------------- | -------------------------------------------------------------- | 36 + | `/package/<name>` | [`/package/nuxt`](https://npmx.dev/package/nuxt) | 37 + | `/package/@scope/name` | [`/package/@nuxt/kit`](https://npmx.dev/package/@nuxt/kit) | 38 38 | `/package/<name>/v/<version>` | [`/package/vue/v/3.4.0`](https://npmx.dev/package/vue/v/3.4.0) | 39 - | `/search?q=<query>` | [`/search?q=vue`](https://npmx.dev/search?q=vue) | 40 - | `/~<username>` | [`/~sindresorhus`](https://npmx.dev/~sindresorhus) | 41 - | `/org/<name>` | [`/org/nuxt`](https://npmx.dev/org/nuxt) | 39 + | `/search?q=<query>` | [`/search?q=vue`](https://npmx.dev/search?q=vue) | 40 + | `/~<username>` | [`/~sindresorhus`](https://npmx.dev/~sindresorhus) | 41 + | `/org/<name>` | [`/org/nuxt`](https://npmx.dev/org/nuxt) | 42 42 43 43 ## Tech stack 44 44
+51 -44
app/app.vue
··· 5 5 const isHomepage = computed(() => route.path === '/') 6 6 7 7 useHead({ 8 - titleTemplate: (titleChunk) => { 8 + titleTemplate: titleChunk => { 9 9 return titleChunk ? titleChunk : 'npmx - Better npm Package Browser' 10 10 }, 11 11 }) ··· 14 14 function handleGlobalKeydown(e: KeyboardEvent) { 15 15 // Ignore if user is typing in an input, textarea, or contenteditable 16 16 const target = e.target as HTMLElement 17 - if ( 18 - target.tagName === 'INPUT' 19 - || target.tagName === 'TEXTAREA' 20 - || target.isContentEditable 21 - ) { 17 + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { 22 18 return 23 19 } 24 20 ··· 32 28 33 29 if (searchInput) { 34 30 searchInput.focus() 35 - } 36 - else { 31 + } else { 37 32 // Navigate to search page 38 33 router.push('/search') 39 34 } ··· 51 46 52 47 <template> 53 48 <div class="min-h-screen flex flex-col bg-bg text-fg"> 54 - <a 55 - href="#main-content" 56 - class="skip-link font-mono" 57 - >Skip to main content</a> 49 + <a href="#main-content" class="skip-link font-mono">Skip to main content</a> 58 50 59 51 <AppHeader :show-logo="!isHomepage" /> 60 52 61 - <div 62 - id="main-content" 63 - class="flex-1" 64 - > 53 + <div id="main-content" class="flex-1"> 65 54 <NuxtPage /> 66 55 </div> 67 56 ··· 71 60 72 61 <style> 73 62 /* Base reset and defaults */ 74 - *, *::before, *::after { 63 + *, 64 + *::before, 65 + *::after { 75 66 box-sizing: border-box; 76 67 } 77 68 ··· 94 85 text-decoration: underline; 95 86 text-underline-offset: 3px; 96 87 text-decoration-color: #404040; 97 - transition: color 0.2s ease, text-decoration-color 0.2s ease; 88 + transition: 89 + color 0.2s ease, 90 + text-decoration-color 0.2s ease; 98 91 } 99 92 100 93 a:hover { ··· 190 183 } 191 184 192 185 /* Visual styling based on original README heading level */ 193 - .readme-content [data-level="1"] { font-size: 1.5rem; } 194 - .readme-content [data-level="2"] { font-size: 1.25rem; padding-bottom: 0.5rem; border-bottom: 1px solid #262626; } 195 - .readme-content [data-level="3"] { font-size: 1.125rem; } 196 - .readme-content [data-level="4"] { font-size: 1rem; } 197 - .readme-content [data-level="5"] { font-size: 0.925rem; } 198 - .readme-content [data-level="6"] { font-size: 0.875rem; } 186 + .readme-content [data-level='1'] { 187 + font-size: 1.5rem; 188 + } 189 + .readme-content [data-level='2'] { 190 + font-size: 1.25rem; 191 + padding-bottom: 0.5rem; 192 + border-bottom: 1px solid #262626; 193 + } 194 + .readme-content [data-level='3'] { 195 + font-size: 1.125rem; 196 + } 197 + .readme-content [data-level='4'] { 198 + font-size: 1rem; 199 + } 200 + .readme-content [data-level='5'] { 201 + font-size: 0.925rem; 202 + } 203 + .readme-content [data-level='6'] { 204 + font-size: 0.875rem; 205 + } 199 206 200 207 .readme-content p { 201 208 margin-bottom: 1rem; ··· 310 317 } 311 318 312 319 /* Note - blue */ 313 - .readme-content blockquote[data-callout="note"] { 320 + .readme-content blockquote[data-callout='note'] { 314 321 border-left-color: #3b82f6; 315 322 background: rgba(59, 130, 246, 0.05); 316 323 } 317 - .readme-content blockquote[data-callout="note"]::before { 318 - content: "Note"; 324 + .readme-content blockquote[data-callout='note']::before { 325 + content: 'Note'; 319 326 color: #3b82f6; 320 327 } 321 328 322 329 /* Tip - green */ 323 - .readme-content blockquote[data-callout="tip"] { 330 + .readme-content blockquote[data-callout='tip'] { 324 331 border-left-color: #22c55e; 325 332 background: rgba(34, 197, 94, 0.05); 326 333 } 327 - .readme-content blockquote[data-callout="tip"]::before { 328 - content: "Tip"; 334 + .readme-content blockquote[data-callout='tip']::before { 335 + content: 'Tip'; 329 336 color: #22c55e; 330 337 } 331 338 332 339 /* Important - purple */ 333 - .readme-content blockquote[data-callout="important"] { 340 + .readme-content blockquote[data-callout='important'] { 334 341 border-left-color: #a855f7; 335 342 background: rgba(168, 85, 247, 0.05); 336 343 } 337 - .readme-content blockquote[data-callout="important"]::before { 338 - content: "Important"; 344 + .readme-content blockquote[data-callout='important']::before { 345 + content: 'Important'; 339 346 color: #a855f7; 340 347 } 341 348 342 349 /* Warning - yellow/orange */ 343 - .readme-content blockquote[data-callout="warning"] { 350 + .readme-content blockquote[data-callout='warning'] { 344 351 border-left-color: #eab308; 345 352 background: rgba(234, 179, 8, 0.05); 346 353 } 347 - .readme-content blockquote[data-callout="warning"]::before { 348 - content: "Warning"; 354 + .readme-content blockquote[data-callout='warning']::before { 355 + content: 'Warning'; 349 356 color: #eab308; 350 357 } 351 358 352 359 /* Caution - red */ 353 - .readme-content blockquote[data-callout="caution"] { 360 + .readme-content blockquote[data-callout='caution'] { 354 361 border-left-color: #ef4444; 355 362 background: rgba(239, 68, 68, 0.05); 356 363 } 357 - .readme-content blockquote[data-callout="caution"]::before { 358 - content: "Caution"; 364 + .readme-content blockquote[data-callout='caution']::before { 365 + content: 'Caution'; 359 366 color: #ef4444; 360 367 } 361 368 ··· 424 431 } 425 432 426 433 /* Safari search input fixes */ 427 - input[type="search"] { 434 + input[type='search'] { 428 435 -webkit-appearance: none; 429 436 appearance: none; 430 437 } 431 438 432 - input[type="search"]::-webkit-search-decoration, 433 - input[type="search"]::-webkit-search-cancel-button, 434 - input[type="search"]::-webkit-search-results-button, 435 - input[type="search"]::-webkit-search-results-decoration { 439 + input[type='search']::-webkit-search-decoration, 440 + input[type='search']::-webkit-search-cancel-button, 441 + input[type='search']::-webkit-search-results-button, 442 + input[type='search']::-webkit-search-results-decoration { 436 443 -webkit-appearance: none; 437 444 appearance: none; 438 445 }
+2 -8
app/components/AppFooter.vue
··· 2 2 <footer class="border-t border-border mt-auto"> 3 3 <div class="container py-8 flex flex-col gap-4 text-fg-subtle text-sm"> 4 4 <div class="flex flex-col sm:flex-row items-center justify-between gap-4"> 5 - <p class="font-mono m-0"> 6 - a better browser for the npm registry 7 - </p> 5 + <p class="font-mono m-0">a better browser for the npm registry</p> 8 6 <div class="flex items-center gap-6"> 9 7 <a 10 8 href="https://github.com/danielroe/npmx.dev" ··· 14 12 source 15 13 </a> 16 14 <span class="text-border">|</span> 17 - <a 18 - href="https://roe.dev" 19 - rel="noopener noreferrer" 20 - class="link-subtle font-mono text-xs" 21 - > 15 + <a href="https://roe.dev" rel="noopener noreferrer" class="link-subtle font-mono text-xs"> 22 16 @danielroe 23 17 </a> 24 18 </div>
+19 -25
app/components/AppHeader.vue
··· 1 1 <script setup lang="ts"> 2 - withDefaults(defineProps<{ 3 - showLogo?: boolean 4 - showConnector?: boolean 5 - }>(), { 6 - showLogo: true, 7 - showConnector: true, 8 - }) 2 + withDefaults( 3 + defineProps<{ 4 + showLogo?: boolean 5 + showConnector?: boolean 6 + }>(), 7 + { 8 + showLogo: true, 9 + showConnector: true, 10 + }, 11 + ) 9 12 </script> 10 13 11 14 <template> 12 15 <header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border"> 13 - <nav 14 - aria-label="Main navigation" 15 - class="container h-14 flex items-center justify-between" 16 - > 16 + <nav aria-label="Main navigation" class="container h-14 flex items-center justify-between"> 17 17 <NuxtLink 18 18 v-if="showLogo" 19 19 to="/" 20 20 aria-label="npmx home" 21 21 class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded" 22 22 > 23 - <span class="text-fg-subtle"><span style="letter-spacing: -0.2em;">.</span>/</span>npmx 23 + <span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx 24 24 </NuxtLink> 25 25 <!-- Spacer when logo is hidden --> 26 - <span 27 - v-else 28 - class="w-1" 29 - /> 26 + <span v-else class="w-1" /> 30 27 31 28 <ul class="flex items-center gap-4 sm:gap-6 list-none m-0 p-0"> 32 29 <li class="flex"> ··· 35 32 class="link-subtle font-mono text-sm inline-flex items-center gap-2" 36 33 > 37 34 search 38 - <kbd class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded">/</kbd> 35 + <kbd 36 + class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded" 37 + >/</kbd 38 + > 39 39 </NuxtLink> 40 40 </li> 41 - <li 42 - v-if="showConnector" 43 - class="flex" 44 - > 41 + <li v-if="showConnector" class="flex"> 45 42 <ConnectorStatus /> 46 43 </li> 47 - <li 48 - v-else 49 - class="flex" 50 - > 44 + <li v-else class="flex"> 51 45 <a 52 46 href="https://github.com/danielroe/npmx.dev" 53 47 rel="noopener noreferrer"
+3 -13
app/components/CodeDirectoryListing.vue
··· 47 47 <template> 48 48 <div class="directory-listing"> 49 49 <!-- Empty state --> 50 - <div 51 - v-if="currentContents.length === 0" 52 - class="py-20 text-center text-fg-muted" 53 - > 50 + <div v-if="currentContents.length === 0" class="py-20 text-center text-fg-muted"> 54 51 <p>No files in this directory</p> 55 52 </div> 56 53 57 54 <!-- File list --> 58 - <table 59 - v-else 60 - class="w-full" 61 - > 55 + <table v-else class="w-full"> 62 56 <thead class="sr-only"> 63 57 <tr> 64 58 <th>Name</th> ··· 99 93 v-if="node.type === 'directory'" 100 94 class="i-carbon-folder w-4 h-4 text-yellow-600" 101 95 /> 102 - <span 103 - v-else 104 - class="w-4 h-4" 105 - :class="getFileIcon(node.name)" 106 - /> 96 + <span v-else class="w-4 h-4" :class="getFileIcon(node.name)" /> 107 97 <span>{{ node.name }}</span> 108 98 </NuxtLink> 109 99 </td>
+21 -25
app/components/CodeFileTree.vue
··· 22 22 const expandedDirs = ref<Set<string>>(new Set()) 23 23 24 24 // Auto-expand directories in the current path 25 - watch(() => props.currentPath, (path) => { 26 - if (!path) return 27 - const parts = path.split('/') 28 - for (let i = 1; i <= parts.length; i++) { 29 - expandedDirs.value.add(parts.slice(0, i).join('/')) 30 - } 31 - }, { immediate: true }) 25 + watch( 26 + () => props.currentPath, 27 + path => { 28 + if (!path) return 29 + const parts = path.split('/') 30 + for (let i = 1; i <= parts.length; i++) { 31 + expandedDirs.value.add(parts.slice(0, i).join('/')) 32 + } 33 + }, 34 + { immediate: true }, 35 + ) 32 36 33 37 function toggleDir(path: string) { 34 38 if (expandedDirs.value.has(path)) { 35 39 expandedDirs.value.delete(path) 36 - } 37 - else { 40 + } else { 38 41 expandedDirs.value.add(path) 39 42 } 40 43 } ··· 45 48 </script> 46 49 47 50 <template> 48 - <ul 49 - class="list-none m-0 p-0" 50 - :class="depth === 0 ? 'py-2' : ''" 51 - > 52 - <li 53 - v-for="node in tree" 54 - :key="node.path" 55 - > 51 + <ul class="list-none m-0 p-0" :class="depth === 0 ? 'py-2' : ''"> 52 + <li v-for="node in tree" :key="node.path"> 56 53 <!-- Directory --> 57 54 <template v-if="node.type === 'directory'"> 58 55 <button ··· 63 60 > 64 61 <span 65 62 class="w-4 h-4 shrink-0 transition-transform" 66 - :class="[ 67 - isExpanded(node.path) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right', 68 - ]" 63 + :class="[isExpanded(node.path) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right']" 69 64 /> 70 65 <span 71 66 class="w-4 h-4 shrink-0" 72 - :class="isExpanded(node.path) ? 'i-carbon-folder-open text-yellow-500' : 'i-carbon-folder text-yellow-600'" 67 + :class=" 68 + isExpanded(node.path) 69 + ? 'i-carbon-folder-open text-yellow-500' 70 + : 'i-carbon-folder text-yellow-600' 71 + " 73 72 /> 74 73 <span class="truncate">{{ node.name }}</span> 75 74 </button> ··· 90 89 :class="currentPath === node.path ? 'bg-bg-muted text-fg' : 'text-fg-muted'" 91 90 :style="{ paddingLeft: `${depth * 12 + 32}px` }" 92 91 > 93 - <span 94 - class="w-4 h-4 shrink-0" 95 - :class="getFileIcon(node.name)" 96 - /> 92 + <span class="w-4 h-4 shrink-0" :class="getFileIcon(node.name)" /> 97 93 <span class="truncate">{{ node.name }}</span> 98 94 </NuxtLink> 99 95 </template>
+14 -21
app/components/CodeMobileTreeDrawer.vue
··· 11 11 12 12 // Close drawer on navigation 13 13 const route = useRoute() 14 - watch(() => route.fullPath, () => { 15 - isOpen.value = false 16 - }) 14 + watch( 15 + () => route.fullPath, 16 + () => { 17 + isOpen.value = false 18 + }, 19 + ) 17 20 18 21 // Prevent body scroll when drawer is open 19 - watch(isOpen, (open) => { 22 + watch(isOpen, open => { 20 23 if (open) { 21 24 document.body.style.overflow = 'hidden' 22 - } 23 - else { 25 + } else { 24 26 document.body.style.overflow = '' 25 27 } 26 28 }) ··· 38 40 aria-label="Toggle file tree" 39 41 @click="isOpen = !isOpen" 40 42 > 41 - <span 42 - class="w-5 h-5" 43 - :class="isOpen ? 'i-carbon-close' : 'i-carbon-folder'" 44 - /> 43 + <span class="w-5 h-5" :class="isOpen ? 'i-carbon-close' : 'i-carbon-folder'" /> 45 44 </button> 46 45 47 46 <!-- Backdrop --> ··· 53 52 leave-from-class="opacity-100" 54 53 leave-to-class="opacity-0" 55 54 > 56 - <div 57 - v-if="isOpen" 58 - class="md:hidden fixed inset-0 z-40 bg-black/50" 59 - @click="isOpen = false" 60 - /> 55 + <div v-if="isOpen" class="md:hidden fixed inset-0 z-40 bg-black/50" @click="isOpen = false" /> 61 56 </Transition> 62 57 63 58 <!-- Drawer --> ··· 73 68 v-if="isOpen" 74 69 class="md:hidden fixed inset-y-0 left-0 z-50 w-72 bg-bg-subtle border-r border-border overflow-y-auto" 75 70 > 76 - <div class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-between"> 71 + <div 72 + class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-between" 73 + > 77 74 <span class="font-mono text-sm text-fg-muted">Files</span> 78 75 <button 79 76 class="text-fg-muted hover:text-fg transition-colors" ··· 83 80 <span class="i-carbon-close w-5 h-5" /> 84 81 </button> 85 82 </div> 86 - <CodeFileTree 87 - :tree="tree" 88 - :current-path="currentPath" 89 - :base-url="baseUrl" 90 - /> 83 + <CodeFileTree :tree="tree" :current-path="currentPath" :base-url="baseUrl" /> 91 84 </aside> 92 85 </Transition> 93 86 </template>
+3 -8
app/components/CodeViewer.vue
··· 2 2 const props = defineProps<{ 3 3 html: string 4 4 lines: number 5 - selectedLines: { start: number, end: number } | null 5 + selectedLines: { start: number; end: number } | null 6 6 }>() 7 7 8 8 const emit = defineEmits<{ ··· 37 37 const lineNum = index + 1 38 38 if (isLineSelected(lineNum)) { 39 39 line.classList.add('highlighted') 40 - } 41 - else { 40 + } else { 42 41 line.classList.remove('highlighted') 43 42 } 44 43 }) ··· 82 81 <!-- Code content --> 83 82 <div class="code-content flex-1 overflow-x-auto min-w-0"> 84 83 <!-- eslint-disable vue/no-v-html -- HTML is generated server-side by Shiki --> 85 - <div 86 - ref="codeRef" 87 - class="code-lines" 88 - v-html="html" 89 - /> 84 + <div ref="codeRef" class="code-lines" v-html="html" /> 90 85 <!-- eslint-enable vue/no-v-html --> 91 86 </div> 92 87 </div>
+20 -45
app/components/ConnectorModal.vue
··· 1 1 <script setup lang="ts"> 2 2 const open = defineModel<boolean>('open', { default: false }) 3 3 4 - const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } = useConnector() 4 + const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } = 5 + useConnector() 5 6 6 7 const tokenInput = ref('') 7 8 const portInput = ref('31415') ··· 20 21 } 21 22 22 23 // Reset form when modal opens 23 - watch(open, (isOpen) => { 24 + watch(open, isOpen => { 24 25 if (isOpen) { 25 26 tokenInput.value = '' 26 27 } ··· 35 36 enter-from-class="opacity-0" 36 37 leave-to-class="opacity-0" 37 38 > 38 - <div 39 - v-if="open" 40 - class="fixed inset-0 z-50 flex items-center justify-center p-4" 41 - > 39 + <div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center p-4"> 42 40 <!-- Backdrop --> 43 41 <button 44 42 type="button" ··· 57 55 > 58 56 <div class="p-6"> 59 57 <div class="flex items-center justify-between mb-6"> 60 - <h2 61 - id="connector-modal-title" 62 - class="font-mono text-lg font-medium" 63 - > 58 + <h2 id="connector-modal-title" class="font-mono text-lg font-medium"> 64 59 Local Connector 65 60 </h2> 66 61 <button ··· 69 64 aria-label="Close" 70 65 @click="open = false" 71 66 > 72 - <span 73 - class="i-carbon-close block w-5 h-5" 74 - aria-hidden="true" 75 - /> 67 + <span class="i-carbon-close block w-5 h-5" aria-hidden="true" /> 76 68 </button> 77 69 </div> 78 70 79 71 <!-- Connected state --> 80 - <div 81 - v-if="isConnected" 82 - class="space-y-4" 83 - > 72 + <div v-if="isConnected" class="space-y-4"> 84 73 <div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg"> 85 - <span 86 - class="w-3 h-3 rounded-full bg-green-500" 87 - aria-hidden="true" 88 - /> 74 + <span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" /> 89 75 <div> 90 - <p class="font-mono text-sm text-fg"> 91 - Connected 92 - </p> 93 - <p 94 - v-if="npmUser" 95 - class="font-mono text-xs text-fg-muted" 96 - > 76 + <p class="font-mono text-sm text-fg">Connected</p> 77 + <p v-if="npmUser" class="font-mono text-xs text-fg-muted"> 97 78 Logged in as @{{ npmUser }} 98 79 </p> 99 80 </div> ··· 102 83 <!-- Operations Queue --> 103 84 <OperationsQueue /> 104 85 105 - <div 106 - v-if="!hasOperations" 107 - class="text-sm text-fg-muted" 108 - > 109 - You can now manage packages, organizations, and teams through the npmx.dev interface. 86 + <div v-if="!hasOperations" class="text-sm text-fg-muted"> 87 + You can now manage packages, organizations, and teams through the npmx.dev 88 + interface. 110 89 </div> 111 90 112 91 <button ··· 119 98 </div> 120 99 121 100 <!-- Disconnected state --> 122 - <form 123 - v-else 124 - class="space-y-4" 125 - @submit.prevent="handleConnect" 126 - > 101 + <form v-else class="space-y-4" @submit.prevent="handleConnect"> 127 102 <p class="text-sm text-fg-muted"> 128 103 Run the connector on your machine to enable admin features: 129 104 </p> ··· 133 108 <span class="text-fg ml-2">npx npmx-connector</span> 134 109 </div> 135 110 136 - <p class="text-sm text-fg-muted"> 137 - Then paste the token shown in your terminal: 138 - </p> 111 + <p class="text-sm text-fg-muted">Then paste the token shown in your terminal:</p> 139 112 140 113 <div class="space-y-3"> 141 114 <div> ··· 154 127 autocomplete="off" 155 128 spellcheck="false" 156 129 class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 157 - > 130 + /> 158 131 </div> 159 132 160 133 <details class="text-sm"> 161 - <summary class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200"> 134 + <summary 135 + class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200" 136 + > 162 137 Advanced options 163 138 </summary> 164 139 <div class="mt-3"> ··· 176 151 inputmode="numeric" 177 152 autocomplete="off" 178 153 class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 179 - > 154 + /> 180 155 </div> 181 156 </details> 182 157 </div>
+3 -4
app/components/ConnectorStatus.client.vue
··· 1 1 <script setup lang="ts"> 2 - const { isConnected, isConnecting, npmUser, error, activeOperations, hasPendingOperations } = useConnector() 2 + const { isConnected, isConnecting, npmUser, error, activeOperations, hasPendingOperations } = 3 + useConnector() 3 4 4 5 const showModal = ref(false) 5 6 const showTooltip = ref(false) ··· 73 74 </div> 74 75 </Transition> 75 76 76 - <ConnectorModal 77 - v-model:open="showModal" 78 - /> 77 + <ConnectorModal v-model:open="showModal" /> 79 78 </div> 80 79 </template>
+1 -4
app/components/LoadingSpinner.vue
··· 6 6 </script> 7 7 8 8 <template> 9 - <div 10 - aria-busy="true" 11 - class="flex items-center gap-3 text-fg-muted font-mono text-sm py-8" 12 - > 9 + <div aria-busy="true" class="flex items-center gap-3 text-fg-muted font-mono text-sm py-8"> 13 10 <span class="w-4 h-4 border-2 border-fg-subtle border-t-fg rounded-full animate-spin" /> 14 11 {{ text ?? 'Loading...' }} 15 12 </div>
+5 -17
app/components/OgImage/Default.vue
··· 1 1 <template> 2 - <div 3 - class="h-full w-full flex flex-col justify-center items-center bg-[#0a0a0a] text-[#fafafa]" 4 - > 5 - <h1 6 - class="text-6xl font-medium tracking-tight" 7 - style="font-family: 'Geist Mono'" 8 - > 9 - <span 10 - class="text-[#666666]" 11 - style="letter-spacing: -0.1em" 12 - >./</span> npmx 2 + <div class="h-full w-full flex flex-col justify-center items-center bg-[#0a0a0a] text-[#fafafa]"> 3 + <h1 class="text-6xl font-medium tracking-tight" style="font-family: 'Geist Mono'"> 4 + <span class="text-[#666666]" style="letter-spacing: -0.1em">./</span> npmx 13 5 </h1> 14 - <h1 class="text-3xl font-medium tracking-tight"> 15 - a better browser for the npm registry 16 - </h1> 6 + <h1 class="text-3xl font-medium tracking-tight">a better browser for the npm registry</h1> 17 7 18 - <p class="absolute bottom-12 text-lg text-[#404040]"> 19 - npmx.dev 20 - </p> 8 + <p class="absolute bottom-12 text-lg text-[#404040]">npmx.dev</p> 21 9 </div> 22 10 </template>
+13 -12
app/components/OgImage/Package.vue
··· 1 1 <script setup lang="ts"> 2 - withDefaults(defineProps<{ 3 - name: string 4 - version: string 5 - downloads?: string 6 - license?: string 7 - }>(), { 8 - downloads: '', 9 - license: '', 10 - }) 2 + withDefaults( 3 + defineProps<{ 4 + name: string 5 + version: string 6 + downloads?: string 7 + license?: string 8 + }>(), 9 + { 10 + downloads: '', 11 + license: '', 12 + }, 13 + ) 11 14 </script> 12 15 13 16 <template> ··· 25 28 <span v-if="license">{{ license }}</span> 26 29 </div> 27 30 28 - <p class="absolute bottom-12 text-lg text-[#404040]"> 29 - npmx.dev 30 - </p> 31 + <p class="absolute bottom-12 text-lg text-[#404040]">npmx.dev</p> 31 32 </div> 32 33 </template>
+66 -96
app/components/OperationsQueue.vue
··· 26 26 27 27 /** Check if any active operation needs OTP */ 28 28 const hasOtpFailures = computed(() => 29 - activeOperations.value.some((op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp), 29 + activeOperations.value.some( 30 + (op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp, 31 + ), 30 32 ) 31 33 32 34 async function handleApproveAll() { ··· 37 39 isExecuting.value = true 38 40 try { 39 41 await executeOperations(otp) 40 - } 41 - finally { 42 + } finally { 42 43 isExecuting.value = false 43 44 } 44 45 } ··· 69 70 70 71 function getStatusColor(status: string): string { 71 72 switch (status) { 72 - case 'pending': return 'bg-yellow-500' 73 - case 'approved': return 'bg-blue-500' 74 - case 'running': return 'bg-purple-500' 75 - case 'completed': return 'bg-green-500' 76 - case 'failed': return 'bg-red-500' 77 - default: return 'bg-fg-subtle' 73 + case 'pending': 74 + return 'bg-yellow-500' 75 + case 'approved': 76 + return 'bg-blue-500' 77 + case 'running': 78 + return 'bg-purple-500' 79 + case 'completed': 80 + return 'bg-green-500' 81 + case 'failed': 82 + return 'bg-red-500' 83 + default: 84 + return 'bg-fg-subtle' 78 85 } 79 86 } 80 87 81 88 function getStatusIcon(status: string): string { 82 89 switch (status) { 83 - case 'pending': return 'i-carbon-time' 84 - case 'approved': return 'i-carbon-checkmark' 85 - case 'running': return 'i-carbon-rotate' 86 - case 'completed': return 'i-carbon-checkmark-filled' 87 - case 'failed': return 'i-carbon-close-filled' 88 - default: return 'i-carbon-help' 90 + case 'pending': 91 + return 'i-carbon-time' 92 + case 'approved': 93 + return 'i-carbon-checkmark' 94 + case 'running': 95 + return 'i-carbon-rotate' 96 + case 'completed': 97 + return 'i-carbon-checkmark-filled' 98 + case 'failed': 99 + return 'i-carbon-close-filled' 100 + default: 101 + return 'i-carbon-help' 89 102 } 90 103 } 91 104 92 105 // Auto-refresh while executing 93 106 let refreshInterval: ReturnType<typeof setInterval> | null = null 94 - watch(isExecuting, (executing) => { 107 + watch(isExecuting, executing => { 95 108 if (executing) { 96 109 refreshInterval = setInterval(() => refreshState(), 1000) 97 - } 98 - else if (refreshInterval) { 110 + } else if (refreshInterval) { 99 111 clearInterval(refreshInterval) 100 112 refreshInterval = null 101 113 } ··· 109 121 </script> 110 122 111 123 <template> 112 - <div 113 - v-if="isConnected" 114 - class="space-y-4" 115 - > 124 + <div v-if="isConnected" class="space-y-4"> 116 125 <!-- Header --> 117 126 <div class="flex items-center justify-between"> 118 127 <h3 class="font-mono text-sm font-medium text-fg"> 119 128 Operations Queue 120 - <span 121 - v-if="hasActiveOperations" 122 - class="text-fg-muted" 123 - >({{ activeOperations.length }})</span> 129 + <span v-if="hasActiveOperations" class="text-fg-muted" 130 + >({{ activeOperations.length }})</span 131 + > 124 132 </h3> 125 133 <div class="flex items-center gap-2"> 126 134 <button ··· 138 146 aria-label="Refresh operations" 139 147 @click="refreshState" 140 148 > 141 - <span 142 - class="i-carbon-renew block w-4 h-4" 143 - aria-hidden="true" 144 - /> 149 + <span class="i-carbon-renew block w-4 h-4" aria-hidden="true" /> 145 150 </button> 146 151 </div> 147 152 </div> 148 153 149 154 <!-- Empty state --> 150 - <div 151 - v-if="!hasActiveOperations && !hasCompletedOperations" 152 - class="py-8 text-center" 153 - > 154 - <p class="font-mono text-sm text-fg-subtle"> 155 - No operations queued 156 - </p> 157 - <p class="font-mono text-xs text-fg-subtle mt-1"> 158 - Add operations from package or org pages 159 - </p> 155 + <div v-if="!hasActiveOperations && !hasCompletedOperations" class="py-8 text-center"> 156 + <p class="font-mono text-sm text-fg-subtle">No operations queued</p> 157 + <p class="font-mono text-xs text-fg-subtle mt-1">Add operations from package or org pages</p> 160 158 </div> 161 159 162 160 <!-- Active operations list --> 163 - <ul 164 - v-if="hasActiveOperations" 165 - class="space-y-2" 166 - aria-label="Active operations" 167 - > 161 + <ul v-if="hasActiveOperations" class="space-y-2" aria-label="Active operations"> 168 162 <li 169 163 v-for="op in activeOperations" 170 164 :key="op.id" ··· 202 196 v-else-if="op.result && (op.status === 'completed' || op.status === 'failed')" 203 197 class="mt-2 p-2 bg-[#0d0d0d] border border-border rounded text-xs font-mono" 204 198 > 205 - <pre 206 - v-if="op.result.stdout" 207 - class="text-fg-muted whitespace-pre-wrap" 208 - >{{ op.result.stdout }}</pre> 209 - <pre 210 - v-if="op.result.stderr" 211 - class="text-red-400 whitespace-pre-wrap" 212 - >{{ op.result.stderr }}</pre> 199 + <pre v-if="op.result.stdout" class="text-fg-muted whitespace-pre-wrap">{{ 200 + op.result.stdout 201 + }}</pre> 202 + <pre v-if="op.result.stderr" class="text-red-400 whitespace-pre-wrap">{{ 203 + op.result.stderr 204 + }}</pre> 213 205 </div> 214 206 </div> 215 207 ··· 222 214 aria-label="Approve operation" 223 215 @click="approveOperation(op.id)" 224 216 > 225 - <span 226 - class="i-carbon-checkmark block w-4 h-4" 227 - aria-hidden="true" 228 - /> 217 + <span class="i-carbon-checkmark block w-4 h-4" aria-hidden="true" /> 229 218 </button> 230 219 <button 231 220 v-if="op.status !== 'running'" ··· 234 223 aria-label="Remove operation" 235 224 @click="removeOperation(op.id)" 236 225 > 237 - <span 238 - class="i-carbon-close block w-4 h-4" 239 - aria-hidden="true" 240 - /> 226 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 241 227 </button> 242 228 </div> 243 229 </li> ··· 250 236 role="alert" 251 237 > 252 238 <div class="flex items-center gap-2 mb-2"> 253 - <span 254 - class="i-carbon-locked block w-4 h-4 text-amber-400 shrink-0" 255 - aria-hidden="true" 256 - /> 257 - <span class="font-mono text-sm text-amber-400"> 258 - Enter OTP to continue 259 - </span> 239 + <span class="i-carbon-locked block w-4 h-4 text-amber-400 shrink-0" aria-hidden="true" /> 240 + <span class="font-mono text-sm text-amber-400"> Enter OTP to continue </span> 260 241 </div> 261 - <form 262 - class="flex items-center gap-2" 263 - @submit.prevent="handleRetryWithOtp" 264 - > 265 - <label 266 - for="otp-input" 267 - class="sr-only" 268 - >One-time password</label> 242 + <form class="flex items-center gap-2" @submit.prevent="handleRetryWithOtp"> 243 + <label for="otp-input" class="sr-only">One-time password</label> 269 244 <input 270 245 id="otp-input" 271 246 v-model="otpInput" ··· 277 252 autocomplete="one-time-code" 278 253 spellcheck="false" 279 254 class="flex-1 px-3 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 280 - > 255 + /> 281 256 <button 282 257 type="submit" 283 258 :disabled="!otpInput.trim() || isExecuting" ··· 289 264 </div> 290 265 291 266 <!-- Action buttons --> 292 - <div 293 - v-if="hasActiveOperations" 294 - class="flex items-center gap-2 pt-2" 295 - > 267 + <div v-if="hasActiveOperations" class="flex items-center gap-2 pt-2"> 296 268 <button 297 269 v-if="hasPendingOperations" 298 270 type="button" ··· 313 285 </div> 314 286 315 287 <!-- Completed operations log (collapsed by default) --> 316 - <details 317 - v-if="hasCompletedOperations" 318 - class="mt-4 border-t border-border pt-4" 319 - > 320 - <summary class="flex items-center gap-2 font-mono text-xs text-fg-muted cursor-pointer hover:text-fg transition-colors duration-200 select-none"> 288 + <details v-if="hasCompletedOperations" class="mt-4 border-t border-border pt-4"> 289 + <summary 290 + class="flex items-center gap-2 font-mono text-xs text-fg-muted cursor-pointer hover:text-fg transition-colors duration-200 select-none" 291 + > 321 292 <span 322 293 class="i-carbon-chevron-right block w-3 h-3 transition-transform duration-200 [[open]>&]:rotate-90" 323 294 aria-hidden="true" 324 295 /> 325 296 Log ({{ completedOperations.length }}) 326 297 </summary> 327 - <ul 328 - class="mt-2 space-y-1" 329 - aria-label="Completed operations log" 330 - > 298 + <ul class="mt-2 space-y-1" aria-label="Completed operations log"> 331 299 <li 332 300 v-for="op in completedOperations" 333 301 :key="op.id" ··· 335 303 :class="op.status === 'completed' ? 'text-fg-muted' : 'text-red-400/80'" 336 304 > 337 305 <span 338 - :class="op.status === 'completed' ? 'i-carbon-checkmark-filled text-green-500' : 'i-carbon-close-filled text-red-500'" 306 + :class=" 307 + op.status === 'completed' 308 + ? 'i-carbon-checkmark-filled text-green-500' 309 + : 'i-carbon-close-filled text-red-500' 310 + " 339 311 class="w-3.5 h-3.5 shrink-0 mt-0.5" 340 312 aria-hidden="true" 341 313 /> ··· 345 317 <pre 346 318 v-if="op.status === 'failed' && op.result?.stderr" 347 319 class="mt-1 text-red-400/70 whitespace-pre-wrap text-[11px]" 348 - >{{ op.result.stderr }}</pre> 320 + >{{ op.result.stderr }}</pre 321 + > 349 322 </div> 350 323 <button 351 324 type="button" ··· 353 326 aria-label="Remove from log" 354 327 @click="removeOperation(op.id)" 355 328 > 356 - <span 357 - class="i-carbon-close block w-3 h-3" 358 - aria-hidden="true" 359 - /> 329 + <span class="i-carbon-close block w-3 h-3" aria-hidden="true" /> 360 330 </button> 361 331 </li> 362 332 </ul>
+69 -151
app/components/OrgMembersPanel.vue
··· 92 92 result = [...result].sort((a, b) => { 93 93 if (sortBy.value === 'name') { 94 94 return sortOrder.value === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) 95 - } 96 - else { 95 + } else { 97 96 const diff = rolePriority[a.role] - rolePriority[b.role] 98 97 return sortOrder.value === 'asc' ? diff : -diff 99 98 } ··· 122 121 const result = await listOrgUsers(props.orgName) 123 122 if (result) { 124 123 members.value = result 125 - } 126 - else { 124 + } else { 127 125 error.value = connectorError.value || 'Failed to load members' 128 126 } 129 - } 130 - finally { 127 + } finally { 131 128 isLoading.value = false 132 129 } 133 130 } ··· 151 148 }) 152 149 await Promise.all(teamPromises) 153 150 } 154 - } 155 - finally { 151 + } finally { 156 152 isLoadingTeams.value = false 157 153 } 158 154 } ··· 198 194 newUsername.value = '' 199 195 newTeam.value = '' 200 196 showAddMember.value = false 201 - } 202 - finally { 197 + } finally { 203 198 isAddingMember.value = false 204 199 } 205 200 } ··· 239 234 function toggleSort(field: 'name' | 'role') { 240 235 if (sortBy.value === field) { 241 236 sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc' 242 - } 243 - else { 237 + } else { 244 238 sortBy.value = field 245 239 sortOrder.value = 'asc' 246 240 } ··· 249 243 // Role badge color 250 244 function getRoleBadgeClass(role: string): string { 251 245 switch (role) { 252 - case 'owner': return 'bg-purple-500/20 text-purple-400 border-purple-500/30' 253 - case 'admin': return 'bg-blue-500/20 text-blue-400 border-blue-500/30' 254 - default: return 'bg-fg-subtle/20 text-fg-muted border-border' 246 + case 'owner': 247 + return 'bg-purple-500/20 text-purple-400 border-purple-500/30' 248 + case 'admin': 249 + return 'bg-blue-500/20 text-blue-400 border-blue-500/30' 250 + default: 251 + return 'bg-fg-subtle/20 text-fg-muted border-border' 255 252 } 256 253 } 257 254 ··· 261 258 } 262 259 263 260 // Load on mount when connected 264 - watch(isConnected, (connected) => { 265 - if (connected) { 266 - loadMembers() 267 - loadTeamMemberships() 268 - } 269 - }, { immediate: true }) 261 + watch( 262 + isConnected, 263 + connected => { 264 + if (connected) { 265 + loadMembers() 266 + loadTeamMemberships() 267 + } 268 + }, 269 + { immediate: true }, 270 + ) 270 271 271 272 // Refresh data when operations complete 272 273 watch(lastExecutionTime, () => { ··· 285 286 > 286 287 <!-- Header --> 287 288 <div class="flex items-center justify-between p-4 border-b border-border"> 288 - <h2 289 - id="members-heading" 290 - class="font-mono text-sm font-medium flex items-center gap-2" 291 - > 292 - <span 293 - class="i-carbon-user-multiple w-4 h-4 text-fg-muted" 294 - aria-hidden="true" 295 - /> 289 + <h2 id="members-heading" class="font-mono text-sm font-medium flex items-center gap-2"> 290 + <span class="i-carbon-user-multiple w-4 h-4 text-fg-muted" aria-hidden="true" /> 296 291 Members 297 - <span 298 - v-if="memberList.length > 0" 299 - class="text-fg-muted" 300 - >({{ memberList.length }})</span> 292 + <span v-if="memberList.length > 0" class="text-fg-muted">({{ memberList.length }})</span> 301 293 </h2> 302 294 <button 303 295 type="button" 304 296 class="p-1.5 text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 305 297 aria-label="Refresh members" 306 298 :disabled="isLoading" 307 - @click="loadMembers(); loadTeamMemberships()" 299 + @click=" 300 + loadMembers() 301 + loadTeamMemberships() 302 + " 308 303 > 309 304 <span 310 305 class="i-carbon-renew block w-4 h-4" ··· 321 316 class="absolute left-2 top-1/2 -translate-y-1/2 i-carbon-search w-3.5 h-3.5 text-fg-subtle" 322 317 aria-hidden="true" 323 318 /> 324 - <label 325 - for="members-search" 326 - class="sr-only" 327 - >Filter members</label> 319 + <label for="members-search" class="sr-only">Filter members</label> 328 320 <input 329 321 id="members-search" 330 322 v-model="searchQuery" ··· 333 325 placeholder="Filter members…" 334 326 autocomplete="off" 335 327 class="w-full pl-7 pr-2 py-1.5 font-mono text-sm bg-bg-subtle border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 336 - > 328 + /> 337 329 </div> 338 - <div 339 - class="flex items-center gap-1" 340 - role="group" 341 - aria-label="Filter by role" 342 - > 330 + <div class="flex items-center gap-1" role="group" aria-label="Filter by role"> 343 331 <button 344 332 v-for="role in ['all', 'owner', 'admin', 'developer'] as const" 345 333 :key="role" ··· 350 338 @click="filterRole = role" 351 339 > 352 340 {{ role }} 353 - <span 354 - v-if="role !== 'all'" 355 - class="text-fg-subtle" 356 - >({{ roleCounts[role] }})</span> 341 + <span v-if="role !== 'all'" class="text-fg-subtle">({{ roleCounts[role] }})</span> 357 342 </button> 358 343 </div> 359 344 <!-- Team filter --> 360 345 <div v-if="teamNames.length > 0"> 361 - <label 362 - for="team-filter" 363 - class="sr-only" 364 - >Filter by team</label> 346 + <label for="team-filter" class="sr-only">Filter by team</label> 365 347 <select 366 348 id="team-filter" 367 349 v-model="filterTeam" 368 350 name="team-filter" 369 351 class="px-2 py-1 font-mono text-xs bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 370 352 > 371 - <option :value="null"> 372 - all teams 373 - </option> 374 - <option 375 - v-for="team in teamNames" 376 - :key="team" 377 - :value="team" 378 - > 353 + <option :value="null">all teams</option> 354 + <option v-for="team in teamNames" :key="team" :value="team"> 379 355 {{ team }} 380 356 </option> 381 357 </select> 382 358 </div> 383 - <div 384 - class="flex items-center gap-1 text-xs" 385 - role="group" 386 - aria-label="Sort by" 387 - > 359 + <div class="flex items-center gap-1 text-xs" role="group" aria-label="Sort by"> 388 360 <button 389 361 type="button" 390 362 class="px-2 py-1 font-mono rounded transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" ··· 409 381 </div> 410 382 411 383 <!-- Loading state --> 412 - <div 413 - v-if="isLoading && memberList.length === 0" 414 - class="p-8 text-center" 415 - > 384 + <div v-if="isLoading && memberList.length === 0" class="p-8 text-center"> 416 385 <span 417 386 class="i-carbon-rotate block w-5 h-5 text-fg-muted animate-spin mx-auto" 418 387 aria-hidden="true" 419 388 /> 420 - <p class="font-mono text-sm text-fg-muted mt-2"> 421 - Loading members… 422 - </p> 389 + <p class="font-mono text-sm text-fg-muted mt-2">Loading members…</p> 423 390 </div> 424 391 425 392 <!-- Error state --> 426 - <div 427 - v-else-if="error" 428 - class="p-4 text-center" 429 - role="alert" 430 - > 393 + <div v-else-if="error" class="p-4 text-center" role="alert"> 431 394 <p class="font-mono text-sm text-red-400"> 432 395 {{ error }} 433 396 </p> ··· 441 404 </div> 442 405 443 406 <!-- Empty state --> 444 - <div 445 - v-else-if="memberList.length === 0" 446 - class="p-8 text-center" 447 - > 448 - <p class="font-mono text-sm text-fg-muted"> 449 - No members found 450 - </p> 407 + <div v-else-if="memberList.length === 0" class="p-8 text-center"> 408 + <p class="font-mono text-sm text-fg-muted">No members found</p> 451 409 </div> 452 410 453 411 <!-- Members list --> ··· 478 436 </div> 479 437 <div class="flex items-center gap-1"> 480 438 <!-- Role selector --> 481 - <label 482 - :for="`role-${member.name}`" 483 - class="sr-only" 484 - >Change role for {{ member.name }}</label> 439 + <label :for="`role-${member.name}`" class="sr-only" 440 + >Change role for {{ member.name }}</label 441 + > 485 442 <select 486 443 :id="`role-${member.name}`" 487 444 :value="member.role" 488 445 :name="`role-${member.name}`" 489 446 class="px-1.5 py-0.5 font-mono text-xs bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 cursor-pointer" 490 - @change="handleChangeRole(member.name, ($event.target as HTMLSelectElement).value as 'developer' | 'admin' | 'owner')" 447 + @change=" 448 + handleChangeRole( 449 + member.name, 450 + ($event.target as HTMLSelectElement).value as 'developer' | 'admin' | 'owner', 451 + ) 452 + " 491 453 > 492 - <option value="developer"> 493 - developer 494 - </option> 495 - <option value="admin"> 496 - admin 497 - </option> 498 - <option value="owner"> 499 - owner 500 - </option> 454 + <option value="developer">developer</option> 455 + <option value="admin">admin</option> 456 + <option value="owner">owner</option> 501 457 </select> 502 458 <!-- Remove button --> 503 459 <button ··· 506 462 :aria-label="`Remove ${member.name} from org`" 507 463 @click="handleRemoveMember(member.name)" 508 464 > 509 - <span 510 - class="i-carbon-close block w-4 h-4" 511 - aria-hidden="true" 512 - /> 465 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 513 466 </button> 514 467 </div> 515 468 </div> 516 469 <!-- Team badges --> 517 - <div 518 - v-if="member.teams.length > 0" 519 - class="flex flex-wrap gap-1 pl-0" 520 - > 470 + <div v-if="member.teams.length > 0" class="flex flex-wrap gap-1 pl-0"> 521 471 <button 522 472 v-for="team in member.teams" 523 473 :key="team" ··· 533 483 </ul> 534 484 535 485 <!-- No results --> 536 - <div 537 - v-if="memberList.length > 0 && filteredMembers.length === 0" 538 - class="p-4 text-center" 539 - > 540 - <p class="font-mono text-sm text-fg-muted"> 541 - No members match your filters 542 - </p> 486 + <div v-if="memberList.length > 0 && filteredMembers.length === 0" class="p-4 text-center"> 487 + <p class="font-mono text-sm text-fg-muted">No members match your filters</p> 543 488 </div> 544 489 545 490 <!-- Add member --> 546 491 <div class="p-3 border-t border-border"> 547 492 <div v-if="showAddMember"> 548 - <form 549 - class="space-y-2" 550 - @submit.prevent="handleAddMember" 551 - > 552 - <label 553 - for="new-member-username" 554 - class="sr-only" 555 - >Username</label> 493 + <form class="space-y-2" @submit.prevent="handleAddMember"> 494 + <label for="new-member-username" class="sr-only">Username</label> 556 495 <input 557 496 id="new-member-username" 558 497 v-model="newUsername" ··· 562 501 autocomplete="off" 563 502 spellcheck="false" 564 503 class="w-full px-2 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 565 - > 504 + /> 566 505 <div class="flex items-center gap-2"> 567 - <label 568 - for="new-member-role" 569 - class="sr-only" 570 - >Role</label> 506 + <label for="new-member-role" class="sr-only">Role</label> 571 507 <select 572 508 id="new-member-role" 573 509 v-model="newRole" 574 510 name="new-member-role" 575 511 class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 576 512 > 577 - <option value="developer"> 578 - developer 579 - </option> 580 - <option value="admin"> 581 - admin 582 - </option> 583 - <option value="owner"> 584 - owner 585 - </option> 513 + <option value="developer">developer</option> 514 + <option value="admin">admin</option> 515 + <option value="owner">owner</option> 586 516 </select> 587 517 <!-- Team selection --> 588 - <label 589 - for="new-member-team" 590 - class="sr-only" 591 - >Team</label> 518 + <label for="new-member-team" class="sr-only">Team</label> 592 519 <select 593 520 id="new-member-team" 594 521 v-model="newTeam" 595 522 name="new-member-team" 596 523 class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 597 524 > 598 - <option value=""> 599 - no team 600 - </option> 601 - <option 602 - v-for="team in teamNames" 603 - :key="team" 604 - :value="team" 605 - > 525 + <option value="">no team</option> 526 + <option v-for="team in teamNames" :key="team" :value="team"> 606 527 {{ team }} 607 528 </option> 608 529 </select> ··· 619 540 aria-label="Cancel adding member" 620 541 @click="showAddMember = false" 621 542 > 622 - <span 623 - class="i-carbon-close block w-4 h-4" 624 - aria-hidden="true" 625 - /> 543 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 626 544 </button> 627 545 </div> 628 546 </form>
+56 -128
app/components/OrgTeamsPanel.vue
··· 57 57 result = [...result].sort((a, b) => { 58 58 if (sortBy.value === 'name') { 59 59 return sortOrder.value === 'asc' ? a.localeCompare(b) : b.localeCompare(a) 60 - } 61 - else { 60 + } else { 62 61 const aCount = teamUsers.value[a]?.length ?? 0 63 62 const bCount = teamUsers.value[b]?.length ?? 0 64 63 return sortOrder.value === 'asc' ? aCount - bCount : bCount - aCount ··· 85 84 if (teamsResult) { 86 85 // Teams come as "org:team" format, extract just the team name 87 86 teams.value = teamsResult.map((t: string) => t.replace(`${props.orgName}:`, '')) 88 - } 89 - else { 87 + } else { 90 88 error.value = connectorError.value || 'Failed to load teams' 91 89 } 92 90 93 91 if (membersResult) { 94 92 orgMembers.value = membersResult 95 93 } 96 - } 97 - finally { 94 + } finally { 98 95 isLoadingTeams.value = false 99 96 } 100 97 } ··· 111 108 if (result) { 112 109 teamUsers.value[teamName] = result 113 110 } 114 - } 115 - finally { 111 + } finally { 116 112 isLoadingUsers.value[teamName] = false 117 113 } 118 114 } ··· 121 117 async function toggleTeam(teamName: string) { 122 118 if (expandedTeams.value.has(teamName)) { 123 119 expandedTeams.value.delete(teamName) 124 - } 125 - else { 120 + } else { 126 121 expandedTeams.value.add(teamName) 127 122 // Load users if not already loaded 128 123 if (!teamUsers.value[teamName]) { ··· 151 146 await addOperation(operation) 152 147 newTeamName.value = '' 153 148 showCreateTeam.value = false 154 - } 155 - finally { 149 + } finally { 156 150 isCreatingTeam.value = false 157 151 } 158 152 } ··· 212 206 213 207 newUserUsername.value = '' 214 208 showAddUserFor.value = null 215 - } 216 - finally { 209 + } finally { 217 210 isAddingUser.value = false 218 211 } 219 212 } ··· 235 228 function toggleSort(field: 'name' | 'members') { 236 229 if (sortBy.value === field) { 237 230 sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc' 238 - } 239 - else { 231 + } else { 240 232 sortBy.value = field 241 233 sortOrder.value = 'asc' 242 234 } 243 235 } 244 236 245 237 // Load on mount when connected 246 - watch(isConnected, (connected) => { 247 - if (connected) { 248 - loadTeams() 249 - } 250 - }, { immediate: true }) 238 + watch( 239 + isConnected, 240 + connected => { 241 + if (connected) { 242 + loadTeams() 243 + } 244 + }, 245 + { immediate: true }, 246 + ) 251 247 252 248 // Refresh data when operations complete 253 249 watch(lastExecutionTime, () => { ··· 265 261 > 266 262 <!-- Header --> 267 263 <div class="flex items-center justify-between p-4 border-b border-border"> 268 - <h2 269 - id="teams-heading" 270 - class="font-mono text-sm font-medium flex items-center gap-2" 271 - > 272 - <span 273 - class="i-carbon-group w-4 h-4 text-fg-muted" 274 - aria-hidden="true" 275 - /> 264 + <h2 id="teams-heading" class="font-mono text-sm font-medium flex items-center gap-2"> 265 + <span class="i-carbon-group w-4 h-4 text-fg-muted" aria-hidden="true" /> 276 266 Teams 277 - <span 278 - v-if="teams.length > 0" 279 - class="text-fg-muted" 280 - >({{ teams.length }})</span> 267 + <span v-if="teams.length > 0" class="text-fg-muted">({{ teams.length }})</span> 281 268 </h2> 282 269 <button 283 270 type="button" ··· 301 288 class="absolute left-2 top-1/2 -translate-y-1/2 i-carbon-search w-3.5 h-3.5 text-fg-subtle" 302 289 aria-hidden="true" 303 290 /> 304 - <label 305 - for="teams-search" 306 - class="sr-only" 307 - >Filter teams</label> 291 + <label for="teams-search" class="sr-only">Filter teams</label> 308 292 <input 309 293 id="teams-search" 310 294 v-model="searchQuery" ··· 313 297 placeholder="Filter teams…" 314 298 autocomplete="off" 315 299 class="w-full pl-7 pr-2 py-1.5 font-mono text-sm bg-bg-subtle border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 316 - > 300 + /> 317 301 </div> 318 - <div 319 - class="flex items-center gap-1 text-xs" 320 - role="group" 321 - aria-label="Sort by" 322 - > 302 + <div class="flex items-center gap-1 text-xs" role="group" aria-label="Sort by"> 323 303 <button 324 304 type="button" 325 305 class="px-2 py-1 font-mono rounded transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" ··· 344 324 </div> 345 325 346 326 <!-- Loading state --> 347 - <div 348 - v-if="isLoadingTeams && teams.length === 0" 349 - class="p-8 text-center" 350 - > 327 + <div v-if="isLoadingTeams && teams.length === 0" class="p-8 text-center"> 351 328 <span 352 329 class="i-carbon-rotate block w-5 h-5 text-fg-muted animate-spin mx-auto" 353 330 aria-hidden="true" 354 331 /> 355 - <p class="font-mono text-sm text-fg-muted mt-2"> 356 - Loading teams… 357 - </p> 332 + <p class="font-mono text-sm text-fg-muted mt-2">Loading teams…</p> 358 333 </div> 359 334 360 335 <!-- Error state --> 361 - <div 362 - v-else-if="error" 363 - class="p-4 text-center" 364 - role="alert" 365 - > 336 + <div v-else-if="error" class="p-4 text-center" role="alert"> 366 337 <p class="font-mono text-sm text-red-400"> 367 338 {{ error }} 368 339 </p> ··· 376 347 </div> 377 348 378 349 <!-- Empty state --> 379 - <div 380 - v-else-if="teams.length === 0" 381 - class="p-8 text-center" 382 - > 383 - <p class="font-mono text-sm text-fg-muted"> 384 - No teams found 385 - </p> 350 + <div v-else-if="teams.length === 0" class="p-8 text-center"> 351 + <p class="font-mono text-sm text-fg-muted">No teams found</p> 386 352 </div> 387 353 388 354 <!-- Teams list --> 389 - <ul 390 - v-else 391 - class="divide-y divide-border" 392 - aria-label="Organization teams" 393 - > 394 - <li 395 - v-for="teamName in filteredTeams" 396 - :key="teamName" 397 - class="bg-bg" 398 - > 355 + <ul v-else class="divide-y divide-border" aria-label="Organization teams"> 356 + <li v-for="teamName in filteredTeams" :key="teamName" class="bg-bg"> 399 357 <!-- Team header --> 400 - <div class="flex items-center justify-between p-3 hover:bg-bg-subtle transition-colors duration-200"> 358 + <div 359 + class="flex items-center justify-between p-3 hover:bg-bg-subtle transition-colors duration-200" 360 + > 401 361 <button 402 362 type="button" 403 363 class="flex-1 flex items-center gap-2 text-left rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" ··· 414 374 aria-hidden="true" 415 375 /> 416 376 <span class="font-mono text-sm text-fg">{{ teamName }}</span> 417 - <span 418 - v-if="teamUsers[teamName]" 419 - class="font-mono text-xs text-fg-subtle" 420 - > 421 - ({{ teamUsers[teamName].length }} member{{ teamUsers[teamName].length === 1 ? '' : 's' }}) 377 + <span v-if="teamUsers[teamName]" class="font-mono text-xs text-fg-subtle"> 378 + ({{ teamUsers[teamName].length }} member{{ 379 + teamUsers[teamName].length === 1 ? '' : 's' 380 + }}) 422 381 </span> 423 382 <span 424 383 v-if="isLoadingUsers[teamName]" ··· 432 391 :aria-label="`Delete team ${teamName}`" 433 392 @click.stop="handleDestroyTeam(teamName)" 434 393 > 435 - <span 436 - class="i-carbon-trash-can block w-4 h-4" 437 - aria-hidden="true" 438 - /> 394 + <span class="i-carbon-trash-can block w-4 h-4" aria-hidden="true" /> 439 395 </button> 440 396 </div> 441 397 ··· 468 424 :aria-label="`Remove ${user} from team`" 469 425 @click="handleRemoveUser(teamName, user)" 470 426 > 471 - <span 472 - class="i-carbon-close block w-3.5 h-3.5" 473 - aria-hidden="true" 474 - /> 427 + <span class="i-carbon-close block w-3.5 h-3.5" aria-hidden="true" /> 475 428 </button> 476 429 </li> 477 430 </ul> 478 - <p 479 - v-else-if="!isLoadingUsers[teamName]" 480 - class="font-mono text-xs text-fg-subtle py-1" 481 - > 431 + <p v-else-if="!isLoadingUsers[teamName]" class="font-mono text-xs text-fg-subtle py-1"> 482 432 No members 483 433 </p> 484 434 485 435 <!-- Add user form --> 486 - <div 487 - v-if="showAddUserFor === teamName" 488 - class="mt-2" 489 - > 490 - <form 491 - class="flex items-center gap-2" 492 - @submit.prevent="handleAddUser(teamName)" 493 - > 494 - <label 495 - :for="`add-user-${teamName}`" 496 - class="sr-only" 497 - >Username to add to {{ teamName }}</label> 436 + <div v-if="showAddUserFor === teamName" class="mt-2"> 437 + <form class="flex items-center gap-2" @submit.prevent="handleAddUser(teamName)"> 438 + <label :for="`add-user-${teamName}`" class="sr-only" 439 + >Username to add to {{ teamName }}</label 440 + > 498 441 <input 499 442 :id="`add-user-${teamName}`" 500 443 v-model="newUserUsername" ··· 504 447 autocomplete="off" 505 448 spellcheck="false" 506 449 class="flex-1 px-2 py-1 font-mono text-sm bg-bg-subtle border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 507 - > 450 + /> 508 451 <button 509 452 type="submit" 510 453 :disabled="!newUserUsername.trim() || isAddingUser" ··· 518 461 aria-label="Cancel adding user" 519 462 @click="showAddUserFor = null" 520 463 > 521 - <span 522 - class="i-carbon-close block w-4 h-4" 523 - aria-hidden="true" 524 - /> 464 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 525 465 </button> 526 466 </form> 527 467 </div> ··· 538 478 </ul> 539 479 540 480 <!-- No results --> 541 - <div 542 - v-if="teams.length > 0 && filteredTeams.length === 0" 543 - class="p-4 text-center" 544 - > 545 - <p class="font-mono text-sm text-fg-muted"> 546 - No teams match "{{ searchQuery }}" 547 - </p> 481 + <div v-if="teams.length > 0 && filteredTeams.length === 0" class="p-4 text-center"> 482 + <p class="font-mono text-sm text-fg-muted">No teams match "{{ searchQuery }}"</p> 548 483 </div> 549 484 550 485 <!-- Create team --> 551 486 <div class="p-3 border-t border-border"> 552 487 <div v-if="showCreateTeam"> 553 - <form 554 - class="flex items-center gap-2" 555 - @submit.prevent="handleCreateTeam" 556 - > 488 + <form class="flex items-center gap-2" @submit.prevent="handleCreateTeam"> 557 489 <div class="flex-1 flex items-center"> 558 - <span class="px-2 py-1.5 font-mono text-sm text-fg-subtle bg-bg border border-r-0 border-border rounded-l"> 490 + <span 491 + class="px-2 py-1.5 font-mono text-sm text-fg-subtle bg-bg border border-r-0 border-border rounded-l" 492 + > 559 493 {{ orgName }}: 560 494 </span> 561 - <label 562 - for="new-team-name" 563 - class="sr-only" 564 - >Team name</label> 495 + <label for="new-team-name" class="sr-only">Team name</label> 565 496 <input 566 497 id="new-team-name" 567 498 v-model="newTeamName" ··· 571 502 autocomplete="off" 572 503 spellcheck="false" 573 504 class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg border border-border rounded-r text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 574 - > 505 + /> 575 506 </div> 576 507 <button 577 508 type="submit" ··· 586 517 aria-label="Cancel creating team" 587 518 @click="showCreateTeam = false" 588 519 > 589 - <span 590 - class="i-carbon-close block w-4 h-4" 591 - aria-hidden="true" 592 - /> 520 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 593 521 </button> 594 522 </form> 595 523 </div>
+60 -104
app/components/PackageAccessControls.vue
··· 36 36 37 37 // Computed collaborator list with type detection 38 38 const collaboratorList = computed(() => { 39 - return Object.entries(collaborators.value).map(([name, perm]) => { 40 - // Check if this looks like a team (org:team format) or user 41 - const isTeam = name.includes(':') 42 - return { 43 - name, 44 - permission: perm, 45 - isTeam, 46 - displayName: isTeam ? name.split(':')[1] : name, 47 - } 48 - }).sort((a, b) => { 49 - // Teams first, then users 50 - if (a.isTeam !== b.isTeam) return a.isTeam ? -1 : 1 51 - return a.name.localeCompare(b.name) 52 - }) 39 + return Object.entries(collaborators.value) 40 + .map(([name, perm]) => { 41 + // Check if this looks like a team (org:team format) or user 42 + const isTeam = name.includes(':') 43 + return { 44 + name, 45 + permission: perm, 46 + isTeam, 47 + displayName: isTeam ? name.split(':')[1] : name, 48 + } 49 + }) 50 + .sort((a, b) => { 51 + // Teams first, then users 52 + if (a.isTeam !== b.isTeam) return a.isTeam ? -1 : 1 53 + return a.name.localeCompare(b.name) 54 + }) 53 55 }) 54 56 55 57 // Load collaborators ··· 63 65 const result = await listPackageCollaborators(props.packageName) 64 66 if (result) { 65 67 collaborators.value = result 66 - } 67 - else { 68 + } else { 68 69 error.value = connectorError.value || 'Failed to load collaborators' 69 70 } 70 - } 71 - finally { 71 + } finally { 72 72 isLoadingCollaborators.value = false 73 73 } 74 74 } ··· 85 85 // Teams come as "org:team" format, extract just the team name 86 86 teams.value = result.map((t: string) => t.replace(`${orgName.value}:`, '')) 87 87 } 88 - } 89 - finally { 88 + } finally { 90 89 isLoadingTeams.value = false 91 90 } 92 91 } ··· 112 111 await addOperation(operation) 113 112 selectedTeam.value = '' 114 113 showGrantAccess.value = false 115 - } 116 - finally { 114 + } finally { 117 115 isGranting.value = false 118 116 } 119 117 } ··· 138 136 } 139 137 140 138 // Load on mount when connected 141 - watch(isConnected, (connected) => { 142 - if (connected && orgName.value) { 143 - loadCollaborators() 144 - loadTeams() 145 - } 146 - }, { immediate: true }) 139 + watch( 140 + isConnected, 141 + connected => { 142 + if (connected && orgName.value) { 143 + loadCollaborators() 144 + loadTeams() 145 + } 146 + }, 147 + { immediate: true }, 148 + ) 147 149 148 150 // Reload when package changes 149 - watch(() => props.packageName, () => { 150 - if (isConnected.value && orgName.value) { 151 - loadCollaborators() 152 - loadTeams() 153 - } 154 - }) 151 + watch( 152 + () => props.packageName, 153 + () => { 154 + if (isConnected.value && orgName.value) { 155 + loadCollaborators() 156 + loadTeams() 157 + } 158 + }, 159 + ) 155 160 156 161 // Refresh data when operations complete 157 162 watch(lastExecutionTime, () => { ··· 163 168 </script> 164 169 165 170 <template> 166 - <section 167 - v-if="isConnected && orgName" 168 - aria-labelledby="access-heading" 169 - > 171 + <section v-if="isConnected && orgName" aria-labelledby="access-heading"> 170 172 <div class="flex items-center justify-between mb-3"> 171 - <h2 172 - id="access-heading" 173 - class="text-xs text-fg-subtle uppercase tracking-wider" 174 - > 173 + <h2 id="access-heading" class="text-xs text-fg-subtle uppercase tracking-wider"> 175 174 Team Access 176 175 </h2> 177 176 <button ··· 190 189 </div> 191 190 192 191 <!-- Loading state --> 193 - <div 194 - v-if="isLoadingCollaborators && collaboratorList.length === 0" 195 - class="py-4 text-center" 196 - > 192 + <div v-if="isLoadingCollaborators && collaboratorList.length === 0" class="py-4 text-center"> 197 193 <span 198 194 class="i-carbon-rotate block w-4 h-4 text-fg-muted animate-spin mx-auto" 199 195 aria-hidden="true" ··· 201 197 </div> 202 198 203 199 <!-- Error state --> 204 - <div 205 - v-else-if="error" 206 - class="text-xs text-red-400 mb-2" 207 - role="alert" 208 - > 200 + <div v-else-if="error" class="text-xs text-red-400 mb-2" role="alert"> 209 201 {{ error }} 210 202 </div> 211 203 212 204 <!-- Collaborators list --> 213 - <ul 214 - v-if="collaboratorList.length > 0" 215 - class="space-y-1 mb-3" 216 - aria-label="Team access list" 217 - > 205 + <ul v-if="collaboratorList.length > 0" class="space-y-1 mb-3" aria-label="Team access list"> 218 206 <li 219 207 v-for="collab in collaboratorList" 220 208 :key="collab.name" ··· 236 224 </span> 237 225 <span 238 226 class="px-1 py-0.5 font-mono text-xs rounded shrink-0" 239 - :class="collab.permission === 'read-write' 240 - ? 'bg-green-500/20 text-green-400' 241 - : 'bg-fg-subtle/20 text-fg-muted'" 227 + :class=" 228 + collab.permission === 'read-write' 229 + ? 'bg-green-500/20 text-green-400' 230 + : 'bg-fg-subtle/20 text-fg-muted' 231 + " 242 232 > 243 233 {{ collab.permission === 'read-write' ? 'rw' : 'ro' }} 244 234 </span> ··· 251 241 :aria-label="`Revoke ${collab.displayName} access`" 252 242 @click="handleRevokeAccess(collab.name)" 253 243 > 254 - <span 255 - class="i-carbon-close block w-3.5 h-3.5" 256 - aria-hidden="true" 257 - /> 244 + <span class="i-carbon-close block w-3.5 h-3.5" aria-hidden="true" /> 258 245 </button> 259 - <span 260 - v-else 261 - class="text-xs text-fg-subtle" 262 - > 263 - owner 264 - </span> 246 + <span v-else class="text-xs text-fg-subtle"> owner </span> 265 247 </li> 266 248 </ul> 267 249 268 - <p 269 - v-else-if="!isLoadingCollaborators && !error" 270 - class="text-xs text-fg-subtle mb-3" 271 - > 250 + <p v-else-if="!isLoadingCollaborators && !error" class="text-xs text-fg-subtle mb-3"> 272 251 No team access configured 273 252 </p> 274 253 275 254 <!-- Grant access form --> 276 255 <div v-if="showGrantAccess"> 277 - <form 278 - class="space-y-2" 279 - @submit.prevent="handleGrantAccess" 280 - > 256 + <form class="space-y-2" @submit.prevent="handleGrantAccess"> 281 257 <div class="flex items-center gap-2"> 282 - <label 283 - for="grant-team-select" 284 - class="sr-only" 285 - >Select team</label> 258 + <label for="grant-team-select" class="sr-only">Select team</label> 286 259 <select 287 260 id="grant-team-select" 288 261 v-model="selectedTeam" ··· 290 263 class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 291 264 :disabled="isLoadingTeams" 292 265 > 293 - <option 294 - value="" 295 - disabled 296 - > 266 + <option value="" disabled> 297 267 {{ isLoadingTeams ? 'Loading teams…' : 'Select team' }} 298 268 </option> 299 - <option 300 - v-for="team in teams" 301 - :key="team" 302 - :value="team" 303 - > 269 + <option v-for="team in teams" :key="team" :value="team"> 304 270 {{ orgName }}:{{ team }} 305 271 </option> 306 272 </select> 307 273 </div> 308 274 <div class="flex items-center gap-2"> 309 - <label 310 - for="grant-permission-select" 311 - class="sr-only" 312 - >Permission level</label> 275 + <label for="grant-permission-select" class="sr-only">Permission level</label> 313 276 <select 314 277 id="grant-permission-select" 315 278 v-model="permission" 316 279 name="grant-permission" 317 280 class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 318 281 > 319 - <option value="read-only"> 320 - read-only 321 - </option> 322 - <option value="read-write"> 323 - read-write 324 - </option> 282 + <option value="read-only">read-only</option> 283 + <option value="read-write">read-write</option> 325 284 </select> 326 285 <button 327 286 type="submit" ··· 336 295 aria-label="Cancel granting access" 337 296 @click="showGrantAccess = false" 338 297 > 339 - <span 340 - class="i-carbon-close block w-4 h-4" 341 - aria-hidden="true" 342 - /> 298 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 343 299 </button> 344 300 </div> 345 301 </form>
+8 -29
app/components/PackageCard.vue
··· 35 35 {{ result.package.name }} 36 36 </component> 37 37 <div class="flex items-center gap-1.5 shrink-0"> 38 - <span 39 - v-if="result.package.version" 40 - class="font-mono text-xs text-fg-subtle" 41 - > 38 + <span v-if="result.package.version" class="font-mono text-xs text-fg-subtle"> 42 39 v{{ result.package.version }} 43 40 </span> 44 41 <ProvenanceBadge ··· 51 48 </div> 52 49 </header> 53 50 54 - <p 55 - v-if="result.package.description" 56 - class="text-fg-muted text-sm line-clamp-2 mb-3" 57 - > 51 + <p v-if="result.package.description" class="text-fg-muted text-sm line-clamp-2 mb-3"> 58 52 <MarkdownText :text="result.package.description" /> 59 53 </p> 60 54 61 55 <footer class="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-fg-subtle"> 62 - <dl 63 - v-if="showPublisher || result.package.date" 64 - class="flex items-center gap-4 m-0" 65 - > 56 + <dl v-if="showPublisher || result.package.date" class="flex items-center gap-4 m-0"> 66 57 <div 67 58 v-if="showPublisher && result.package.publisher?.username" 68 59 class="flex items-center gap-1.5" 69 60 > 70 - <dt class="sr-only"> 71 - Publisher 72 - </dt> 73 - <dd class="font-mono"> 74 - @{{ result.package.publisher.username }} 75 - </dd> 61 + <dt class="sr-only">Publisher</dt> 62 + <dd class="font-mono">@{{ result.package.publisher.username }}</dd> 76 63 </div> 77 - <div 78 - v-if="result.package.date" 79 - class="flex items-center gap-1.5" 80 - > 81 - <dt class="sr-only"> 82 - Updated 83 - </dt> 64 + <div v-if="result.package.date" class="flex items-center gap-1.5"> 65 + <dt class="sr-only">Updated</dt> 84 66 <dd> 85 67 <time :datetime="result.package.date">{{ formatDate(result.package.date) }}</time> 86 68 </dd> ··· 94 76 aria-label="Keywords" 95 77 class="flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0" 96 78 > 97 - <li 98 - v-for="keyword in result.package.keywords.slice(0, 5)" 99 - :key="keyword" 100 - > 79 + <li v-for="keyword in result.package.keywords.slice(0, 5)" :key="keyword"> 101 80 <NuxtLink 102 81 :to="`/search?q=keywords:${encodeURIComponent(keyword)}`" 103 82 class="tag decoration-none"
+7 -25
app/components/PackageDependencies.vue
··· 13 13 // Sort dependencies alphabetically 14 14 const sortedDependencies = computed(() => { 15 15 if (!props.dependencies) return [] 16 - return Object.entries(props.dependencies) 17 - .sort(([a], [b]) => a.localeCompare(b)) 16 + return Object.entries(props.dependencies).sort(([a], [b]) => a.localeCompare(b)) 18 17 }) 19 18 20 19 // Sort peer dependencies alphabetically, with required first then optional ··· 49 48 <template> 50 49 <div class="space-y-8"> 51 50 <!-- Dependencies --> 52 - <section 53 - v-if="sortedDependencies.length > 0" 54 - aria-labelledby="dependencies-heading" 55 - > 51 + <section v-if="sortedDependencies.length > 0" aria-labelledby="dependencies-heading"> 56 52 <div class="flex items-center justify-between mb-3"> 57 - <h2 58 - id="dependencies-heading" 59 - class="text-xs text-fg-subtle uppercase tracking-wider" 60 - > 53 + <h2 id="dependencies-heading" class="text-xs text-fg-subtle uppercase tracking-wider"> 61 54 Dependencies ({{ sortedDependencies.length }}) 62 55 </h2> 63 56 <a ··· 68 61 aria-label="View dependency graph" 69 62 title="View dependency graph" 70 63 > 71 - <span class="text-xs uppercase tracking-wider"> 72 - Graph 73 - </span> 64 + <span class="text-xs uppercase tracking-wider"> Graph </span> 74 65 </a> 75 66 </div> 76 - <ul 77 - class="space-y-1 list-none m-0 p-0" 78 - aria-label="Package dependencies" 79 - > 67 + <ul class="space-y-1 list-none m-0 p-0" aria-label="Package dependencies"> 80 68 <li 81 69 v-for="[dep, version] in sortedDependencies.slice(0, depsExpanded ? undefined : 10)" 82 70 :key="dep" ··· 108 96 </section> 109 97 110 98 <!-- Peer Dependencies --> 111 - <section 112 - v-if="sortedPeerDependencies.length > 0" 113 - aria-labelledby="peer-dependencies-heading" 114 - > 99 + <section v-if="sortedPeerDependencies.length > 0" aria-labelledby="peer-dependencies-heading"> 115 100 <h2 116 101 id="peer-dependencies-heading" 117 102 class="text-xs text-fg-subtle uppercase tracking-wider mb-3" 118 103 > 119 104 Peer Dependencies ({{ sortedPeerDependencies.length }}) 120 105 </h2> 121 - <ul 122 - class="space-y-1 list-none m-0 p-0" 123 - aria-label="Package peer dependencies" 124 - > 106 + <ul class="space-y-1 list-none m-0 p-0" aria-label="Package peer dependencies"> 125 107 <li 126 108 v-for="peer in sortedPeerDependencies.slice(0, peerDepsExpanded ? undefined : 10)" 127 109 :key="peer.name"
+1 -5
app/components/PackageList.vue
··· 19 19 class="animate-fade-in animate-fill-both" 20 20 :style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }" 21 21 > 22 - <PackageCard 23 - :result="result" 24 - :heading-level="headingLevel" 25 - :show-publisher="showPublisher" 26 - /> 22 + <PackageCard :result="result" :heading-level="headingLevel" :show-publisher="showPublisher" /> 27 23 </li> 28 24 </ol> 29 25 </template>
+25 -49
app/components/PackageMaintainers.vue
··· 3 3 4 4 const props = defineProps<{ 5 5 packageName: string 6 - maintainers?: Array<{ name?: string, email?: string }> 6 + maintainers?: Array<{ name?: string; email?: string }> 7 7 }>() 8 8 9 9 const { ··· 38 38 const maintainerAccess = computed(() => { 39 39 if (!props.maintainers) return [] 40 40 41 - return props.maintainers.map((maintainer) => { 41 + return props.maintainers.map(maintainer => { 42 42 const name = maintainer.name 43 43 if (!name) return { ...maintainer, accessVia: [] as string[] } 44 44 ··· 97 97 } 98 98 await Promise.all(teamPromises) 99 99 } 100 - } 101 - finally { 100 + } finally { 102 101 isLoadingAccess.value = false 103 102 } 104 103 } ··· 122 121 await addOperation(operation) 123 122 newOwnerUsername.value = '' 124 123 showAddOwner.value = false 125 - } 126 - finally { 124 + } finally { 127 125 isAdding.value = false 128 126 } 129 127 } ··· 143 141 } 144 142 145 143 // Load access info when connected and for scoped packages 146 - watch([isConnected, () => props.packageName], ([connected]) => { 147 - if (connected && orgName.value) { 148 - loadAccessInfo() 149 - } 150 - }, { immediate: true }) 144 + watch( 145 + [isConnected, () => props.packageName], 146 + ([connected]) => { 147 + if (connected && orgName.value) { 148 + loadAccessInfo() 149 + } 150 + }, 151 + { immediate: true }, 152 + ) 151 153 152 154 // Refresh data when operations complete 153 155 watch(lastExecutionTime, () => { ··· 158 160 </script> 159 161 160 162 <template> 161 - <section 162 - v-if="maintainers?.length" 163 - aria-labelledby="maintainers-heading" 164 - > 165 - <h2 166 - id="maintainers-heading" 167 - class="text-xs text-fg-subtle uppercase tracking-wider mb-3" 168 - > 163 + <section v-if="maintainers?.length" aria-labelledby="maintainers-heading"> 164 + <h2 id="maintainers-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3"> 169 165 Maintainers 170 166 </h2> 171 - <ul 172 - class="space-y-2 list-none m-0 p-0" 173 - aria-label="Package maintainers" 174 - > 167 + <ul class="space-y-2 list-none m-0 p-0" aria-label="Package maintainers"> 175 168 <li 176 169 v-for="maintainer in maintainerAccess.slice(0, canManageOwners ? undefined : 5)" 177 170 :key="maintainer.name ?? maintainer.email" ··· 185 178 > 186 179 @{{ maintainer.name }} 187 180 </NuxtLink> 188 - <span 189 - v-else 190 - class="font-mono text-sm text-fg-muted" 191 - >{{ maintainer.email }}</span> 181 + <span v-else class="font-mono text-sm text-fg-muted">{{ maintainer.email }}</span> 192 182 193 183 <!-- Access source badges --> 194 184 <span ··· 200 190 <span 201 191 v-if="canManageOwners && maintainer.name === npmUser" 202 192 class="text-xs text-fg-subtle shrink-0" 203 - >(you)</span> 193 + >(you)</span 194 + > 204 195 </div> 205 196 206 197 <!-- Remove button (only when can manage and not self) --> ··· 211 202 :aria-label="`Remove ${maintainer.name} as owner`" 212 203 @click="handleRemoveOwner(maintainer.name)" 213 204 > 214 - <span 215 - class="i-carbon-close block w-3.5 h-3.5" 216 - aria-hidden="true" 217 - /> 205 + <span class="i-carbon-close block w-3.5 h-3.5" aria-hidden="true" /> 218 206 </button> 219 207 </li> 220 208 </ul> 221 209 222 210 <!-- Add owner form (only when can manage) --> 223 - <div 224 - v-if="canManageOwners" 225 - class="mt-3" 226 - > 211 + <div v-if="canManageOwners" class="mt-3"> 227 212 <div v-if="showAddOwner"> 228 - <form 229 - class="flex items-center gap-2" 230 - @submit.prevent="handleAddOwner" 231 - > 232 - <label 233 - for="add-owner-username" 234 - class="sr-only" 235 - >Username to add as owner</label> 213 + <form class="flex items-center gap-2" @submit.prevent="handleAddOwner"> 214 + <label for="add-owner-username" class="sr-only">Username to add as owner</label> 236 215 <input 237 216 id="add-owner-username" 238 217 v-model="newOwnerUsername" ··· 242 221 autocomplete="off" 243 222 spellcheck="false" 244 223 class="flex-1 px-2 py-1 font-mono text-sm bg-bg-subtle border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 245 - > 224 + /> 246 225 <button 247 226 type="submit" 248 227 :disabled="!newOwnerUsername.trim() || isAdding" ··· 256 235 aria-label="Cancel adding owner" 257 236 @click="showAddOwner = false" 258 237 > 259 - <span 260 - class="i-carbon-close block w-4 h-4" 261 - aria-hidden="true" 262 - /> 238 + <span class="i-carbon-close block w-4 h-4" aria-hidden="true" /> 263 239 </button> 264 240 </form> 265 241 </div>
+8 -28
app/components/PackageSkeleton.vue
··· 1 1 <template> 2 - <article 3 - aria-busy="true" 4 - aria-label="Loading package details" 5 - class="animate-fade-in" 6 - > 2 + <article aria-busy="true" aria-label="Loading package details" class="animate-fade-in"> 7 3 <!-- Package header - matches header in [...name].vue --> 8 4 <header class="mb-8 pb-8 border-b border-border"> 9 5 <div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-4"> ··· 30 26 <dl class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4 mt-6"> 31 27 <!-- License --> 32 28 <div class="space-y-1"> 33 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 34 - License 35 - </dt> 29 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">License</dt> 36 30 <dd class="font-mono text-sm"> 37 31 <span class="skeleton inline-block h-5 w-12" /> 38 32 </dd> ··· 40 34 41 35 <!-- Weekly --> 42 36 <div class="space-y-1"> 43 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 44 - Weekly 45 - </dt> 37 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Weekly</dt> 46 38 <dd class="font-mono text-sm"> 47 39 <span class="skeleton inline-block h-5 w-20" /> 48 40 </dd> ··· 50 42 51 43 <!-- Size --> 52 44 <div class="space-y-1"> 53 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 54 - Size 55 - </dt> 45 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Size</dt> 56 46 <dd class="font-mono text-sm"> 57 47 <span class="skeleton inline-block h-5 w-16" /> 58 48 </dd> ··· 60 50 61 51 <!-- Deps --> 62 52 <div class="space-y-1"> 63 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 64 - Deps 65 - </dt> 53 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Deps</dt> 66 54 <dd class="font-mono text-sm"> 67 55 <span class="skeleton inline-block h-5 w-8" /> 68 56 </dd> ··· 70 58 71 59 <!-- Updated --> 72 60 <div class="space-y-1 col-span-2"> 73 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 74 - Updated 75 - </dt> 61 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Updated</dt> 76 62 <dd class="font-mono text-sm"> 77 63 <span class="skeleton inline-block h-5 w-28" /> 78 64 </dd> ··· 80 66 </dl> 81 67 82 68 <!-- Links: mt-6, flex flex-wrap items-center gap-4 --> 83 - <nav 84 - aria-label="Package links" 85 - class="mt-6" 86 - > 69 + <nav aria-label="Package links" class="mt-6"> 87 70 <ul class="flex flex-wrap items-center gap-4 list-none m-0 p-0"> 88 71 <li> 89 72 <span class="skeleton inline-block h-5 w-14" /> ··· 102 85 </header> 103 86 104 87 <!-- Install section: mb-8 --> 105 - <section 106 - aria-labelledby="install-heading-skeleton" 107 - class="mb-8" 108 - > 88 + <section aria-labelledby="install-heading-skeleton" class="mb-8"> 109 89 <h2 110 90 id="install-heading-skeleton" 111 91 class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
+40 -75
app/components/PackageVersions.vue
··· 24 24 } 25 25 26 26 // Parse semver 27 - function parseVersion(version: string): { major: number, minor: number, patch: number, prerelease: string } { 27 + function parseVersion(version: string): { 28 + major: number 29 + minor: number 30 + patch: number 31 + prerelease: string 32 + } { 28 33 const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?/) 29 34 if (!match) return { major: 0, minor: 0, patch: 0, prerelease: '' } 30 35 return { ··· 96 101 const loadingTags = ref<Set<string>>(new Set()) 97 102 98 103 const otherVersionsExpanded = ref(false) 99 - const otherMajorGroups = ref<Array<{ major: number, versions: VersionDisplay[], expanded: boolean }>>([]) 104 + const otherMajorGroups = ref< 105 + Array<{ major: number; versions: VersionDisplay[]; expanded: boolean }> 106 + >([]) 100 107 const otherVersionsLoading = ref(false) 101 108 102 109 // Cached full version list ··· 115 122 if (allVersionsCache.value) return allVersionsCache.value 116 123 117 124 if (loadingVersions.value) { 118 - await new Promise<void>((resolve) => { 119 - const unwatch = watch(allVersionsCache, (val) => { 125 + await new Promise<void>(resolve => { 126 + const unwatch = watch(allVersionsCache, val => { 120 127 if (val) { 121 128 unwatch() 122 129 resolve() ··· 148 155 allVersionsCache.value = versions 149 156 hasLoadedAll.value = true 150 157 return versions 151 - } 152 - finally { 158 + } finally { 153 159 loadingVersions.value = false 154 160 } 155 161 } ··· 169 175 const tagChannel = getPrereleaseChannel(tagVersion) 170 176 171 177 const channelVersions = allVersions 172 - .filter((v) => { 178 + .filter(v => { 173 179 const vParsed = parseVersion(v.version) 174 180 const vChannel = getPrereleaseChannel(v.version) 175 181 return vParsed.major === tagParsed.major && vChannel === tagChannel ··· 235 241 try { 236 242 const allVersions = await loadAllVersions() 237 243 processLoadedVersions(allVersions) 238 - } 239 - catch (error) { 244 + } catch (error) { 240 245 console.error('Failed to load versions:', error) 241 - } 242 - finally { 246 + } finally { 243 247 loadingTags.value.delete(tag) 244 248 loadingTags.value = new Set(loadingTags.value) 245 249 } ··· 261 265 try { 262 266 const allVersions = await loadAllVersions() 263 267 processLoadedVersions(allVersions) 264 - } 265 - catch (error) { 268 + } catch (error) { 266 269 console.error('Failed to load versions:', error) 267 - } 268 - finally { 270 + } finally { 269 271 otherVersionsLoading.value = false 270 272 } 271 273 } ··· 296 298 </script> 297 299 298 300 <template> 299 - <section 300 - v-if="initialTagRows.length > 0" 301 - aria-labelledby="versions-heading" 302 - > 303 - <h2 304 - id="versions-heading" 305 - class="text-xs text-fg-subtle uppercase tracking-wider mb-3" 306 - > 301 + <section v-if="initialTagRows.length > 0" aria-labelledby="versions-heading"> 302 + <h2 id="versions-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3"> 307 303 Versions 308 304 </h2> 309 305 310 306 <div class="space-y-0.5"> 311 307 <!-- Dist-tag rows --> 312 - <div 313 - v-for="row in initialTagRows" 314 - :key="row.id" 315 - > 308 + <div v-for="row in initialTagRows" :key="row.id"> 316 309 <div class="flex items-center gap-2"> 317 310 <!-- Expand button (only if there are more versions to show) --> 318 311 <button ··· 323 316 :aria-label="expandedTags.has(row.tag) ? `Collapse ${row.tag}` : `Expand ${row.tag}`" 324 317 @click="expandTagRow(row.tag)" 325 318 > 326 - <span 327 - v-if="loadingTags.has(row.tag)" 328 - class="i-carbon-rotate w-3 h-3 animate-spin" 329 - /> 319 + <span v-if="loadingTags.has(row.tag)" class="i-carbon-rotate w-3 h-3 animate-spin" /> 330 320 <span 331 321 v-else 332 322 class="w-3 h-3 transition-transform duration-200" 333 - :class="expandedTags.has(row.tag) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'" 323 + :class=" 324 + expandedTags.has(row.tag) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right' 325 + " 334 326 /> 335 327 </button> 336 - <span 337 - v-else 338 - class="w-4" 339 - /> 328 + <span v-else class="w-4" /> 340 329 341 330 <!-- Version info --> 342 331 <div class="flex-1 flex items-center justify-between py-1.5 text-sm gap-2 min-w-0"> ··· 347 336 > 348 337 {{ row.primaryVersion.version }} 349 338 </NuxtLink> 350 - <span class="px-1.5 py-0.5 text-[10px] font-semibold text-fg-subtle bg-bg-muted border border-border rounded shrink-0"> 339 + <span 340 + class="px-1.5 py-0.5 text-[10px] font-semibold text-fg-subtle bg-bg-muted border border-border rounded shrink-0" 341 + > 351 342 {{ row.tag }} 352 343 </span> 353 344 </div> ··· 394 385 </span> 395 386 </div> 396 387 <div class="flex items-center gap-2 shrink-0"> 397 - <time 398 - v-if="v.time" 399 - :datetime="v.time" 400 - class="text-[10px] text-fg-subtle" 401 - > 388 + <time v-if="v.time" :datetime="v.time" class="text-[10px] text-fg-subtle"> 402 389 {{ formatDate(v.time) }} 403 390 </time> 404 391 <ProvenanceBadge ··· 420 407 :aria-expanded="otherVersionsExpanded" 421 408 @click="expandOtherVersions" 422 409 > 423 - <span class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg transition-colors"> 424 - <span 425 - v-if="otherVersionsLoading" 426 - class="i-carbon-rotate w-3 h-3 animate-spin" 427 - /> 410 + <span 411 + class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg transition-colors" 412 + > 413 + <span v-if="otherVersionsLoading" class="i-carbon-rotate w-3 h-3 animate-spin" /> 428 414 <span 429 415 v-else 430 416 class="w-3 h-3 transition-transform duration-200" 431 417 :class="otherVersionsExpanded ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'" 432 418 /> 433 419 </span> 434 - <span class="text-xs text-fg-muted py-1.5"> 435 - Other versions 436 - </span> 420 + <span class="text-xs text-fg-muted py-1.5"> Other versions </span> 437 421 </button> 438 422 439 423 <!-- Expanded other versions --> 440 - <div 441 - v-if="otherVersionsExpanded" 442 - class="ml-4 pl-2 border-l border-border space-y-0.5" 443 - > 424 + <div v-if="otherVersionsExpanded" class="ml-4 pl-2 border-l border-border space-y-0.5"> 444 425 <template v-if="otherMajorGroups.length > 0"> 445 - <div 446 - v-for="(group, groupIndex) in otherMajorGroups" 447 - :key="group.major" 448 - > 426 + <div v-for="(group, groupIndex) in otherMajorGroups" :key="group.major"> 449 427 <!-- Major group header --> 450 428 <button 451 429 v-if="group.versions.length > 1" ··· 469 447 </span> 470 448 </button> 471 449 <!-- Single version (no expand needed) --> 472 - <div 473 - v-else 474 - class="flex items-center gap-2 py-1" 475 - > 450 + <div v-else class="flex items-center gap-2 py-1"> 476 451 <span class="w-3" /> 477 452 <NuxtLink 478 453 :to="`/package/${packageName}/v/${group.versions[0]?.version}`" ··· 489 464 </div> 490 465 491 466 <!-- Major group versions --> 492 - <div 493 - v-if="group.expanded && group.versions.length > 1" 494 - class="ml-5 space-y-0.5" 495 - > 467 + <div v-if="group.expanded && group.versions.length > 1" class="ml-5 space-y-0.5"> 496 468 <div 497 469 v-for="v in group.versions.slice(1)" 498 470 :key="v.version" ··· 513 485 </span> 514 486 </div> 515 487 <div class="flex items-center gap-2 shrink-0"> 516 - <time 517 - v-if="v.time" 518 - :datetime="v.time" 519 - class="text-[10px] text-fg-subtle" 520 - > 488 + <time v-if="v.time" :datetime="v.time" class="text-[10px] text-fg-subtle"> 521 489 {{ formatDate(v.time) }} 522 490 </time> 523 491 <ProvenanceBadge ··· 531 499 </div> 532 500 </div> 533 501 </template> 534 - <div 535 - v-else-if="hasLoadedAll" 536 - class="py-1 text-xs text-fg-subtle" 537 - > 502 + <div v-else-if="hasLoadedAll" class="py-1 text-xs text-fg-subtle"> 538 503 All versions are covered by tags above 539 504 </div> 540 505 </div>
+12 -10
app/components/ProvenanceBadge.vue
··· 23 23 target="_blank" 24 24 rel="noopener noreferrer" 25 25 class="inline-flex items-center gap-1 text-xs font-mono text-fg-muted hover:text-fg transition-colors duration-200" 26 - :title="provider ? `Verified: published via ${providerLabels[provider] ?? provider}` : 'Verified provenance'" 26 + :title=" 27 + provider 28 + ? `Verified: published via ${providerLabels[provider] ?? provider}` 29 + : 'Verified provenance' 30 + " 27 31 > 28 32 <span 29 33 class="i-solar-shield-check-outline shrink-0" 30 34 :class="compact ? 'w-3.5 h-3.5' : 'w-4 h-4'" 31 35 /> 32 - <span 33 - v-if="!compact" 34 - class="sr-only sm:not-sr-only" 35 - >verified</span> 36 + <span v-if="!compact" class="sr-only sm:not-sr-only">verified</span> 36 37 </a> 37 38 <span 38 39 v-else 39 40 class="inline-flex items-center gap-1 text-xs font-mono text-fg-muted" 40 - :title="provider ? `Verified: published via ${providerLabels[provider] ?? provider}` : 'Verified provenance'" 41 + :title=" 42 + provider 43 + ? `Verified: published via ${providerLabels[provider] ?? provider}` 44 + : 'Verified provenance' 45 + " 41 46 > 42 47 <span 43 48 class="i-solar-shield-check-outline shrink-0" 44 49 :class="compact ? 'w-3.5 h-3.5' : 'w-4 h-4'" 45 50 /> 46 - <span 47 - v-if="!compact" 48 - class="sr-only sm:not-sr-only" 49 - >verified</span> 51 + <span v-if="!compact" class="sr-only sm:not-sr-only">verified</span> 50 52 </span> 51 53 </template>
+14 -17
app/components/UserCombobox.vue
··· 31 31 return props.suggestions.slice(0, 10) // Show first 10 when empty 32 32 } 33 33 const query = inputValue.value.toLowerCase().replace(/^@/, '') 34 - return props.suggestions 35 - .filter(s => s.toLowerCase().includes(query)) 36 - .slice(0, 10) 34 + return props.suggestions.filter(s => s.toLowerCase().includes(query)).slice(0, 10) 37 35 }) 38 36 39 37 // Check if current input matches a suggestion exactly ··· 82 80 const username = inputValue.value.trim().replace(/^@/, '') 83 81 if (!username) return 84 82 85 - const inSuggestions = props.suggestions.some( 86 - s => s.toLowerCase() === username.toLowerCase(), 87 - ) 83 + const inSuggestions = props.suggestions.some(s => s.toLowerCase() === username.toLowerCase()) 88 84 emit('select', username, inSuggestions) 89 85 inputValue.value = '' 90 86 isOpen.value = false ··· 117 113 const selectedSuggestion = filteredSuggestions.value[highlightedIndex.value] 118 114 if (highlightedIndex.value >= 0 && selectedSuggestion) { 119 115 selectSuggestion(selectedSuggestion) 120 - } 121 - else { 116 + } else { 122 117 handleSubmit() 123 118 } 124 119 break ··· 131 126 } 132 127 133 128 // Scroll highlighted item into view 134 - watch(highlightedIndex, (index) => { 129 + watch(highlightedIndex, index => { 135 130 if (index >= 0 && listRef.value) { 136 131 const item = listRef.value.children[index] as HTMLElement 137 132 item?.scrollIntoView({ block: 'nearest' }) ··· 147 142 148 143 <template> 149 144 <div class="relative"> 150 - <label 151 - v-if="label" 152 - :for="inputId" 153 - class="sr-only" 154 - >{{ label }}</label> 145 + <label v-if="label" :for="inputId" class="sr-only">{{ label }}</label> 155 146 <input 156 147 :id="inputId" 157 148 ref="inputRef" ··· 166 157 :aria-expanded="isOpen && (filteredSuggestions.length > 0 || showNewUserHint)" 167 158 aria-haspopup="listbox" 168 159 :aria-controls="listboxId" 169 - :aria-activedescendant="highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined" 160 + :aria-activedescendant=" 161 + highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined 162 + " 170 163 class="w-full px-2 py-1 font-mono text-sm bg-bg-subtle border border-border rounded text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 disabled:opacity-50 disabled:cursor-not-allowed" 171 164 @input="handleInput" 172 165 @focus="handleFocus" 173 166 @blur="handleBlur" 174 167 @keydown="handleKeydown" 175 - > 168 + /> 176 169 177 170 <!-- Dropdown --> 178 171 <Transition ··· 199 192 role="option" 200 193 :aria-selected="highlightedIndex === index" 201 194 class="px-2 py-1 font-mono text-sm cursor-pointer transition-colors duration-100" 202 - :class="highlightedIndex === index ? 'bg-bg-muted text-fg' : 'text-fg-muted hover:bg-bg-subtle hover:text-fg'" 195 + :class=" 196 + highlightedIndex === index 197 + ? 'bg-bg-muted text-fg' 198 + : 'text-fg-muted hover:bg-bg-subtle hover:text-fg' 199 + " 203 200 @mouseenter="highlightedIndex = index" 204 201 @click="selectSuggestion(username)" 205 202 >
+51 -39
app/composables/useConnector.ts
··· 54 54 55 55 export const useConnector = createSharedComposable(function useConnector() { 56 56 // Persisted connection config 57 - const config = useState<{ token: string, port: number } | null>('connector-config', () => null) 57 + const config = useState<{ token: string; port: number } | null>('connector-config', () => null) 58 58 59 59 // Connection state 60 60 const state = useState<ConnectorState>('connector-state', () => ({ ··· 93 93 if (config.value) { 94 94 reconnect() 95 95 } 96 - } 97 - catch { 96 + } catch { 98 97 localStorage.removeItem(STORAGE_KEY) 99 98 } 100 99 } ··· 122 121 // Fetch full state after connecting 123 122 await refreshState() 124 123 return true 125 - } 126 - else { 124 + } else { 127 125 state.value.error = response.error ?? 'Connection failed' 128 126 return false 129 127 } 130 - } 131 - catch (err) { 128 + } catch (err) { 132 129 const message = err instanceof Error ? err.message : 'Connection failed' 133 - if (message.includes('fetch') || message.includes('network') || message.includes('ECONNREFUSED')) { 130 + if ( 131 + message.includes('fetch') || 132 + message.includes('network') || 133 + message.includes('ECONNREFUSED') 134 + ) { 134 135 state.value.error = 'Could not reach connector. Is it running?' 135 - } 136 - else if (message.includes('401') || message.includes('Unauthorized')) { 136 + } else if (message.includes('401') || message.includes('Unauthorized')) { 137 137 state.value.error = 'Invalid token' 138 - } 139 - else { 138 + } else { 140 139 state.value.error = message 141 140 } 142 141 return false 143 - } 144 - finally { 142 + } finally { 145 143 state.value.connecting = false 146 144 } 147 145 } ··· 180 178 state.value.operations = response.data.operations 181 179 state.value.connected = true 182 180 } 183 - } 184 - catch { 181 + } catch { 185 182 // Connection lost 186 183 state.value.connected = false 187 184 state.value.error = 'Connection lost' ··· 190 187 191 188 async function connectorFetch<T>( 192 189 path: string, 193 - options: { method?: 'GET' | 'POST' | 'DELETE', body?: Record<string, unknown> } = {}, 190 + options: { method?: 'GET' | 'POST' | 'DELETE'; body?: Record<string, unknown> } = {}, 194 191 ): Promise<T | null> { 195 192 if (!config.value) return null 196 193 ··· 204 201 timeout: 30000, 205 202 }) 206 203 return response as T 207 - } 208 - catch (err) { 204 + } catch (err) { 209 205 state.value.error = err instanceof Error ? err.message : 'Request failed' 210 206 return null 211 207 } ··· 292 288 return 0 293 289 } 294 290 295 - async function executeOperations(otp?: string): Promise<{ success: boolean, otpRequired?: boolean }> { 296 - const response = await connectorFetch<ApiResponse<{ results: unknown[], otpRequired?: boolean }>>('/execute', { 291 + async function executeOperations( 292 + otp?: string, 293 + ): Promise<{ success: boolean; otpRequired?: boolean }> { 294 + const response = await connectorFetch< 295 + ApiResponse<{ results: unknown[]; otpRequired?: boolean }> 296 + >('/execute', { 297 297 method: 'POST', 298 298 body: otp ? { otp } : undefined, 299 299 }) ··· 311 311 312 312 // Data fetching functions 313 313 314 - async function listOrgUsers(org: string): Promise<Record<string, 'developer' | 'admin' | 'owner'> | null> { 315 - const response = await connectorFetch<ApiResponse<Record<string, 'developer' | 'admin' | 'owner'>>>(`/org/${encodeURIComponent(org)}/users`) 316 - return response?.success ? response.data ?? null : null 314 + async function listOrgUsers( 315 + org: string, 316 + ): Promise<Record<string, 'developer' | 'admin' | 'owner'> | null> { 317 + const response = await connectorFetch< 318 + ApiResponse<Record<string, 'developer' | 'admin' | 'owner'>> 319 + >(`/org/${encodeURIComponent(org)}/users`) 320 + return response?.success ? (response.data ?? null) : null 317 321 } 318 322 319 323 async function listOrgTeams(org: string): Promise<string[] | null> { 320 - const response = await connectorFetch<ApiResponse<string[]>>(`/org/${encodeURIComponent(org)}/teams`) 321 - return response?.success ? response.data ?? null : null 324 + const response = await connectorFetch<ApiResponse<string[]>>( 325 + `/org/${encodeURIComponent(org)}/teams`, 326 + ) 327 + return response?.success ? (response.data ?? null) : null 322 328 } 323 329 324 330 async function listTeamUsers(scopeTeam: string): Promise<string[] | null> { 325 - const response = await connectorFetch<ApiResponse<string[]>>(`/team/${encodeURIComponent(scopeTeam)}/users`) 326 - return response?.success ? response.data ?? null : null 331 + const response = await connectorFetch<ApiResponse<string[]>>( 332 + `/team/${encodeURIComponent(scopeTeam)}/users`, 333 + ) 334 + return response?.success ? (response.data ?? null) : null 327 335 } 328 336 329 - async function listPackageCollaborators(pkg: string): Promise<Record<string, 'read-only' | 'read-write'> | null> { 330 - const response = await connectorFetch<ApiResponse<Record<string, 'read-only' | 'read-write'>>>(`/package/${encodeURIComponent(pkg)}/collaborators`) 331 - return response?.success ? response.data ?? null : null 337 + async function listPackageCollaborators( 338 + pkg: string, 339 + ): Promise<Record<string, 'read-only' | 'read-write'> | null> { 340 + const response = await connectorFetch<ApiResponse<Record<string, 'read-only' | 'read-write'>>>( 341 + `/package/${encodeURIComponent(pkg)}/collaborators`, 342 + ) 343 + return response?.success ? (response.data ?? null) : null 332 344 } 333 345 334 346 // Computed helpers for operations ··· 340 352 ) 341 353 /** Operations that are done (completed, or failed without needing OTP retry) */ 342 354 const completedOperations = computed(() => 343 - state.value.operations.filter(op => 344 - op.status === 'completed' 345 - || (op.status === 'failed' && !op.result?.requiresOtp), 355 + state.value.operations.filter( 356 + op => op.status === 'completed' || (op.status === 'failed' && !op.result?.requiresOtp), 346 357 ), 347 358 ) 348 359 /** Operations that are still active (pending, approved, running, or failed needing OTP retry) */ 349 360 const activeOperations = computed(() => 350 - state.value.operations.filter(op => 351 - op.status === 'pending' 352 - || op.status === 'approved' 353 - || op.status === 'running' 354 - || (op.status === 'failed' && op.result?.requiresOtp), 361 + state.value.operations.filter( 362 + op => 363 + op.status === 'pending' || 364 + op.status === 'approved' || 365 + op.status === 'running' || 366 + (op.status === 'failed' && op.result?.requiresOtp), 355 367 ), 356 368 ) 357 369 const hasOperations = computed(() => state.value.operations.length > 0)
+12 -4
app/composables/useNpmRegistry.ts
··· 120 120 } 121 121 } 122 122 123 - export function usePackage(name: MaybeRefOrGetter<string>, requestedVersion?: MaybeRefOrGetter<string | null>) { 123 + export function usePackage( 124 + name: MaybeRefOrGetter<string>, 125 + requestedVersion?: MaybeRefOrGetter<string | null>, 126 + ) { 124 127 return useLazyAsyncData( 125 128 () => `package:${toValue(name)}:${toValue(requestedVersion) ?? ''}`, 126 - () => fetchNpmPackage(toValue(name)).then(r => transformPackument(r, toValue(requestedVersion))), 129 + () => 130 + fetchNpmPackage(toValue(name)).then(r => transformPackument(r, toValue(requestedVersion))), 127 131 ) 128 132 } 129 133 ··· 137 141 ) 138 142 } 139 143 140 - const emptySearchResponse = { objects: [], total: 0, time: new Date().toISOString() } satisfies NpmSearchResponse 144 + const emptySearchResponse = { 145 + objects: [], 146 + total: 0, 147 + time: new Date().toISOString(), 148 + } satisfies NpmSearchResponse 141 149 142 150 export function useNpmSearch( 143 151 query: MaybeRefOrGetter<string>, ··· 155 163 if (!q.trim()) { 156 164 return Promise.resolve(emptySearchResponse) 157 165 } 158 - return lastSearch = await searchNpmPackages(q, toValue(options)) 166 + return (lastSearch = await searchNpmPackages(q, toValue(options))) 159 167 }, 160 168 { default: () => lastSearch || emptySearchResponse }, 161 169 )
+8 -4
app/error.vue
··· 9 9 const statusMessage = computed(() => { 10 10 if (props.error.statusMessage) return props.error.statusMessage 11 11 switch (statusCode.value) { 12 - case 404: return 'Page not found' 13 - case 500: return 'Internal server error' 14 - case 503: return 'Service unavailable' 15 - default: return 'Something went wrong' 12 + case 404: 13 + return 'Page not found' 14 + case 500: 15 + return 'Internal server error' 16 + case 503: 17 + return 'Service unavailable' 18 + default: 19 + return 'Something went wrong' 16 20 } 17 21 }) 18 22
+19 -20
app/pages/index.vue
··· 12 12 13 13 useSeoMeta({ 14 14 title: 'npmx - Package Browser for the npm Registry', 15 - description: 'A better browser for the npm registry. Search, browse, and explore packages with a modern interface.', 15 + description: 16 + 'A better browser for the npm registry. Search, browse, and explore packages with a modern interface.', 16 17 }) 17 18 18 19 defineOgImageComponent('Default') ··· 21 22 <template> 22 23 <main class="container"> 23 24 <!-- Hero section with dramatic vertical centering --> 24 - <header class="min-h-[calc(100vh-12rem)] flex flex-col items-center justify-center text-center py-20"> 25 + <header 26 + class="min-h-[calc(100vh-12rem)] flex flex-col items-center justify-center text-center py-20" 27 + > 25 28 <!-- Animated title --> 26 - <h1 class="font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 animate-fade-in animate-fill-both"> 27 - <span class="text-fg-subtle"><span style="letter-spacing: -0.2em;">.</span>/</span>npmx 29 + <h1 30 + class="font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 animate-fade-in animate-fill-both" 31 + > 32 + <span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx 28 33 </h1> 29 34 30 35 <p ··· 39 44 class="w-full max-w-xl animate-slide-up animate-fill-both" 40 45 style="animation-delay: 0.2s" 41 46 > 42 - <form 43 - role="search" 44 - class="relative" 45 - @submit.prevent="handleSearch" 46 - > 47 - <label 48 - for="home-search" 49 - class="sr-only" 50 - >Search npm packages</label> 47 + <form role="search" class="relative" @submit.prevent="handleSearch"> 48 + <label for="home-search" class="sr-only">Search npm packages</label> 51 49 52 50 <!-- Search input with glow effect on focus --> 53 - <div 54 - class="relative group" 55 - :class="{ 'is-focused': isSearchFocused }" 56 - > 51 + <div class="relative group" :class="{ 'is-focused': isSearchFocused }"> 57 52 <!-- Subtle glow effect --> 58 53 <div 59 54 class="absolute -inset-px rounded-lg bg-gradient-to-r from-fg/0 via-fg/5 to-fg/0 opacity-0 transition-opacity duration-500 blur-sm group-[.is-focused]:opacity-100" 60 55 /> 61 56 62 57 <div class="search-box relative flex items-center"> 63 - <span class="absolute left-4 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted z-1"> 58 + <span 59 + class="absolute left-4 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted z-1" 60 + > 64 61 / 65 62 </span> 66 63 ··· 75 72 @input="handleSearch" 76 73 @focus="isSearchFocused = true" 77 74 @blur="isSearchFocused = false" 78 - > 75 + /> 79 76 80 77 <button 81 78 type="submit" ··· 104 101 :to="`/package/${pkg}`" 105 102 class="link-subtle font-mono text-sm inline-flex items-center gap-2 group" 106 103 > 107 - <span class="w-1 h-1 rounded-full bg-fg-subtle group-hover:bg-fg transition-colors duration-200" /> 104 + <span 105 + class="w-1 h-1 rounded-full bg-fg-subtle group-hover:bg-fg transition-colors duration-200" 106 + /> 108 107 {{ pkg }} 109 108 </NuxtLink> 110 109 </li>
+26 -54
app/pages/org/[name].vue
··· 33 33 34 34 defineOgImageComponent('Default', { 35 35 title: () => `@${orgName.value}`, 36 - description: () => scopedPackages.value.length ? `${scopedPackages.value.length} packages` : 'npm organization', 36 + description: () => 37 + scopedPackages.value.length ? `${scopedPackages.value.length} packages` : 'npm organization', 37 38 }) 38 39 </script> 39 40 ··· 47 48 class="w-16 h-16 rounded-lg bg-bg-muted border border-border flex items-center justify-center" 48 49 aria-hidden="true" 49 50 > 50 - <span class="text-2xl text-fg-subtle font-mono">{{ orgName.charAt(0).toUpperCase() }}</span> 51 + <span class="text-2xl text-fg-subtle font-mono">{{ 52 + orgName.charAt(0).toUpperCase() 53 + }}</span> 51 54 </div> 52 55 <div> 53 - <h1 class="font-mono text-2xl sm:text-3xl font-medium"> 54 - @{{ orgName }} 55 - </h1> 56 - <p 57 - v-if="status === 'success'" 58 - class="text-fg-muted text-sm mt-1" 59 - > 56 + <h1 class="font-mono text-2xl sm:text-3xl font-medium">@{{ orgName }}</h1> 57 + <p v-if="status === 'success'" class="text-fg-muted text-sm mt-1"> 60 58 {{ formatNumber(packageCount) }} public package{{ packageCount === 1 ? '' : 's' }} 61 59 </p> 62 60 </div> ··· 78 76 79 77 <!-- Admin panels (when connected) --> 80 78 <ClientOnly> 81 - <section 82 - v-if="isConnected" 83 - class="mb-8" 84 - aria-label="Organization management" 85 - > 79 + <section v-if="isConnected" class="mb-8" aria-label="Organization management"> 86 80 <!-- Tab buttons --> 87 81 <div class="flex items-center gap-1 mb-4"> 88 82 <button 89 83 type="button" 90 84 class="px-4 py-2 font-mono text-sm rounded-t-lg transition-colors duration-200" 91 - :class="activeTab === 'members' 92 - ? 'bg-bg-subtle text-fg border border-border border-b-0' 93 - : 'text-fg-muted hover:text-fg'" 85 + :class=" 86 + activeTab === 'members' 87 + ? 'bg-bg-subtle text-fg border border-border border-b-0' 88 + : 'text-fg-muted hover:text-fg' 89 + " 94 90 @click="activeTab = 'members'" 95 91 > 96 92 Members ··· 98 94 <button 99 95 type="button" 100 96 class="px-4 py-2 font-mono text-sm rounded-t-lg transition-colors duration-200" 101 - :class="activeTab === 'teams' 102 - ? 'bg-bg-subtle text-fg border border-border border-b-0' 103 - : 'text-fg-muted hover:text-fg'" 97 + :class=" 98 + activeTab === 'teams' 99 + ? 'bg-bg-subtle text-fg border border-border border-b-0' 100 + : 'text-fg-muted hover:text-fg' 101 + " 104 102 @click="activeTab = 'teams'" 105 103 > 106 104 Teams ··· 108 106 </div> 109 107 110 108 <!-- Tab content --> 111 - <OrgMembersPanel 112 - v-if="activeTab === 'members'" 113 - :org-name="orgName" 114 - /> 115 - <OrgTeamsPanel 116 - v-else 117 - :org-name="orgName" 118 - /> 109 + <OrgMembersPanel v-if="activeTab === 'members'" :org-name="orgName" /> 110 + <OrgTeamsPanel v-else :org-name="orgName" /> 119 111 </section> 120 112 </ClientOnly> 121 113 122 114 <!-- Loading state --> 123 - <LoadingSpinner 124 - v-if="status === 'pending'" 125 - text="Loading packages..." 126 - /> 115 + <LoadingSpinner v-if="status === 'pending'" text="Loading packages..." /> 127 116 128 117 <!-- Error state --> 129 - <div 130 - v-else-if="status === 'error'" 131 - role="alert" 132 - class="py-12 text-center" 133 - > 118 + <div v-else-if="status === 'error'" role="alert" class="py-12 text-center"> 134 119 <p class="text-fg-muted mb-4"> 135 120 {{ error?.message ?? 'Failed to load organization packages' }} 136 121 </p> 137 - <NuxtLink 138 - to="/" 139 - class="btn" 140 - > 141 - Go back home 142 - </NuxtLink> 122 + <NuxtLink to="/" class="btn"> Go back home </NuxtLink> 143 123 </div> 144 124 145 125 <!-- Empty state --> 146 - <div 147 - v-else-if="packageCount === 0" 148 - class="py-12 text-center" 149 - > 126 + <div v-else-if="packageCount === 0" class="py-12 text-center"> 150 127 <p class="text-fg-muted font-mono"> 151 128 No public packages found for <span class="text-fg">@{{ orgName }}</span> 152 129 </p> ··· 156 133 </div> 157 134 158 135 <!-- Package list --> 159 - <section 160 - v-else-if="scopedPackages.length > 0" 161 - aria-label="Organization packages" 162 - > 163 - <h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4"> 164 - Packages 165 - </h2> 136 + <section v-else-if="scopedPackages.length > 0" aria-label="Organization packages"> 137 + <h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4">Packages</h2> 166 138 167 139 <PackageList :results="scopedPackages" /> 168 140 </section>
+99 -156
app/pages/package/[...name].vue
··· 11 11 // /package/@nuxt/kit → packageName: "@nuxt/kit", requestedVersion: null 12 12 // /package/@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", requestedVersion: "1.0.0" 13 13 const parsedRoute = computed(() => { 14 - const segments = Array.isArray(route.params.name) 15 - ? route.params.name 16 - : [route.params.name ?? ''] 14 + const segments = Array.isArray(route.params.name) ? route.params.name : [route.params.name ?? ''] 17 15 18 16 // Find the /v/ separator for version 19 17 const vIndex = segments.indexOf('v') ··· 46 44 const { data: downloads } = usePackageDownloads(packageName, 'last-week') 47 45 48 46 // Fetch README for specific version if requested, otherwise latest 49 - const { data: readmeData } = useLazyFetch<{ html: string }>(() => { 50 - const base = `/api/registry/readme/${packageName.value}` 51 - const version = requestedVersion.value 52 - return version ? `${base}/v/${version}` : base 53 - }, { default: () => ({ html: '' }) }) 47 + const { data: readmeData } = useLazyFetch<{ html: string }>( 48 + () => { 49 + const base = `/api/registry/readme/${packageName.value}` 50 + const version = requestedVersion.value 51 + return version ? `${base}/v/${version}` : base 52 + }, 53 + { default: () => ({ html: '' }) }, 54 + ) 54 55 55 56 // Get the version to display (requested or latest) 56 57 const displayVersion = computed(() => { ··· 145 146 { id: 'deno', label: 'deno', action: 'add npm:' }, 146 147 ] as const 147 148 148 - type PackageManagerId = typeof packageManagers[number]['id'] 149 + type PackageManagerId = (typeof packageManagers)[number]['id'] 149 150 150 151 // Persist preference in localStorage 151 152 const selectedPM = ref<PackageManagerId>('npm') ··· 157 158 } 158 159 }) 159 160 160 - watch(selectedPM, (value) => { 161 + watch(selectedPM, value => { 161 162 localStorage.setItem('npmx-pm', value) 162 163 }) 163 164 164 - const currentPM = computed(() => packageManagers.find(p => p.id === selectedPM.value) || packageManagers[0]) 165 + const currentPM = computed( 166 + () => packageManagers.find(p => p.id === selectedPM.value) || packageManagers[0], 167 + ) 165 168 const selectedPMLabel = computed(() => currentPM.value.label) 166 169 const selectedPMAction = computed(() => currentPM.value.action) 167 170 ··· 185 188 if (!installCommand.value) return 186 189 await navigator.clipboard.writeText(installCommand.value) 187 190 copied.value = true 188 - setTimeout(() => copied.value = false, 2000) 191 + setTimeout(() => (copied.value = false), 2000) 189 192 } 190 193 191 194 // Expandable description ··· 204 207 } 205 208 } 206 209 207 - watch(() => pkg.value?.description, () => { 208 - descriptionExpanded.value = false 209 - nextTick(checkDescriptionOverflow) 210 - }) 210 + watch( 211 + () => pkg.value?.description, 212 + () => { 213 + descriptionExpanded.value = false 214 + nextTick(checkDescriptionOverflow) 215 + }, 216 + ) 211 217 212 218 onMounted(() => { 213 219 nextTick(checkDescriptionOverflow) 214 220 }) 215 221 216 222 useSeoMeta({ 217 - title: () => pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx', 223 + title: () => (pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx'), 218 224 description: () => pkg.value?.description ?? '', 219 225 }) 220 226 221 227 defineOgImageComponent('Package', { 222 228 name: () => pkg.value?.name ?? 'Package', 223 229 version: () => displayVersion.value?.version ?? '', 224 - downloads: () => downloads.value ? formatNumber(downloads.value.downloads) : '', 230 + downloads: () => (downloads.value ? formatNumber(downloads.value.downloads) : ''), 225 231 license: () => pkg.value?.license ?? '', 226 232 }) 227 233 </script> ··· 230 236 <main class="container py-8 sm:py-12"> 231 237 <PackageSkeleton v-if="status === 'pending'" /> 232 238 233 - <article 234 - v-else-if="status === 'success' && pkg" 235 - class="animate-fade-in" 236 - > 239 + <article v-else-if="status === 'success' && pkg" class="animate-fade-in"> 237 240 <!-- Package header --> 238 241 <header class="mb-8 pb-8 border-b border-border"> 239 242 <div class="mb-4"> ··· 244 247 v-if="orgName" 245 248 :to="`/org/${orgName}`" 246 249 class="text-fg-muted hover:text-fg transition-colors duration-200" 247 - >@{{ orgName }}</NuxtLink><span v-if="orgName">/</span>{{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }} 250 + >@{{ orgName }}</NuxtLink 251 + ><span v-if="orgName">/</span 252 + >{{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }} 248 253 </h1> 249 254 <a 250 255 v-if="displayVersion" 251 - :href="hasProvenance(displayVersion) ? `https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance` : undefined" 256 + :href=" 257 + hasProvenance(displayVersion) 258 + ? `https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance` 259 + : undefined 260 + " 252 261 :target="hasProvenance(displayVersion) ? '_blank' : undefined" 253 262 :rel="hasProvenance(displayVersion) ? 'noopener noreferrer' : undefined" 254 263 class="shrink-0 inline-flex items-center gap-1.5 px-3 py-1 font-mono text-sm bg-bg-muted border border-border rounded-md transition-colors duration-200" 255 - :class="hasProvenance(displayVersion) ? 'hover:border-border-hover cursor-pointer' : 'cursor-default'" 264 + :class=" 265 + hasProvenance(displayVersion) 266 + ? 'hover:border-border-hover cursor-pointer' 267 + : 'cursor-default' 268 + " 256 269 :title="hasProvenance(displayVersion) ? 'Verified provenance' : undefined" 257 270 > 258 271 v{{ displayVersion.version }} 259 272 <span 260 - v-if="requestedVersion && latestVersion && displayVersion.version !== latestVersion.version" 273 + v-if=" 274 + requestedVersion && 275 + latestVersion && 276 + displayVersion.version !== latestVersion.version 277 + " 261 278 class="text-fg-subtle" 262 - >(not latest)</span> 279 + >(not latest)</span 280 + > 263 281 <span 264 282 v-if="hasProvenance(displayVersion)" 265 283 class="i-solar-shield-check-outline w-4 h-4 text-fg-muted" ··· 268 286 </a> 269 287 </div> 270 288 <!-- Fixed height description container to prevent CLS --> 271 - <div 272 - ref="descriptionRef" 273 - class="relative max-w-2xl min-h-[4.5rem]" 274 - > 289 + <div ref="descriptionRef" class="relative max-w-2xl min-h-[4.5rem]"> 275 290 <p 276 291 v-if="pkg.description" 277 292 class="text-fg-muted text-base m-0 overflow-hidden" ··· 279 294 > 280 295 <MarkdownText :text="pkg.description" /> 281 296 </p> 282 - <p 283 - v-else 284 - class="text-fg-subtle text-base m-0 italic" 285 - > 286 - No description provided 287 - </p> 297 + <p v-else class="text-fg-subtle text-base m-0 italic">No description provided</p> 288 298 <!-- Fade overlay with show more button - only when collapsed and overflowing --> 289 299 <div 290 300 v-if="pkg.description && descriptionOverflows && !descriptionExpanded" ··· 303 313 304 314 <!-- Stats grid --> 305 315 <dl class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4 mt-6"> 306 - <div 307 - v-if="pkg.license" 308 - class="space-y-1" 309 - > 310 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 311 - License 312 - </dt> 316 + <div v-if="pkg.license" class="space-y-1"> 317 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">License</dt> 313 318 <dd class="font-mono text-sm text-fg"> 314 319 {{ pkg.license }} 315 320 </dd> 316 321 </div> 317 322 318 - <div 319 - v-if="downloads" 320 - class="space-y-1" 321 - > 322 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 323 - Weekly 324 - </dt> 323 + <div v-if="downloads" class="space-y-1"> 324 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Weekly</dt> 325 325 <dd class="font-mono text-sm text-fg"> 326 326 {{ formatNumber(downloads.downloads) }} 327 327 </dd> 328 328 </div> 329 329 330 - <div 331 - v-if="displayVersion?.dist?.unpackedSize" 332 - class="space-y-1" 333 - > 334 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 335 - Size 336 - </dt> 330 + <div v-if="displayVersion?.dist?.unpackedSize" class="space-y-1"> 331 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Size</dt> 337 332 <dd class="font-mono text-sm text-fg"> 338 333 {{ formatBytes(displayVersion.dist.unpackedSize) }} 339 334 </dd> 340 335 </div> 341 336 342 - <div 343 - v-if="getDependencyCount(displayVersion) > 0" 344 - class="space-y-1" 345 - > 346 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 347 - Deps 348 - </dt> 337 + <div v-if="getDependencyCount(displayVersion) > 0" class="space-y-1"> 338 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Deps</dt> 349 339 <dd class="font-mono text-sm text-fg"> 350 340 {{ getDependencyCount(displayVersion) }} 351 341 </dd> 352 342 </div> 353 343 354 - <div 355 - v-if="pkg.time?.modified" 356 - class="space-y-1 col-span-2" 357 - > 358 - <dt class="text-xs text-fg-subtle uppercase tracking-wider"> 359 - Updated 360 - </dt> 344 + <div v-if="pkg.time?.modified" class="space-y-1 col-span-2"> 345 + <dt class="text-xs text-fg-subtle uppercase tracking-wider">Updated</dt> 361 346 <dd class="font-mono text-sm text-fg"> 362 347 <time :datetime="pkg.time.modified">{{ formatDate(pkg.time.modified) }}</time> 363 348 </dd> ··· 365 350 </dl> 366 351 367 352 <!-- Links --> 368 - <nav 369 - aria-label="Package links" 370 - class="mt-6" 371 - > 353 + <nav aria-label="Package links" class="mt-6"> 372 354 <ul class="flex flex-wrap items-center gap-4 list-none m-0 p-0"> 373 355 <li v-if="repositoryUrl"> 374 356 <a ··· 461 443 </header> 462 444 463 445 <!-- Install command with package manager selector --> 464 - <section 465 - aria-labelledby="install-heading" 466 - class="mb-8" 467 - > 446 + <section aria-labelledby="install-heading" class="mb-8"> 468 447 <div class="flex items-center justify-between mb-3"> 469 - <h2 470 - id="install-heading" 471 - class="text-xs text-fg-subtle uppercase tracking-wider" 472 - > 448 + <h2 id="install-heading" class="text-xs text-fg-subtle uppercase tracking-wider"> 473 449 Install 474 450 </h2> 475 451 <!-- Package manager tabs --> ··· 485 461 role="tab" 486 462 :aria-selected="selectedPM === pm.id" 487 463 class="px-2 py-1 font-mono text-xs rounded transition-all duration-150" 488 - :class="selectedPM === pm.id 489 - ? 'bg-bg-elevated text-fg' 490 - : 'text-fg-subtle hover:text-fg-muted'" 464 + :class=" 465 + selectedPM === pm.id 466 + ? 'bg-bg-elevated text-fg' 467 + : 'text-fg-subtle hover:text-fg-muted' 468 + " 491 469 @click="selectedPM = pm.id" 492 470 > 493 471 {{ pm.label }} ··· 515 493 </div> 516 494 <div class="flex items-center gap-2 px-4 pt-3 pb-4"> 517 495 <span class="text-fg-subtle font-mono text-sm select-none">$</span> 518 - <code class="font-mono text-sm"><ClientOnly><span class="text-fg">{{ selectedPMLabel }}</span> <span class="text-fg-muted">{{ selectedPMAction }}</span><span 519 - v-if="selectedPM !== 'deno'" 520 - class="text-fg-muted" 521 - >&nbsp;{{ pkg.name }}</span><span 522 - v-else 523 - class="text-fg-muted" 524 - >{{ pkg.name }}</span><span 525 - v-if="requestedVersion" 526 - class="text-fg-muted" 527 - >@{{ requestedVersion }}</span><template #fallback><span class="text-fg">npm</span>&nbsp;<span class="text-fg-muted">install {{ pkg.name }}</span></template></ClientOnly></code> 496 + <code class="font-mono text-sm" 497 + ><ClientOnly 498 + ><span class="text-fg">{{ selectedPMLabel }}</span> 499 + <span class="text-fg-muted">{{ selectedPMAction }}</span 500 + ><span v-if="selectedPM !== 'deno'" class="text-fg-muted" 501 + >&nbsp;{{ pkg.name }}</span 502 + ><span v-else class="text-fg-muted">{{ pkg.name }}</span 503 + ><span v-if="requestedVersion" class="text-fg-muted">@{{ requestedVersion }}</span 504 + ><template #fallback 505 + ><span class="text-fg">npm</span>&nbsp;<span class="text-fg-muted" 506 + >install {{ pkg.name }}</span 507 + ></template 508 + ></ClientOnly 509 + ></code 510 + > 528 511 </div> 529 512 </div> 530 513 <button ··· 541 524 <!-- Main content (README) --> 542 525 <div class="lg:col-span-2 order-2 lg:order-1 min-w-0"> 543 526 <section aria-labelledby="readme-heading"> 544 - <h2 545 - id="readme-heading" 546 - class="text-xs text-fg-subtle uppercase tracking-wider mb-4" 547 - > 527 + <h2 id="readme-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-4"> 548 528 Readme 549 529 </h2> 550 530 <!-- eslint-disable vue/no-v-html -- HTML is sanitized server-side --> ··· 553 533 class="readme-content prose prose-invert max-w-none" 554 534 v-html="readmeData.html" 555 535 /> 556 - <p 557 - v-else 558 - class="text-fg-subtle italic" 559 - > 536 + <p v-else class="text-fg-subtle italic"> 560 537 No README available. 561 - <a 562 - v-if="repositoryUrl" 563 - :href="repositoryUrl" 564 - rel="noopener noreferrer" 565 - class="link" 566 - >View on GitHub</a> 538 + <a v-if="repositoryUrl" :href="repositoryUrl" rel="noopener noreferrer" class="link" 539 + >View on GitHub</a 540 + > 567 541 </p> 568 542 </section> 569 543 </div> ··· 571 545 <!-- Sidebar --> 572 546 <aside class="order-1 lg:order-2 space-y-8"> 573 547 <!-- Maintainers (with admin actions when connected) --> 574 - <PackageMaintainers 575 - :package-name="pkg.name" 576 - :maintainers="pkg.maintainers" 577 - /> 548 + <PackageMaintainers :package-name="pkg.name" :maintainers="pkg.maintainers" /> 578 549 579 550 <!-- Team access controls (for scoped packages when connected) --> 580 551 <ClientOnly> ··· 582 553 </ClientOnly> 583 554 584 555 <!-- Keywords --> 585 - <section 586 - v-if="displayVersion?.keywords?.length" 587 - aria-labelledby="keywords-heading" 588 - > 589 - <h2 590 - id="keywords-heading" 591 - class="text-xs text-fg-subtle uppercase tracking-wider mb-3" 592 - > 556 + <section v-if="displayVersion?.keywords?.length" aria-labelledby="keywords-heading"> 557 + <h2 id="keywords-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3"> 593 558 Keywords 594 559 </h2> 595 560 <ul class="flex flex-wrap gap-1.5 list-none m-0 p-0"> 596 - <li 597 - v-for="keyword in displayVersion.keywords.slice(0, 15)" 598 - :key="keyword" 599 - > 600 - <NuxtLink 601 - :to="`/search?q=keywords:${encodeURIComponent(keyword)}`" 602 - class="tag" 603 - > 561 + <li v-for="keyword in displayVersion.keywords.slice(0, 15)" :key="keyword"> 562 + <NuxtLink :to="`/search?q=keywords:${encodeURIComponent(keyword)}`" class="tag"> 604 563 {{ keyword }} 605 564 </NuxtLink> 606 565 </li> ··· 608 567 </section> 609 568 610 569 <section 611 - v-if="displayVersion?.engines && (displayVersion.engines.node || displayVersion.engines.npm)" 570 + v-if=" 571 + displayVersion?.engines && (displayVersion.engines.node || displayVersion.engines.npm) 572 + " 612 573 aria-labelledby="compatibility-heading" 613 574 > 614 575 <h2 ··· 622 583 v-if="displayVersion.engines.node" 623 584 class="flex items-center justify-between py-1" 624 585 > 625 - <dt class="text-fg-muted text-sm"> 626 - node 627 - </dt> 586 + <dt class="text-fg-muted text-sm">node</dt> 628 587 <dd class="font-mono text-sm text-fg"> 629 588 {{ displayVersion.engines.node }} 630 589 </dd> 631 590 </div> 632 - <div 633 - v-if="displayVersion.engines.npm" 634 - class="flex items-center justify-between py-1" 635 - > 636 - <dt class="text-fg-muted text-sm"> 637 - npm 638 - </dt> 591 + <div v-if="displayVersion.engines.npm" class="flex items-center justify-between py-1"> 592 + <dt class="text-fg-muted text-sm">npm</dt> 639 593 <dd class="font-mono text-sm text-fg"> 640 594 {{ displayVersion.engines.npm }} 641 595 </dd> ··· 665 619 </article> 666 620 667 621 <!-- Error state --> 668 - <div 669 - v-else-if="status === 'error'" 670 - role="alert" 671 - class="py-20 text-center" 672 - > 673 - <h1 class="font-mono text-2xl font-medium mb-4"> 674 - Package Not Found 675 - </h1> 622 + <div v-else-if="status === 'error'" role="alert" class="py-20 text-center"> 623 + <h1 class="font-mono text-2xl font-medium mb-4">Package Not Found</h1> 676 624 <p class="text-fg-muted mb-8"> 677 625 {{ error?.message ?? 'The package could not be found.' }} 678 626 </p> 679 - <NuxtLink 680 - to="/" 681 - class="btn" 682 - > 683 - Go back home 684 - </NuxtLink> 627 + <NuxtLink to="/" class="btn"> Go back home </NuxtLink> 685 628 </div> 686 629 </main> 687 630 </template>
+50 -100
app/pages/package/code/[...path].vue
··· 1 1 <script setup lang="ts"> 2 - import type { PackageFileTree, PackageFileTreeResponse, PackageFileContentResponse } from '#shared/types' 2 + import type { 3 + PackageFileTree, 4 + PackageFileTreeResponse, 5 + PackageFileContentResponse, 6 + } from '#shared/types' 3 7 4 8 const route = useRoute('package-code-path') 5 9 const router = useRouter() ··· 10 14 // /package/code/nuxt/v/4.2.0/src/index.ts → packageName: "nuxt", version: "4.2.0", filePath: "src/index.ts" 11 15 // /package/code/@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", version: "1.0.0", filePath: null 12 16 const parsedRoute = computed(() => { 13 - const segments = Array.isArray(route.params.path) 14 - ? route.params.path 15 - : [route.params.path ?? ''] 17 + const segments = Array.isArray(route.params.path) ? route.params.path : [route.params.path ?? ''] 16 18 17 19 // Find the /v/ separator for version 18 20 const vIndex = segments.indexOf('v') ··· 49 51 50 52 // Get dist-tag versions first (latest, next, beta, etc.) 51 53 const taggedVersions = new Set(Object.values(distTags)) 52 - const taggedList = Object.entries(distTags) 53 - .map(([tag, ver]) => ({ version: ver, tag })) 54 + const taggedList = Object.entries(distTags).map(([tag, ver]) => ({ version: ver, tag })) 54 55 55 56 // Get other versions (not in dist-tags), sorted by semver descending 56 57 const otherVersions = allVersions ··· 144 145 }) 145 146 146 147 // Also sync when route changes (e.g., navigating to a different file) 147 - watch(() => route.hash, (hash) => { 148 - currentHash.value = hash 149 - }) 148 + watch( 149 + () => route.hash, 150 + hash => { 151 + currentHash.value = hash 152 + }, 153 + ) 150 154 151 155 // Line number handling from hash 152 156 const selectedLines = computed(() => { ··· 184 188 // Build breadcrumb path segments 185 189 const breadcrumbs = computed(() => { 186 190 const parts = filePath.value?.split('/').filter(Boolean) ?? [] 187 - const result: { name: string, path: string }[] = [] 191 + const result: { name: string; path: string }[] = [] 188 192 189 193 for (let i = 0; i < parts.length; i++) { 190 194 const part = parts[i] ··· 228 232 const start = Math.min(selectedLines.value.start, lineNum) 229 233 const end = Math.max(selectedLines.value.end, lineNum) 230 234 newHash = `#L${start}-L${end}` 231 - } 232 - else { 235 + } else { 233 236 // Single click: select line 234 237 newHash = `#L${lineNum}` 235 238 } ··· 274 277 :to="`/package/${packageName}${version ? `/v/${version}` : ''}`" 275 278 class="font-mono text-lg font-medium hover:text-fg transition-colors" 276 279 > 277 - <span 278 - v-if="orgName" 279 - class="text-fg-muted" 280 - >@{{ orgName }}/</span>{{ orgName ? packageName.replace(`@${orgName}/`, '') : packageName }} 280 + <span v-if="orgName" class="text-fg-muted">@{{ orgName }}/</span 281 + >{{ orgName ? packageName.replace(`@${orgName}/`, '') : packageName }} 281 282 </NuxtLink> 282 283 <!-- Version selector --> 283 - <div 284 - v-if="version && availableVersions.length > 0" 285 - class="relative" 286 - > 284 + <div v-if="version && availableVersions.length > 0" class="relative"> 287 285 <select 288 286 :value="version" 289 287 class="appearance-none pl-2 pr-6 py-0.5 font-mono text-sm bg-bg-muted border border-border rounded cursor-pointer hover:border-border-hover transition-colors" 290 288 @change="switchVersion(($event.target as HTMLSelectElement).value)" 291 289 > 292 - <option 293 - v-for="v in availableVersions" 294 - :key="v.version" 295 - :value="v.version" 296 - > 290 + <option v-for="v in availableVersions" :key="v.version" :value="v.version"> 297 291 v{{ v.version }}{{ v.tag ? ` (${v.tag})` : '' }} 298 292 </option> 299 293 </select> 300 - <span class="i-carbon-chevron-down w-3 h-3 absolute right-1.5 top-1/2 -translate-y-1/2 pointer-events-none text-fg-muted" /> 294 + <span 295 + class="i-carbon-chevron-down w-3 h-3 absolute right-1.5 top-1/2 -translate-y-1/2 pointer-events-none text-fg-muted" 296 + /> 301 297 </div> 302 298 <span 303 299 v-else-if="version" ··· 321 317 > 322 318 root 323 319 </NuxtLink> 324 - <template 325 - v-for="(crumb, i) in breadcrumbs" 326 - :key="crumb.path" 327 - > 320 + <template v-for="(crumb, i) in breadcrumbs" :key="crumb.path"> 328 321 <span class="text-fg-subtle">/</span> 329 322 <NuxtLink 330 323 v-if="i < breadcrumbs.length - 1" ··· 333 326 > 334 327 {{ crumb.name }} 335 328 </NuxtLink> 336 - <span 337 - v-else 338 - class="text-fg" 339 - >{{ crumb.name }}</span> 329 + <span v-else class="text-fg">{{ crumb.name }}</span> 340 330 </template> 341 331 </nav> 342 332 </div> 343 333 </header> 344 334 345 335 <!-- Error: no version --> 346 - <div 347 - v-if="!version" 348 - class="container py-20 text-center" 349 - > 350 - <p class="text-fg-muted mb-4"> 351 - Version is required to browse code 352 - </p> 353 - <NuxtLink 354 - :to="`/package/${packageName}`" 355 - class="btn" 356 - > 357 - Go to package 358 - </NuxtLink> 336 + <div v-if="!version" class="container py-20 text-center"> 337 + <p class="text-fg-muted mb-4">Version is required to browse code</p> 338 + <NuxtLink :to="`/package/${packageName}`" class="btn"> Go to package </NuxtLink> 359 339 </div> 360 340 361 341 <!-- Loading state --> 362 - <div 363 - v-else-if="treeStatus === 'pending'" 364 - class="container py-20 text-center" 365 - > 342 + <div v-else-if="treeStatus === 'pending'" class="container py-20 text-center"> 366 343 <div class="i-svg-spinners-ring-resize w-8 h-8 mx-auto text-fg-muted" /> 367 - <p class="mt-4 text-fg-muted"> 368 - Loading file tree... 369 - </p> 344 + <p class="mt-4 text-fg-muted">Loading file tree...</p> 370 345 </div> 371 346 372 347 <!-- Error state --> 373 - <div 374 - v-else-if="treeStatus === 'error'" 375 - class="container py-20 text-center" 376 - role="alert" 377 - > 378 - <p class="text-fg-muted mb-4"> 379 - Failed to load files for this package version 380 - </p> 381 - <NuxtLink 382 - :to="`/package/${packageName}${version ? `/v/${version}` : ''}`" 383 - class="btn" 384 - > 348 + <div v-else-if="treeStatus === 'error'" class="container py-20 text-center" role="alert"> 349 + <p class="text-fg-muted mb-4">Failed to load files for this package version</p> 350 + <NuxtLink :to="`/package/${packageName}${version ? `/v/${version}` : ''}`" class="btn"> 385 351 Back to package 386 352 </NuxtLink> 387 353 </div> 388 354 389 355 <!-- Main content: file tree + file viewer --> 390 - <div 391 - v-else-if="fileTree" 392 - class="flex-1 flex min-h-0" 393 - > 356 + <div v-else-if="fileTree" class="flex-1 flex min-h-0"> 394 357 <!-- File tree sidebar --> 395 - <aside class="w-64 lg:w-72 border-r border-border overflow-y-auto shrink-0 hidden md:block bg-bg-subtle"> 358 + <aside 359 + class="w-64 lg:w-72 border-r border-border overflow-y-auto shrink-0 hidden md:block bg-bg-subtle" 360 + > 396 361 <CodeFileTree 397 362 :tree="fileTree.tree" 398 363 :current-path="filePath ?? ''" ··· 404 369 <div class="flex-1 overflow-auto min-w-0"> 405 370 <!-- File viewer --> 406 371 <template v-if="isViewingFile && fileContent"> 407 - <div class="sticky top-0 bg-bg border-b border-border px-4 py-2 flex items-center justify-between"> 372 + <div 373 + class="sticky top-0 bg-bg border-b border-border px-4 py-2 flex items-center justify-between" 374 + > 408 375 <div class="flex items-center gap-3 text-sm"> 409 376 <span class="text-fg-muted">{{ fileContent.lines }} lines</span> 410 - <span 411 - v-if="currentNode?.size" 412 - class="text-fg-subtle" 413 - >{{ formatBytes(currentNode.size) }}</span> 377 + <span v-if="currentNode?.size" class="text-fg-subtle">{{ 378 + formatBytes(currentNode.size) 379 + }}</span> 414 380 </div> 415 381 <div class="flex items-center gap-2"> 416 382 <button ··· 440 406 </template> 441 407 442 408 <!-- File too large warning --> 443 - <div 444 - v-else-if="isViewingFile && isFileTooLarge" 445 - class="py-20 text-center" 446 - > 409 + <div v-else-if="isViewingFile && isFileTooLarge" class="py-20 text-center"> 447 410 <div class="i-carbon-document w-12 h-12 mx-auto text-fg-subtle mb-4" /> 448 - <p class="text-fg-muted mb-2"> 449 - File too large to preview 450 - </p> 411 + <p class="text-fg-muted mb-2">File too large to preview</p> 451 412 <p class="text-fg-subtle text-sm mb-4"> 452 - {{ formatBytes(currentNode?.size ?? 0) }} exceeds the 500KB limit for syntax highlighting 413 + {{ formatBytes(currentNode?.size ?? 0) }} exceeds the 500KB limit for syntax 414 + highlighting 453 415 </p> 454 416 <a 455 417 :href="`https://cdn.jsdelivr.net/npm/${packageName}@${version}/${filePath}`" ··· 471 433 > 472 434 <!-- Fake line numbers column --> 473 435 <div class="shrink-0 bg-bg-subtle border-r border-border w-14 py-0"> 474 - <div 475 - v-for="n in 20" 476 - :key="n" 477 - class="px-3 h-6 flex items-center justify-end" 478 - > 436 + <div v-for="n in 20" :key="n" class="px-3 h-6 flex items-center justify-end"> 479 437 <span class="skeleton w-4 h-3 rounded-sm" /> 480 438 </div> 481 439 </div> ··· 505 463 </div> 506 464 507 465 <!-- Error loading file --> 508 - <div 509 - v-else-if="filePath && fileStatus === 'error'" 510 - class="py-20 text-center" 511 - role="alert" 512 - > 466 + <div v-else-if="filePath && fileStatus === 'error'" class="py-20 text-center" role="alert"> 513 467 <div class="i-carbon-warning-alt w-8 h-8 mx-auto text-fg-subtle mb-4" /> 514 - <p class="text-fg-muted mb-2"> 515 - Failed to load file 516 - </p> 517 - <p class="text-fg-subtle text-sm mb-4"> 518 - The file may be too large or unavailable 519 - </p> 468 + <p class="text-fg-muted mb-2">Failed to load file</p> 469 + <p class="text-fg-subtle text-sm mb-4">The file may be too large or unavailable</p> 520 470 <a 521 471 :href="`https://cdn.jsdelivr.net/npm/${packageName}@${version}/${filePath}`" 522 472 target="_blank"
+50 -79
app/pages/search.vue
··· 14 14 }, 250) 15 15 16 16 // Watch input and debounce URL updates 17 - watch(inputValue, (value) => { 17 + watch(inputValue, value => { 18 18 updateUrlQuery(value) 19 19 }) 20 20 ··· 22 22 const query = computed(() => (route.query.q as string) ?? '') 23 23 24 24 // Sync input with URL when navigating (e.g., back button) 25 - watch(() => route.query.q, (urlQuery) => { 26 - const value = (urlQuery as string) ?? '' 27 - if (inputValue.value !== value) { 28 - inputValue.value = value 29 - } 30 - }) 25 + watch( 26 + () => route.query.q, 27 + urlQuery => { 28 + const value = (urlQuery as string) ?? '' 29 + if (inputValue.value !== value) { 30 + inputValue.value = value 31 + } 32 + }, 33 + ) 31 34 32 35 // For glow effect 33 36 const isSearchFocused = ref(false) ··· 89 92 const resultsListRef = ref<HTMLOListElement>() 90 93 91 94 // Scroll to restored position once results are loaded 92 - watch([results, status, () => needsScrollRestore.value], ([newResults, newStatus, shouldScroll]) => { 93 - if (shouldScroll && newStatus === 'success' && newResults && newResults.objects.length > 0) { 94 - needsScrollRestore.value = false 95 - // Scroll to the first item of the target page 96 - nextTick(() => { 97 - const targetItemIndex = (initialPage.value - 1) * pageSize 98 - const listItems = resultsListRef.value?.children 99 - if (listItems && listItems[targetItemIndex]) { 100 - listItems[targetItemIndex].scrollIntoView({ 101 - behavior: 'instant', 102 - block: 'start', 103 - }) 104 - } 105 - }) 106 - } 107 - }) 95 + watch( 96 + [results, status, () => needsScrollRestore.value], 97 + ([newResults, newStatus, shouldScroll]) => { 98 + if (shouldScroll && newStatus === 'success' && newResults && newResults.objects.length > 0) { 99 + needsScrollRestore.value = false 100 + // Scroll to the first item of the target page 101 + nextTick(() => { 102 + const targetItemIndex = (initialPage.value - 1) * pageSize 103 + const listItems = resultsListRef.value?.children 104 + if (listItems && listItems[targetItemIndex]) { 105 + listItems[targetItemIndex].scrollIntoView({ 106 + behavior: 'instant', 107 + block: 'start', 108 + }) 109 + } 110 + }) 111 + } 112 + }, 113 + ) 108 114 109 115 // Determine if we should show previous results while loading 110 116 // (when new query is a continuation of the old one) ··· 167 173 if (!loadMoreTrigger.value) return 168 174 169 175 const observer = new IntersectionObserver( 170 - (entries) => { 176 + entries => { 171 177 if (entries[0]?.isIntersecting && hasMore.value && status.value !== 'pending') { 172 178 loadMore() 173 179 } ··· 187 193 }) 188 194 189 195 useSeoMeta({ 190 - title: () => query.value ? `Search: ${query.value} - npmx` : 'Search Packages - npmx', 196 + title: () => (query.value ? `Search: ${query.value} - npmx` : 'Search Packages - npmx'), 191 197 }) 192 198 193 199 defineOgImageComponent('Default', { 194 200 title: 'npmx', 195 - description: () => query.value ? `Search results for "${query.value}"` : 'Search npm packages', 201 + description: () => (query.value ? `Search results for "${query.value}"` : 'Search npm packages'), 196 202 }) 197 203 </script> 198 204 199 205 <template> 200 206 <main class="container py-8 sm:py-12 overflow-x-hidden"> 201 207 <header class="mb-8"> 202 - <h1 class="font-mono text-2xl sm:text-3xl font-medium mb-6"> 203 - search 204 - </h1> 208 + <h1 class="font-mono text-2xl sm:text-3xl font-medium mb-6">search</h1> 205 209 206 210 <search> 207 - <form 208 - role="search" 209 - class="relative" 210 - @submit.prevent 211 - > 212 - <label 213 - for="search-input" 214 - class="sr-only" 215 - >Search npm packages</label> 211 + <form role="search" class="relative" @submit.prevent> 212 + <label for="search-input" class="sr-only">Search npm packages</label> 216 213 217 - <div 218 - class="relative group" 219 - :class="{ 'is-focused': isSearchFocused }" 220 - > 214 + <div class="relative group" :class="{ 'is-focused': isSearchFocused }"> 221 215 <!-- Subtle glow effect --> 222 216 <div 223 217 class="absolute -inset-px rounded-lg bg-gradient-to-r from-fg/0 via-fg/5 to-fg/0 opacity-0 transition-opacity duration-500 blur-sm group-[.is-focused]:opacity-100" 224 218 /> 225 219 226 220 <div class="search-box relative flex items-center"> 227 - <span class="absolute left-4 text-fg-subtle font-mono text-base pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted"> 221 + <span 222 + class="absolute left-4 text-fg-subtle font-mono text-base pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted" 223 + > 228 224 / 229 225 </span> 230 226 <input ··· 238 234 class="w-full max-w-full bg-bg-subtle border border-border rounded-lg pl-8 pr-4 py-3 font-mono text-base text-fg placeholder:text-fg-subtle transition-all duration-300 focus:(border-border-hover outline-none) appearance-none" 239 235 @focus="isSearchFocused = true" 240 236 @blur="isSearchFocused = false" 241 - > 237 + /> 242 238 <!-- Hidden submit button for accessibility (form must have submit button per WCAG) --> 243 - <button 244 - type="submit" 245 - class="sr-only" 246 - > 247 - Search 248 - </button> 239 + <button type="submit" class="sr-only">Search</button> 249 240 </div> 250 241 </div> 251 242 </form> 252 243 </search> 253 244 </header> 254 245 255 - <section 256 - v-if="query" 257 - aria-label="Search results" 258 - > 246 + <section v-if="query" aria-label="Search results"> 259 247 <!-- Initial loading (only after user interaction, not during view transition) --> 260 - <LoadingSpinner 261 - v-if="showSearching" 262 - text="Searching..." 263 - /> 248 + <LoadingSpinner v-if="showSearching" text="Searching..." /> 264 249 265 250 <div v-else-if="visibleResults"> 266 251 <p ··· 269 254 class="text-fg-muted text-sm mb-6 font-mono" 270 255 > 271 256 Found <span class="text-fg">{{ formatNumber(visibleResults.total) }}</span> packages 272 - <span 273 - v-if="status === 'pending'" 274 - class="text-fg-subtle" 275 - >(updating...)</span> 257 + <span v-if="status === 'pending'" class="text-fg-subtle">(updating...)</span> 276 258 </p> 277 259 278 260 <p ··· 280 262 role="status" 281 263 class="text-fg-muted py-12 text-center font-mono" 282 264 > 283 - No packages found for "<span class="text-fg">{{ query }}</span>" 265 + No packages found for "<span class="text-fg">{{ query }}</span 266 + >" 284 267 </p> 285 268 286 269 <ol ··· 294 277 class="animate-fade-in animate-fill-both" 295 278 :style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }" 296 279 > 297 - <PackageCard 298 - :result="result" 299 - heading-level="h2" 300 - show-publisher 301 - /> 280 + <PackageCard :result="result" heading-level="h2" show-publisher /> 302 281 </li> 303 282 </ol> 304 283 305 284 <!-- Infinite scroll trigger --> 306 - <div 307 - ref="loadMoreTrigger" 308 - class="py-8 flex items-center justify-center" 309 - > 285 + <div ref="loadMoreTrigger" class="py-8 flex items-center justify-center"> 310 286 <div 311 287 v-if="isLoadingMore || (status === 'pending' && loadedPages > 1)" 312 288 class="flex items-center gap-3 text-fg-muted font-mono text-sm" ··· 324 300 </div> 325 301 </section> 326 302 327 - <section 328 - v-else 329 - class="py-20 text-center" 330 - > 331 - <p class="text-fg-subtle font-mono text-sm"> 332 - Start typing to search packages 333 - </p> 303 + <section v-else class="py-20 text-center"> 304 + <p class="text-fg-subtle font-mono text-sm">Start typing to search packages</p> 334 305 </section> 335 306 </main> 336 307 </template>
+13 -38
app/pages/~[username].vue
··· 23 23 24 24 defineOgImageComponent('Default', { 25 25 title: () => `@${username.value}`, 26 - description: () => results.value ? `${results.value.total} packages` : 'npm user profile', 26 + description: () => (results.value ? `${results.value.total} packages` : 'npm user profile'), 27 27 }) 28 28 </script> 29 29 ··· 37 37 class="w-16 h-16 rounded-full bg-bg-muted border border-border flex items-center justify-center" 38 38 aria-hidden="true" 39 39 > 40 - <span class="text-2xl text-fg-subtle font-mono">{{ username.charAt(0).toUpperCase() }}</span> 40 + <span class="text-2xl text-fg-subtle font-mono">{{ 41 + username.charAt(0).toUpperCase() 42 + }}</span> 41 43 </div> 42 44 <div> 43 - <h1 class="font-mono text-2xl sm:text-3xl font-medium"> 44 - @{{ username }} 45 - </h1> 46 - <p 47 - v-if="results?.total" 48 - class="text-fg-muted text-sm mt-1" 49 - > 45 + <h1 class="font-mono text-2xl sm:text-3xl font-medium">@{{ username }}</h1> 46 + <p v-if="results?.total" class="text-fg-muted text-sm mt-1"> 50 47 {{ formatNumber(results.total) }} public package{{ results.total === 1 ? '' : 's' }} 51 48 </p> 52 49 </div> ··· 67 64 </header> 68 65 69 66 <!-- Loading state --> 70 - <LoadingSpinner 71 - v-if="status === 'pending'" 72 - text="Loading packages..." 73 - /> 67 + <LoadingSpinner v-if="status === 'pending'" text="Loading packages..." /> 74 68 75 69 <!-- Error state --> 76 - <div 77 - v-else-if="status === 'error'" 78 - role="alert" 79 - class="py-12 text-center" 80 - > 70 + <div v-else-if="status === 'error'" role="alert" class="py-12 text-center"> 81 71 <p class="text-fg-muted mb-4"> 82 72 {{ error?.message ?? 'Failed to load user packages' }} 83 73 </p> 84 - <NuxtLink 85 - to="/" 86 - class="btn" 87 - > 88 - Go back home 89 - </NuxtLink> 74 + <NuxtLink to="/" class="btn"> Go back home </NuxtLink> 90 75 </div> 91 76 92 77 <!-- Empty state --> 93 - <div 94 - v-else-if="results && results.total === 0" 95 - class="py-12 text-center" 96 - > 78 + <div v-else-if="results && results.total === 0" class="py-12 text-center"> 97 79 <p class="text-fg-muted font-mono"> 98 80 No public packages found for <span class="text-fg">@{{ username }}</span> 99 81 </p> 100 - <p class="text-fg-subtle text-sm mt-2"> 101 - This user may not exist or has no public packages. 102 - </p> 82 + <p class="text-fg-subtle text-sm mt-2">This user may not exist or has no public packages.</p> 103 83 </div> 104 84 105 85 <!-- Package list --> 106 - <section 107 - v-else-if="results && sortedPackages.length > 0" 108 - aria-label="User packages" 109 - > 110 - <h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4"> 111 - Packages 112 - </h2> 86 + <section v-else-if="results && sortedPackages.length > 0" aria-label="User packages"> 87 + <h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4">Packages</h2> 113 88 114 89 <PackageList :results="sortedPackages" /> 115 90 </section>
+1 -1
app/plugins/vercel-analytics.client.ts
··· 7 7 } 8 8 9 9 window.va = function (...params: unknown[]) { 10 - (window.vaq = window.vaq || []).push(params) 10 + ;(window.vaq = window.vaq || []).push(params) 11 11 } 12 12 13 13 useScript({
+2 -2
cli/src/cli.ts
··· 10 10 const DEFAULT_PORT = 31415 11 11 12 12 async function runNpmLogin(): Promise<boolean> { 13 - return new Promise((resolve) => { 13 + return new Promise(resolve => { 14 14 const child = spawn('npm', ['login'], { 15 15 stdio: 'inherit', 16 16 shell: true, 17 17 }) 18 18 19 - child.on('close', (code) => { 19 + child.on('close', code => { 20 20 resolve(code === 0) 21 21 }) 22 22
+10 -29
cli/src/npm-client.ts
··· 53 53 54 54 export async function execNpm( 55 55 args: string[], 56 - options: { otp?: string, silent?: boolean } = {}, 56 + options: { otp?: string; silent?: boolean } = {}, 57 57 ): Promise<NpmExecResult> { 58 58 const cmd = ['npm', ...args] 59 59 ··· 63 63 64 64 // Log the command being run (hide OTP value for security) 65 65 if (!options.silent) { 66 - const displayCmd = options.otp 67 - ? ['npm', ...args, '--otp', '******'].join(' ') 68 - : cmd.join(' ') 66 + const displayCmd = options.otp ? ['npm', ...args, '--otp', '******'].join(' ') : cmd.join(' ') 69 67 logCommand(displayCmd) 70 68 } 71 69 ··· 84 82 stderr: filterNpmWarnings(stderr), 85 83 exitCode: 0, 86 84 } 87 - } 88 - catch (error) { 89 - const err = error as { stdout?: string, stderr?: string, code?: number } 85 + } catch (error) { 86 + const err = error as { stdout?: string; stderr?: string; code?: number } 90 87 const stderr = err.stderr?.trim() ?? String(error) 91 88 const requiresOtp = detectOtpRequired(stderr) 92 89 const authFailure = detectAuthFailure(stderr) ··· 94 91 if (!options.silent) { 95 92 if (requiresOtp) { 96 93 logError('OTP required') 97 - } 98 - else if (authFailure) { 94 + } else if (authFailure) { 99 95 logError('Authentication required - please run "npm login" and restart the connector') 100 - } 101 - else { 96 + } else { 102 97 logError(filterNpmWarnings(stderr).split('\n')[0] || 'Command failed') 103 98 } 104 99 } ··· 142 137 return execNpm(['org', 'rm', org, user], { otp }) 143 138 } 144 139 145 - export async function teamCreate( 146 - scopeTeam: string, 147 - otp?: string, 148 - ): Promise<NpmExecResult> { 140 + export async function teamCreate(scopeTeam: string, otp?: string): Promise<NpmExecResult> { 149 141 return execNpm(['team', 'create', scopeTeam], { otp }) 150 142 } 151 143 152 - export async function teamDestroy( 153 - scopeTeam: string, 154 - otp?: string, 155 - ): Promise<NpmExecResult> { 144 + export async function teamDestroy(scopeTeam: string, otp?: string): Promise<NpmExecResult> { 156 145 return execNpm(['team', 'destroy', scopeTeam], { otp }) 157 146 } 158 147 ··· 189 178 return execNpm(['access', 'revoke', scopeTeam, pkg], { otp }) 190 179 } 191 180 192 - export async function ownerAdd( 193 - user: string, 194 - pkg: string, 195 - otp?: string, 196 - ): Promise<NpmExecResult> { 181 + export async function ownerAdd(user: string, pkg: string, otp?: string): Promise<NpmExecResult> { 197 182 return execNpm(['owner', 'add', user, pkg], { otp }) 198 183 } 199 184 200 - export async function ownerRemove( 201 - user: string, 202 - pkg: string, 203 - otp?: string, 204 - ): Promise<NpmExecResult> { 185 + export async function ownerRemove(user: string, pkg: string, otp?: string): Promise<NpmExecResult> { 205 186 return execNpm(['owner', 'rm', user, pkg], { otp }) 206 187 } 207 188
+40 -41
cli/src/server.ts
··· 2 2 import { readFileSync } from 'node:fs' 3 3 import { fileURLToPath } from 'node:url' 4 4 import { dirname, join } from 'node:path' 5 - import { createApp, createRouter, eventHandler, readBody, getQuery, createError, getHeader, setResponseHeaders, getRouterParam } from 'h3' 6 - import type { 7 - ConnectorState, 8 - PendingOperation, 9 - OperationType, 10 - ApiResponse, 11 - } from './types.ts' 5 + import { 6 + createApp, 7 + createRouter, 8 + eventHandler, 9 + readBody, 10 + getQuery, 11 + createError, 12 + getHeader, 13 + setResponseHeaders, 14 + getRouterParam, 15 + } from 'h3' 16 + import type { ConnectorState, PendingOperation, OperationType, ApiResponse } from './types.ts' 12 17 import { 13 18 getNpmUser, 14 19 orgAddUser, ··· 35 40 const pkgPath = join(__dirname, '..', 'package.json') 36 41 const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) 37 42 return pkg.version || '0.0.0' 38 - } 39 - catch { 43 + } catch { 40 44 // Fallback if package.json can't be read (e.g., in bundled builds) 41 45 return '0.0.0' 42 46 } ··· 90 94 91 95 router.post( 92 96 '/connect', 93 - eventHandler(async (event) => { 97 + eventHandler(async event => { 94 98 const body = await readBody(event) 95 99 if (body?.token !== expectedToken) { 96 100 throw createError({ statusCode: 401, message: 'Invalid token' }) ··· 112 116 113 117 router.get( 114 118 '/state', 115 - eventHandler((event) => { 119 + eventHandler(event => { 116 120 const auth = getHeader(event, 'authorization') 117 121 if (!validateToken(auth)) { 118 122 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 130 134 131 135 router.post( 132 136 '/operations', 133 - eventHandler(async (event) => { 137 + eventHandler(async event => { 134 138 const auth = getHeader(event, 'authorization') 135 139 if (!validateToken(auth)) { 136 140 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 165 169 166 170 router.post( 167 171 '/operations/batch', 168 - eventHandler(async (event) => { 172 + eventHandler(async event => { 169 173 const auth = getHeader(event, 'authorization') 170 174 if (!validateToken(auth)) { 171 175 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 203 207 204 208 router.post( 205 209 '/approve', 206 - eventHandler(async (event) => { 210 + eventHandler(async event => { 207 211 const auth = getHeader(event, 'authorization') 208 212 if (!validateToken(auth)) { 209 213 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 232 236 233 237 router.post( 234 238 '/approve-all', 235 - eventHandler(async (event) => { 239 + eventHandler(async event => { 236 240 const auth = getHeader(event, 'authorization') 237 241 if (!validateToken(auth)) { 238 242 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 252 256 253 257 router.post( 254 258 '/retry', 255 - eventHandler(async (event) => { 259 + eventHandler(async event => { 256 260 const auth = getHeader(event, 'authorization') 257 261 if (!validateToken(auth)) { 258 262 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 283 287 284 288 router.post( 285 289 '/execute', 286 - eventHandler(async (event) => { 290 + eventHandler(async event => { 287 291 const auth = getHeader(event, 'authorization') 288 292 if (!validateToken(auth)) { 289 293 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 294 298 const otp = body?.otp as string | undefined 295 299 296 300 const approvedOps = state.operations.filter(op => op.status === 'approved') 297 - const results: Array<{ id: string, result: NpmExecResult }> = [] 301 + const results: Array<{ id: string; result: NpmExecResult }> = [] 298 302 let otpRequired = false 299 303 const completedIds = new Set<string>() 300 304 const failedIds = new Set<string>() ··· 303 307 // Each wave contains operations whose dependencies are satisfied 304 308 while (true) { 305 309 // Find operations ready to run (no pending dependencies) 306 - const readyOps = approvedOps.filter((op) => { 310 + const readyOps = approvedOps.filter(op => { 307 311 // Already processed 308 312 if (completedIds.has(op.id) || failedIds.has(op.id)) return false 309 313 // No dependency - ready ··· 329 333 if (otpRequired && !otp) break 330 334 331 335 // Execute ready operations in parallel 332 - const runningOps = readyOps.map(async (op) => { 336 + const runningOps = readyOps.map(async op => { 333 337 op.status = 'running' 334 338 const result = await executeOperation(op, otp) 335 339 op.result = result ··· 337 341 338 342 if (result.exitCode === 0) { 339 343 completedIds.add(op.id) 340 - } 341 - else { 344 + } else { 342 345 failedIds.add(op.id) 343 346 } 344 347 ··· 369 372 370 373 router.delete( 371 374 '/operations', 372 - eventHandler(async (event) => { 375 + eventHandler(async event => { 373 376 const auth = getHeader(event, 'authorization') 374 377 if (!validateToken(auth)) { 375 378 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 396 399 397 400 router.delete( 398 401 '/operations/all', 399 - eventHandler(async (event) => { 402 + eventHandler(async event => { 400 403 const auth = getHeader(event, 'authorization') 401 404 if (!validateToken(auth)) { 402 405 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 416 419 417 420 router.get( 418 421 '/org/:org/users', 419 - eventHandler(async (event) => { 422 + eventHandler(async event => { 420 423 const auth = getHeader(event, 'authorization') 421 424 if (!validateToken(auth)) { 422 425 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 441 444 success: true, 442 445 data: users, 443 446 } as ApiResponse 444 - } 445 - catch { 447 + } catch { 446 448 return { 447 449 success: false, 448 450 error: 'Failed to parse org users', ··· 453 455 454 456 router.get( 455 457 '/org/:org/teams', 456 - eventHandler(async (event) => { 458 + eventHandler(async event => { 457 459 const auth = getHeader(event, 'authorization') 458 460 if (!validateToken(auth)) { 459 461 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 478 480 success: true, 479 481 data: teams, 480 482 } as ApiResponse 481 - } 482 - catch { 483 + } catch { 483 484 return { 484 485 success: false, 485 486 error: 'Failed to parse teams', ··· 490 491 491 492 router.get( 492 493 '/team/:scopeTeam/users', 493 - eventHandler(async (event) => { 494 + eventHandler(async event => { 494 495 const auth = getHeader(event, 'authorization') 495 496 if (!validateToken(auth)) { 496 497 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 518 519 success: true, 519 520 data: users, 520 521 } as ApiResponse 521 - } 522 - catch { 522 + } catch { 523 523 return { 524 524 success: false, 525 525 error: 'Failed to parse team users', ··· 530 530 531 531 router.get( 532 532 '/package/:pkg/collaborators', 533 - eventHandler(async (event) => { 533 + eventHandler(async event => { 534 534 const auth = getHeader(event, 'authorization') 535 535 if (!validateToken(auth)) { 536 536 throw createError({ statusCode: 401, message: 'Unauthorized' }) ··· 553 553 } 554 554 555 555 try { 556 - const collaborators = JSON.parse(result.stdout) as Record<string, 'read-only' | 'read-write'> 556 + const collaborators = JSON.parse(result.stdout) as Record< 557 + string, 558 + 'read-only' | 'read-write' 559 + > 557 560 return { 558 561 success: true, 559 562 data: collaborators, 560 563 } as ApiResponse 561 - } 562 - catch { 564 + } catch { 563 565 return { 564 566 success: false, 565 567 error: 'Failed to parse collaborators', ··· 572 574 return app 573 575 } 574 576 575 - async function executeOperation( 576 - op: PendingOperation, 577 - otp?: string, 578 - ): Promise<NpmExecResult> { 577 + async function executeOperation(op: PendingOperation, otp?: string): Promise<NpmExecResult> { 579 578 const { type, params } = op 580 579 581 580 switch (type) {
+19 -19
cli/src/types.ts
··· 9 9 npmUser: string | null 10 10 } 11 11 12 - export type OperationType 13 - = | 'org:add-user' 14 - | 'org:rm-user' 15 - | 'org:set-role' 16 - | 'team:create' 17 - | 'team:destroy' 18 - | 'team:add-user' 19 - | 'team:rm-user' 20 - | 'access:grant' 21 - | 'access:revoke' 22 - | 'owner:add' 23 - | 'owner:rm' 12 + export type OperationType = 13 + | 'org:add-user' 14 + | 'org:rm-user' 15 + | 'org:set-role' 16 + | 'team:create' 17 + | 'team:destroy' 18 + | 'team:add-user' 19 + | 'team:rm-user' 20 + | 'access:grant' 21 + | 'access:revoke' 22 + | 'owner:add' 23 + | 'owner:rm' 24 24 25 - export type OperationStatus 26 - = | 'pending' 27 - | 'approved' 28 - | 'running' 29 - | 'completed' 30 - | 'failed' 31 - | 'cancelled' 25 + export type OperationStatus = 26 + | 'pending' 27 + | 'approved' 28 + | 'running' 29 + | 'completed' 30 + | 'failed' 31 + | 'cancelled' 32 32 33 33 export interface OperationResult { 34 34 stdout: string
+2 -7
cli/tsconfig.json
··· 10 10 "declaration": true, 11 11 "declarationMap": true 12 12 }, 13 - "include": [ 14 - "src/**/*.ts" 15 - ], 16 - "exclude": [ 17 - "node_modules", 18 - "dist" 19 - ] 13 + "include": ["src/**/*.ts"], 14 + "exclude": ["node_modules", "dist"] 20 15 }
-6
eslint.config.mjs
··· 1 - // @ts-check 2 - import withNuxt from './.nuxt/eslint.config.mjs' 3 - 4 - export default withNuxt( 5 - // Your custom configs here 6 - )
+1 -1
modules/cache.ts
··· 10 10 return 11 11 } 12 12 13 - nuxt.hook('nitro:config', (nitroConfig) => { 13 + nuxt.hook('nitro:config', nitroConfig => { 14 14 nitroConfig.storage = nitroConfig.storage || {} 15 15 nitroConfig.storage.cache = { 16 16 driver: 'vercel-runtime-cache',
-7
nuxt.config.ts
··· 8 8 } 9 9 }, 10 10 '@unocss/nuxt', 11 - '@nuxt/eslint', 12 11 '@nuxtjs/html-validator', 13 12 '@nuxt/scripts', 14 13 '@nuxt/fonts', ··· 66 65 '@shikijs/engine-javascript', 67 66 '@shikijs/core', 68 67 ], 69 - }, 70 - }, 71 - 72 - eslint: { 73 - config: { 74 - stylistic: true, 75 68 }, 76 69 }, 77 70
+9 -5
package.json
··· 11 11 "scripts": { 12 12 "build": "nuxt build", 13 13 "dev": "nuxt dev", 14 - "lint": "eslint .", 14 + "lint": "oxlint && oxfmt --check .", 15 + "lint:fix": "oxlint --fix && oxfmt .", 15 16 "generate": "nuxt generate", 16 17 "preview": "nuxt preview", 17 18 "postinstall": "nuxt prepare && simple-git-hooks", ··· 26 27 }, 27 28 "dependencies": { 28 29 "@iconify-json/vscode-icons": "^1.2.40", 29 - "@nuxt/eslint": "^1.12.1", 30 30 "@nuxt/fonts": "^0.13.0", 31 31 "@nuxt/scripts": "^0.13.2", 32 32 "@nuxtjs/html-validator": "^2.1.0", ··· 59 59 "@vitest/browser-playwright": "^4.0.18", 60 60 "@vitest/coverage-v8": "^4.0.18", 61 61 "@vue/test-utils": "2.4.6", 62 - "eslint": "9.39.2", 63 62 "happy-dom": "20.3.5", 64 63 "lint-staged": "16.2.7", 65 64 "marked": "^17.0.1", 65 + "oxfmt": "^0.26.0", 66 + "oxlint": "^1.41.0", 66 67 "playwright-core": "^1.57.0", 67 68 "simple-git-hooks": "2.13.1", 68 69 "std-env": "^3.10.0", ··· 87 88 "pre-commit": "npx lint-staged" 88 89 }, 89 90 "lint-staged": { 90 - "*.{js,ts,mjs,cjs,json,.*rc}": [ 91 - "npx eslint --fix" 91 + "*.{js,ts,mjs,cjs,vue}": [ 92 + "oxlint --fix" 93 + ], 94 + "*.{js,ts,mjs,cjs,vue,json,md,html,css}": [ 95 + "oxfmt" 92 96 ] 93 97 }, 94 98 "packageManager": "pnpm@10.28.1"
+284 -1105
pnpm-lock.yaml
··· 16 16 '@iconify-json/vscode-icons': 17 17 specifier: ^1.2.40 18 18 version: 1.2.40 19 - '@nuxt/eslint': 20 - specifier: ^1.12.1 21 - version: 1.12.1(@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) 22 19 '@nuxt/fonts': 23 20 specifier: ^0.13.0 24 21 version: 0.13.0(db0@0.3.4)(ioredis@5.9.2)(magicast@0.5.1)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) ··· 39 36 version: 14.1.0(vue@3.5.27(typescript@5.9.3)) 40 37 '@vueuse/nuxt': 41 38 specifier: 14.1.0 42 - version: 14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)) 39 + version: 14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)) 43 40 nuxt: 44 41 specifier: ^4.3.0 45 - version: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 42 + version: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 46 43 nuxt-og-image: 47 44 specifier: ^5.1.13 48 45 version: 5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(db0@0.3.4)(ioredis@5.9.2))(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)) ··· 110 107 '@vue/test-utils': 111 108 specifier: 2.4.6 112 109 version: 2.4.6 113 - eslint: 114 - specifier: 9.39.2 115 - version: 9.39.2(jiti@2.6.1) 116 110 happy-dom: 117 111 specifier: 20.3.5 118 112 version: 20.3.5 ··· 122 116 marked: 123 117 specifier: ^17.0.1 124 118 version: 17.0.1 119 + oxfmt: 120 + specifier: ^0.26.0 121 + version: 0.26.0 122 + oxlint: 123 + specifier: ^1.41.0 124 + version: 1.41.0 125 125 playwright-core: 126 126 specifier: ^1.57.0 127 127 version: 1.57.0 ··· 194 194 engines: {node: '>=10'} 195 195 peerDependencies: 196 196 ajv: '>=8' 197 - 198 - '@apidevtools/json-schema-ref-parser@14.2.1': 199 - resolution: {integrity: sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==} 200 - engines: {node: '>= 20'} 201 - peerDependencies: 202 - '@types/json-schema': ^7.0.15 203 197 204 198 '@asamuzakjp/css-color@4.1.1': 205 199 resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} ··· 762 756 resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} 763 757 engines: {node: '>=18'} 764 758 765 - '@clack/core@0.5.0': 766 - resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} 767 - 768 759 '@clack/core@1.0.0-alpha.7': 769 760 resolution: {integrity: sha512-3vdh6Ar09D14rVxJZIm3VQJkU+ZOKKT5I5cC0cOVazy70CNyYYjiwRj9unwalhESndgxx6bGc/m6Hhs4EKF5XQ==} 770 - 771 - '@clack/prompts@0.11.0': 772 - resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} 773 761 774 762 '@clack/prompts@1.0.0-alpha.9': 775 763 resolution: {integrity: sha512-sKs0UjiHFWvry4SiRfBi5Qnj0C/6AYx8aKkFPZQSuUZXgAram25ZDmhQmP7vj1aFyLpfHWtLQjWvOvcat0TOLg==} ··· 824 812 825 813 '@emnapi/wasi-threads@1.1.0': 826 814 resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} 827 - 828 - '@es-joy/jsdoccomment@0.78.0': 829 - resolution: {integrity: sha512-rQkU5u8hNAq2NVRzHnIUUvR6arbO0b6AOlvpTNS48CkiKSn/xtNfOzBK23JE4SiW89DgvU7GtxLVgV4Vn2HBAw==} 830 - engines: {node: '>=20.11.0'} 831 - 832 - '@es-joy/resolve.exports@1.2.0': 833 - resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} 834 - engines: {node: '>=10'} 835 815 836 816 '@esbuild/aix-ppc64@0.25.12': 837 817 resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} ··· 1155 1135 resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} 1156 1136 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 1157 1137 1158 - '@eslint/compat@1.4.1': 1159 - resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} 1160 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1161 - peerDependencies: 1162 - eslint: ^8.40 || 9 1163 - peerDependenciesMeta: 1164 - eslint: 1165 - optional: true 1166 - 1167 1138 '@eslint/config-array@0.21.1': 1168 1139 resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} 1169 1140 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} ··· 1171 1142 '@eslint/config-helpers@0.4.2': 1172 1143 resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} 1173 1144 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1174 - 1175 - '@eslint/config-inspector@1.4.2': 1176 - resolution: {integrity: sha512-Ay8QcvV/Tq6YDeoltwZDQsQTrcS5flPkOp4ylk1WdV7L2UGotINwjatjbAIEqBTmP3G0g3Ah8dnuHC8DsnKPYQ==} 1177 - hasBin: true 1178 - peerDependencies: 1179 - eslint: ^8.50.0 || ^9.0.0 1180 1145 1181 1146 '@eslint/core@0.17.0': 1182 1147 resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} ··· 1533 1498 engines: {node: '>=18'} 1534 1499 hasBin: true 1535 1500 1536 - '@napi-rs/wasm-runtime@0.2.12': 1537 - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} 1538 - 1539 1501 '@napi-rs/wasm-runtime@1.1.1': 1540 1502 resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} 1541 1503 ··· 1582 1544 '@vitejs/devtools': 1583 1545 optional: true 1584 1546 1585 - '@nuxt/eslint-config@1.12.1': 1586 - resolution: {integrity: sha512-fsKKtIIvVwQ5OGE30lJEhzwXxXj40ol7vR6h3eTH8sSBVZLOdmPn2BHrhoOjHTDXpLPw1AZ/8GcQfJZ2o3gcHQ==} 1587 - peerDependencies: 1588 - eslint: ^9.0.0 1589 - eslint-plugin-format: '*' 1590 - peerDependenciesMeta: 1591 - eslint-plugin-format: 1592 - optional: true 1593 - 1594 - '@nuxt/eslint-plugin@1.12.1': 1595 - resolution: {integrity: sha512-9EBWZTgJC2oclDIL53YG6paEoaTU2SDWVPybEQ0Pe2Bm/5YSbHd//6EGLvdGwAgN+xJQmEsPunUpd4Y+NX2OCQ==} 1596 - peerDependencies: 1597 - eslint: ^9.0.0 1598 - 1599 - '@nuxt/eslint@1.12.1': 1600 - resolution: {integrity: sha512-weXMt09C2XsWo7mpkVciApTXXaNUYQ1IbvrURNtnhpJcvcb2WkQutIOc/+pIhTsmb2O3T1t23HL76+Ll+7bpFQ==} 1601 - peerDependencies: 1602 - eslint: ^9.0.0 1603 - eslint-webpack-plugin: ^4.1.0 1604 - vite-plugin-eslint2: ^5.0.0 1605 - peerDependenciesMeta: 1606 - eslint-webpack-plugin: 1607 - optional: true 1608 - vite-plugin-eslint2: 1609 - optional: true 1610 - 1611 1547 '@nuxt/fonts@0.13.0': 1612 1548 resolution: {integrity: sha512-70t42uWyk1ugILdgdP7VG2B0Q+52hKrR8IODSABU6qYSMsd+PtT2pW4Fj+hVEhQVOW+cZe0dvSeKi0p6v5gCIw==} 1613 1549 ··· 1695 1631 optional: true 1696 1632 1697 1633 '@nuxt/test-utils@https://pkg.pr.new/@nuxt/test-utils@1499a48': 1698 - resolution: {integrity: sha512-T7DnXM09HsMWu43qcXpeNW1KsH230b/OYlvpRD49RKocyv/6XjATiQw1zWeNnOVw4fPULIeMlrrL5TX3c1jGWg==, tarball: https://pkg.pr.new/@nuxt/test-utils@1499a48} 1634 + resolution: {tarball: https://pkg.pr.new/@nuxt/test-utils@1499a48} 1699 1635 version: 3.23.0 1700 1636 engines: {node: ^20.11.1 || ^22.0.0 || >=24.0.0} 1701 1637 peerDependencies: ··· 2112 2048 cpu: [x64] 2113 2049 os: [win32] 2114 2050 2051 + '@oxfmt/darwin-arm64@0.26.0': 2052 + resolution: {integrity: sha512-AAGc+8CffkiWeVgtWf4dPfQwHEE5c/j/8NWH7VGVxxJRCZFdmWcqCXprvL2H6qZFewvDLrFbuSPRCqYCpYGaTQ==} 2053 + cpu: [arm64] 2054 + os: [darwin] 2055 + 2056 + '@oxfmt/darwin-x64@0.26.0': 2057 + resolution: {integrity: sha512-xFx5ijCTjw577wJvFlZEMmKDnp3HSCcbYdCsLRmC5i3TZZiDe9DEYh3P46uqhzj8BkEw1Vm1ZCWdl48aEYAzvQ==} 2058 + cpu: [x64] 2059 + os: [darwin] 2060 + 2061 + '@oxfmt/linux-arm64-gnu@0.26.0': 2062 + resolution: {integrity: sha512-GubkQeQT5d3B/Jx/IiR7NMkSmXrCZcVI0BPh1i7mpFi8HgD1hQ/LbhiBKAMsMqs5bbugdQOgBEl8bOhe8JhW1g==} 2063 + cpu: [arm64] 2064 + os: [linux] 2065 + 2066 + '@oxfmt/linux-arm64-musl@0.26.0': 2067 + resolution: {integrity: sha512-OEypUwK69bFPj+aa3/LYCnlIUPgoOLu//WNcriwpnWNmt47808Ht7RJSg+MNK8a7pSZHpXJ5/E6CRK/OTwFdaQ==} 2068 + cpu: [arm64] 2069 + os: [linux] 2070 + 2071 + '@oxfmt/linux-x64-gnu@0.26.0': 2072 + resolution: {integrity: sha512-xO6iEW2bC6ZHyOTPmPWrg/nM6xgzyRPaS84rATy6F8d79wz69LdRdJ3l/PXlkqhi7XoxhvX4ExysA0Nf10ZZEQ==} 2073 + cpu: [x64] 2074 + os: [linux] 2075 + 2076 + '@oxfmt/linux-x64-musl@0.26.0': 2077 + resolution: {integrity: sha512-Z3KuZFC+MIuAyFCXBHY71kCsdRq1ulbsbzTe71v+hrEv7zVBn6yzql+/AZcgfIaKzWO9OXNuz5WWLWDmVALwow==} 2078 + cpu: [x64] 2079 + os: [linux] 2080 + 2081 + '@oxfmt/win32-arm64@0.26.0': 2082 + resolution: {integrity: sha512-3zRbqwVWK1mDhRhTknlQFpRFL9GhEB5GfU6U7wawnuEwpvi39q91kJ+SRJvJnhyPCARkjZBd1V8XnweN5IFd1g==} 2083 + cpu: [arm64] 2084 + os: [win32] 2085 + 2086 + '@oxfmt/win32-x64@0.26.0': 2087 + resolution: {integrity: sha512-m8TfIljU22i9UEIkD+slGPifTFeaCwIUfxszN3E6ABWP1KQbtwSw9Ak0TdoikibvukF/dtbeyG3WW63jv9DnEg==} 2088 + cpu: [x64] 2089 + os: [win32] 2090 + 2091 + '@oxlint/darwin-arm64@1.41.0': 2092 + resolution: {integrity: sha512-K0Bs0cNW11oWdSrKmrollKF44HMM2HKr4QidZQHMlhJcSX8pozxv0V5FLdqB4sddzCY0J9Wuuw+oRAfR8sdRwA==} 2093 + cpu: [arm64] 2094 + os: [darwin] 2095 + 2096 + '@oxlint/darwin-x64@1.41.0': 2097 + resolution: {integrity: sha512-1LCCXCe9nN8LbrJ1QOGari2HqnxrZrveYKysWDIg8gFsQglIg00XF/8lRbA0kWHMdLgt4X0wfNYhhFz+c3XXLQ==} 2098 + cpu: [x64] 2099 + os: [darwin] 2100 + 2101 + '@oxlint/linux-arm64-gnu@1.41.0': 2102 + resolution: {integrity: sha512-Fow7H84Bs8XxuaK1yfSEWBC8HI7rfEQB9eR2A0J61un1WgCas7jNrt1HbT6+p6KmUH2bhR+r/RDu/6JFAvvj4g==} 2103 + cpu: [arm64] 2104 + os: [linux] 2105 + 2106 + '@oxlint/linux-arm64-musl@1.41.0': 2107 + resolution: {integrity: sha512-WoRRDNwgP5W3rjRh42Zdx8ferYnqpKoYCv2QQLenmdrLjRGYwAd52uywfkcS45mKEWHeY1RPwPkYCSROXiGb2w==} 2108 + cpu: [arm64] 2109 + os: [linux] 2110 + 2111 + '@oxlint/linux-x64-gnu@1.41.0': 2112 + resolution: {integrity: sha512-75k3CKj3fOc/a/2aSgO81s3HsTZOFROthPJ+UI2Oatic1LhvH6eKjKfx3jDDyVpzeDS2qekPlc/y3N33iZz5Og==} 2113 + cpu: [x64] 2114 + os: [linux] 2115 + 2116 + '@oxlint/linux-x64-musl@1.41.0': 2117 + resolution: {integrity: sha512-8r82eBwGPoAPn67ZvdxTlX/Z3gVb+ZtN6nbkyFzwwHWAh8yGutX+VBcVkyrePSl6XgBP4QAaddPnHmkvJjqY0g==} 2118 + cpu: [x64] 2119 + os: [linux] 2120 + 2121 + '@oxlint/win32-arm64@1.41.0': 2122 + resolution: {integrity: sha512-aK+DAcckQsNCOXKruatyYuY/ROjNiRejQB1PeJtkZwM21+8rV9ODYbvKNvt0pW+YCws7svftBSFMCpl3ke2unw==} 2123 + cpu: [arm64] 2124 + os: [win32] 2125 + 2126 + '@oxlint/win32-x64@1.41.0': 2127 + resolution: {integrity: sha512-dVBXkZ6MGLd3owV7jvuqJsZwiF3qw7kEkDVsYVpS/O96eEvlHcxVbaPjJjrTBgikXqyC22vg3dxBU7MW0utGfw==} 2128 + cpu: [x64] 2129 + os: [win32] 2130 + 2115 2131 '@parcel/watcher-android-arm64@2.5.4': 2116 2132 resolution: {integrity: sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==} 2117 2133 engines: {node: '>= 10.0.0'} ··· 2668 2684 peerDependencies: 2669 2685 ajv: ^6.12.3 || ^7.0.0 || ^8.0.0 2670 2686 2671 - '@sindresorhus/base62@1.0.0': 2672 - resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} 2673 - engines: {node: '>=18'} 2674 - 2675 2687 '@sindresorhus/is@7.2.0': 2676 2688 resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} 2677 2689 engines: {node: '>=18'} ··· 2685 2697 2686 2698 '@standard-schema/spec@1.1.0': 2687 2699 resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} 2688 - 2689 - '@stylistic/eslint-plugin@5.7.0': 2690 - resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==} 2691 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2692 - peerDependencies: 2693 - eslint: '>=9.0.0' 2694 2700 2695 2701 '@surma/rollup-plugin-off-main-thread@2.2.3': 2696 2702 resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} ··· 2762 2768 '@types/ws@8.18.1': 2763 2769 resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} 2764 2770 2765 - '@typescript-eslint/eslint-plugin@8.53.1': 2766 - resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==} 2767 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2768 - peerDependencies: 2769 - '@typescript-eslint/parser': ^8.53.1 2770 - eslint: ^8.57.0 || ^9.0.0 2771 - typescript: '>=4.8.4 <6.0.0' 2772 - 2773 - '@typescript-eslint/parser@8.53.1': 2774 - resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==} 2775 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2776 - peerDependencies: 2777 - eslint: ^8.57.0 || ^9.0.0 2778 - typescript: '>=4.8.4 <6.0.0' 2779 - 2780 - '@typescript-eslint/project-service@8.53.1': 2781 - resolution: {integrity: sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==} 2782 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2783 - peerDependencies: 2784 - typescript: '>=4.8.4 <6.0.0' 2785 - 2786 - '@typescript-eslint/scope-manager@8.53.1': 2787 - resolution: {integrity: sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==} 2788 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2789 - 2790 - '@typescript-eslint/tsconfig-utils@8.53.1': 2791 - resolution: {integrity: sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==} 2792 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2793 - peerDependencies: 2794 - typescript: '>=4.8.4 <6.0.0' 2795 - 2796 - '@typescript-eslint/type-utils@8.53.1': 2797 - resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==} 2798 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2799 - peerDependencies: 2800 - eslint: ^8.57.0 || ^9.0.0 2801 - typescript: '>=4.8.4 <6.0.0' 2802 - 2803 - '@typescript-eslint/types@8.53.1': 2804 - resolution: {integrity: sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==} 2805 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2806 - 2807 - '@typescript-eslint/typescript-estree@8.53.1': 2808 - resolution: {integrity: sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==} 2809 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2810 - peerDependencies: 2811 - typescript: '>=4.8.4 <6.0.0' 2812 - 2813 - '@typescript-eslint/utils@8.53.1': 2814 - resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==} 2815 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2816 - peerDependencies: 2817 - eslint: ^8.57.0 || ^9.0.0 2818 - typescript: '>=4.8.4 <6.0.0' 2819 - 2820 - '@typescript-eslint/visitor-keys@8.53.1': 2821 - resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==} 2822 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2823 - 2824 2771 '@ungap/structured-clone@1.3.0': 2825 2772 resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} 2826 2773 ··· 2923 2870 peerDependencies: 2924 2871 webpack: ^4 || ^5 2925 2872 2926 - '@unrs/resolver-binding-android-arm-eabi@1.11.1': 2927 - resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} 2928 - cpu: [arm] 2929 - os: [android] 2930 - 2931 - '@unrs/resolver-binding-android-arm64@1.11.1': 2932 - resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} 2933 - cpu: [arm64] 2934 - os: [android] 2935 - 2936 - '@unrs/resolver-binding-darwin-arm64@1.11.1': 2937 - resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} 2938 - cpu: [arm64] 2939 - os: [darwin] 2940 - 2941 - '@unrs/resolver-binding-darwin-x64@1.11.1': 2942 - resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} 2943 - cpu: [x64] 2944 - os: [darwin] 2945 - 2946 - '@unrs/resolver-binding-freebsd-x64@1.11.1': 2947 - resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} 2948 - cpu: [x64] 2949 - os: [freebsd] 2950 - 2951 - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': 2952 - resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} 2953 - cpu: [arm] 2954 - os: [linux] 2955 - 2956 - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': 2957 - resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} 2958 - cpu: [arm] 2959 - os: [linux] 2960 - 2961 - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': 2962 - resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} 2963 - cpu: [arm64] 2964 - os: [linux] 2965 - 2966 - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': 2967 - resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} 2968 - cpu: [arm64] 2969 - os: [linux] 2970 - 2971 - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': 2972 - resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} 2973 - cpu: [ppc64] 2974 - os: [linux] 2975 - 2976 - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': 2977 - resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} 2978 - cpu: [riscv64] 2979 - os: [linux] 2980 - 2981 - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': 2982 - resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} 2983 - cpu: [riscv64] 2984 - os: [linux] 2985 - 2986 - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': 2987 - resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} 2988 - cpu: [s390x] 2989 - os: [linux] 2990 - 2991 - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': 2992 - resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} 2993 - cpu: [x64] 2994 - os: [linux] 2995 - 2996 - '@unrs/resolver-binding-linux-x64-musl@1.11.1': 2997 - resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} 2998 - cpu: [x64] 2999 - os: [linux] 3000 - 3001 - '@unrs/resolver-binding-wasm32-wasi@1.11.1': 3002 - resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} 3003 - engines: {node: '>=14.0.0'} 3004 - cpu: [wasm32] 3005 - 3006 - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': 3007 - resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} 3008 - cpu: [arm64] 3009 - os: [win32] 3010 - 3011 - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': 3012 - resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} 3013 - cpu: [ia32] 3014 - os: [win32] 3015 - 3016 - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': 3017 - resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} 3018 - cpu: [x64] 3019 - os: [win32] 3020 - 3021 2873 '@vercel/nft@1.3.0': 3022 2874 resolution: {integrity: sha512-i4EYGkCsIjzu4vorDUbqglZc5eFtQI2syHb++9ZUDm6TU4edVywGpVnYDein35x9sevONOn9/UabfQXuNXtuzQ==} 3023 2875 engines: {node: '>=20'} ··· 3347 3199 resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} 3348 3200 engines: {node: '>= 14'} 3349 3201 3350 - are-docs-informative@0.0.2: 3351 - resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} 3352 - engines: {node: '>=14'} 3353 - 3354 3202 argparse@2.0.1: 3355 3203 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 3356 3204 ··· 3487 3335 buffer@6.0.3: 3488 3336 resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 3489 3337 3490 - builtin-modules@5.0.0: 3491 - resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} 3492 - engines: {node: '>=18.20'} 3493 - 3494 3338 bundle-name@4.1.0: 3495 3339 resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} 3496 3340 engines: {node: '>=18'} 3497 3341 3498 - bundle-require@5.1.0: 3499 - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 3500 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 3501 - peerDependencies: 3502 - esbuild: '>=0.18' 3503 - 3504 3342 c12@3.3.3: 3505 3343 resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} 3506 3344 peerDependencies: ··· 3549 3387 resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 3550 3388 engines: {node: '>=10'} 3551 3389 3552 - change-case@5.4.4: 3553 - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} 3554 - 3555 3390 character-entities-html4@2.1.0: 3556 3391 resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} 3557 3392 ··· 3579 3414 resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} 3580 3415 engines: {node: '>=6.0'} 3581 3416 3582 - ci-info@4.3.1: 3583 - resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} 3584 - engines: {node: '>=8'} 3585 - 3586 3417 citty@0.1.6: 3587 3418 resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} 3588 3419 3589 3420 citty@0.2.0: 3590 3421 resolution: {integrity: sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==} 3591 3422 3592 - clean-regexp@1.0.0: 3593 - resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} 3594 - engines: {node: '>=4'} 3595 - 3596 3423 cli-cursor@5.0.0: 3597 3424 resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} 3598 3425 engines: {node: '>=18'} ··· 3654 3481 3655 3482 commander@2.20.3: 3656 3483 resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 3657 - 3658 - comment-parser@1.4.1: 3659 - resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} 3660 - engines: {node: '>= 12.0.0'} 3661 - 3662 - comment-parser@1.4.4: 3663 - resolution: {integrity: sha512-0D6qSQ5IkeRrGJFHRClzaMOenMeT0gErz3zIw3AprKMqhRN6LNU2jQOdkPG/FZ+8bCgXE1VidrgSzlBBDZRr8A==} 3664 - engines: {node: '>= 12.0.0'} 3665 3484 3666 3485 common-tags@1.8.2: 3667 3486 resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} ··· 4077 3896 escape-html@1.0.3: 4078 3897 resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 4079 3898 4080 - escape-string-regexp@1.0.5: 4081 - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 4082 - engines: {node: '>=0.8.0'} 4083 - 4084 3899 escape-string-regexp@4.0.0: 4085 3900 resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 4086 3901 engines: {node: '>=10'} ··· 4089 3904 resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} 4090 3905 engines: {node: '>=12'} 4091 3906 4092 - eslint-config-flat-gitignore@2.1.0: 4093 - resolution: {integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==} 4094 - peerDependencies: 4095 - eslint: ^9.5.0 4096 - 4097 - eslint-flat-config-utils@2.1.4: 4098 - resolution: {integrity: sha512-bEnmU5gqzS+4O+id9vrbP43vByjF+8KOs+QuuV4OlqAuXmnRW2zfI/Rza1fQvdihQ5h4DUo0NqFAiViD4mSrzQ==} 4099 - 4100 - eslint-import-context@0.1.9: 4101 - resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} 4102 - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 4103 - peerDependencies: 4104 - unrs-resolver: ^1.0.0 4105 - peerDependenciesMeta: 4106 - unrs-resolver: 4107 - optional: true 4108 - 4109 - eslint-merge-processors@2.0.0: 4110 - resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==} 4111 - peerDependencies: 4112 - eslint: '*' 4113 - 4114 - eslint-plugin-import-lite@0.3.1: 4115 - resolution: {integrity: sha512-9+EByHZatvWFn/lRsUja5pwah0U5lhOA6SXqTI/iIzoIJHMgmsHUHEaTlLzKU/ukyCRwKEU5E92aUURPgVWq0A==} 4116 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 4117 - peerDependencies: 4118 - eslint: '>=9.0.0' 4119 - typescript: '>=4.5' 4120 - peerDependenciesMeta: 4121 - typescript: 4122 - optional: true 4123 - 4124 - eslint-plugin-import-x@4.16.1: 4125 - resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==} 4126 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 4127 - peerDependencies: 4128 - '@typescript-eslint/utils': ^8.0.0 4129 - eslint: ^8.57.0 || ^9.0.0 4130 - eslint-import-resolver-node: '*' 4131 - peerDependenciesMeta: 4132 - '@typescript-eslint/utils': 4133 - optional: true 4134 - eslint-import-resolver-node: 4135 - optional: true 4136 - 4137 - eslint-plugin-jsdoc@61.7.1: 4138 - resolution: {integrity: sha512-36DpldF95MlTX//n3/naULFVt8d1cV4jmSkx7ZKrE9ikkKHAgMLesuWp1SmwpVwAs5ndIM6abKd6PeOYZUgdWg==} 4139 - engines: {node: '>=20.11.0'} 4140 - peerDependencies: 4141 - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 4142 - 4143 - eslint-plugin-regexp@2.10.0: 4144 - resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==} 4145 - engines: {node: ^18 || >=20} 4146 - peerDependencies: 4147 - eslint: '>=8.44.0' 4148 - 4149 - eslint-plugin-unicorn@62.0.0: 4150 - resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} 4151 - engines: {node: ^20.10.0 || >=21.0.0} 4152 - peerDependencies: 4153 - eslint: '>=9.38.0' 4154 - 4155 - eslint-plugin-vue@10.7.0: 4156 - resolution: {integrity: sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==} 4157 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 4158 - peerDependencies: 4159 - '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 4160 - '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 4161 - eslint: ^8.57.0 || ^9.0.0 4162 - vue-eslint-parser: ^10.0.0 4163 - peerDependenciesMeta: 4164 - '@stylistic/eslint-plugin': 4165 - optional: true 4166 - '@typescript-eslint/parser': 4167 - optional: true 4168 - 4169 - eslint-processor-vue-blocks@2.0.0: 4170 - resolution: {integrity: sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==} 4171 - peerDependencies: 4172 - '@vue/compiler-sfc': ^3.3.0 4173 - eslint: '>=9.0.0' 4174 - 4175 3907 eslint-scope@5.1.1: 4176 3908 resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} 4177 3909 engines: {node: '>=8.0.0'} ··· 4180 3912 resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} 4181 3913 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 4182 3914 4183 - eslint-typegen@2.3.0: 4184 - resolution: {integrity: sha512-azYgAvhlz1AyTpeLfVSKcrNJInuIsRrcUrOcHmEl8T9oMKesePVUPrF8gRgE6azV8CAlFzxJDTyaXAAbA/BYiA==} 4185 - peerDependencies: 4186 - eslint: ^9.0.0 4187 - 4188 3915 eslint-visitor-keys@3.4.3: 4189 3916 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 4190 3917 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} ··· 4193 3920 resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} 4194 3921 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 4195 3922 4196 - eslint-visitor-keys@5.0.0: 4197 - resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} 4198 - engines: {node: ^20.19.0 || ^22.13.0 || >=24} 4199 - 4200 3923 eslint@9.39.2: 4201 3924 resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} 4202 3925 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} ··· 4211 3934 resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} 4212 3935 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 4213 3936 4214 - espree@11.1.0: 4215 - resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==} 4216 - engines: {node: ^20.19.0 || ^22.13.0 || >=24} 4217 - 4218 3937 esquery@1.7.0: 4219 3938 resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} 4220 3939 engines: {node: '>=0.10'} ··· 4336 4055 resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 4337 4056 engines: {node: '>=8'} 4338 4057 4339 - find-up-simple@1.0.1: 4340 - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} 4341 - engines: {node: '>=18'} 4342 - 4343 4058 find-up@5.0.0: 4344 4059 resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 4345 4060 engines: {node: '>=10'} 4346 - 4347 - find-up@8.0.0: 4348 - resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==} 4349 - engines: {node: '>=20'} 4350 4061 4351 4062 fix-dts-default-cjs-exports@1.0.1: 4352 4063 resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} ··· 4516 4227 resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 4517 4228 engines: {node: '>=18'} 4518 4229 4519 - globals@16.5.0: 4520 - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} 4521 - engines: {node: '>=18'} 4522 - 4523 4230 globalthis@1.0.4: 4524 4231 resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} 4525 4232 engines: {node: '>= 0.4'} ··· 4605 4312 html-encoding-sniffer@6.0.0: 4606 4313 resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} 4607 4314 engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} 4608 - 4609 - html-entities@2.6.0: 4610 - resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} 4611 4315 4612 4316 html-escaper@2.0.2: 4613 4317 resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} ··· 4704 4408 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 4705 4409 engines: {node: '>=0.8.19'} 4706 4410 4707 - indent-string@5.0.0: 4708 - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} 4709 - engines: {node: '>=12'} 4710 - 4711 4411 inherits@2.0.4: 4712 4412 resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 4713 4413 ··· 4748 4448 resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} 4749 4449 engines: {node: '>= 0.4'} 4750 4450 4751 - is-builtin-module@5.0.0: 4752 - resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} 4753 - engines: {node: '>=18.20'} 4754 - 4755 4451 is-callable@1.2.7: 4756 4452 resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 4757 4453 engines: {node: '>= 0.4'} ··· 4995 4691 resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 4996 4692 hasBin: true 4997 4693 4998 - jsdoc-type-pratt-parser@4.8.0: 4999 - resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} 5000 - engines: {node: '>=12.0.0'} 5001 - 5002 - jsdoc-type-pratt-parser@7.0.0: 5003 - resolution: {integrity: sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==} 5004 - engines: {node: '>=20.0.0'} 5005 - 5006 4694 jsdom@27.4.0: 5007 4695 resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==} 5008 4696 engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} ··· 5022 4710 5023 4711 json-parse-even-better-errors@2.3.1: 5024 4712 resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} 5025 - 5026 - json-schema-to-typescript-lite@15.0.0: 5027 - resolution: {integrity: sha512-5mMORSQm9oTLyjM4mWnyNBi2T042Fhg1/0gCIB6X8U/LVpM2A+Nmj2yEyArqVouDmFThDxpEXcnTgSrjkGJRFA==} 5028 4713 5029 4714 json-schema-traverse@0.4.1: 5030 4715 resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} ··· 5176 4861 resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} 5177 4862 engines: {node: '>=20.0.0'} 5178 4863 5179 - load-tsconfig@0.2.5: 5180 - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 5181 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 5182 - 5183 4864 loader-runner@4.3.1: 5184 4865 resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} 5185 4866 engines: {node: '>=6.11.5'} ··· 5191 4872 locate-path@6.0.0: 5192 4873 resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 5193 4874 engines: {node: '>=10'} 5194 - 5195 - locate-path@8.0.0: 5196 - resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} 5197 - engines: {node: '>=20'} 5198 4875 5199 4876 lodash.debounce@4.0.8: 5200 4877 resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} ··· 5417 5094 nanotar@0.2.0: 5418 5095 resolution: {integrity: sha512-9ca1h0Xjvo9bEkE4UOxgAzLV0jHKe6LMaxo37ND2DAhhAtd0j8pR1Wxz+/goMrZO8AEZTWCmyaOsFI/W5AdpCQ==} 5419 5096 5420 - napi-postinstall@0.3.4: 5421 - resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} 5422 - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 5423 - hasBin: true 5424 - 5425 5097 natural-compare@1.4.0: 5426 5098 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 5427 5099 ··· 5523 5195 engines: {node: '>=18'} 5524 5196 hasBin: true 5525 5197 5526 - object-deep-merge@2.0.0: 5527 - resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} 5528 - 5529 5198 object-inspect@1.13.4: 5530 5199 resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 5531 5200 engines: {node: '>= 0.4'} ··· 5602 5271 peerDependencies: 5603 5272 oxc-parser: '>=0.98.0' 5604 5273 5274 + oxfmt@0.26.0: 5275 + resolution: {integrity: sha512-UDD1wFNwfeorMm2ZY0xy1KRAAvJ5NjKBfbDmiMwGP7baEHTq65cYpC0aPP+BGHc8weXUbSZaK8MdGyvuRUvS4Q==} 5276 + engines: {node: ^20.19.0 || >=22.12.0} 5277 + hasBin: true 5278 + 5279 + oxlint@1.41.0: 5280 + resolution: {integrity: sha512-Dyaoup82uhgAgp5xLNt4dPdvl5eSJTIzqzL7DcKbkooUE4PDViWURIPlSUF8hu5a+sCnNIp/LlQMDsKoyaLTBA==} 5281 + engines: {node: ^20.19.0 || >=22.12.0} 5282 + hasBin: true 5283 + peerDependencies: 5284 + oxlint-tsgolint: '>=0.11.1' 5285 + peerDependenciesMeta: 5286 + oxlint-tsgolint: 5287 + optional: true 5288 + 5605 5289 p-limit@3.1.0: 5606 5290 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 5607 5291 engines: {node: '>=10'} 5608 5292 5609 - p-limit@4.0.0: 5610 - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} 5611 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 5612 - 5613 5293 p-locate@5.0.0: 5614 5294 resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 5615 5295 engines: {node: '>=10'} 5616 5296 5617 - p-locate@6.0.0: 5618 - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} 5619 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 5620 - 5621 5297 package-json-from-dist@1.0.1: 5622 5298 resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 5623 5299 ··· 5634 5310 parse-css-color@0.2.1: 5635 5311 resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} 5636 5312 5637 - parse-imports-exports@0.2.4: 5638 - resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} 5639 - 5640 5313 parse-ms@4.0.0: 5641 5314 resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} 5642 5315 engines: {node: '>=18'} ··· 5646 5319 5647 5320 parse-srcset@1.0.2: 5648 5321 resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} 5649 - 5650 - parse-statements@1.0.11: 5651 - resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} 5652 5322 5653 5323 parse-url@9.2.0: 5654 5324 resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} ··· 5731 5401 resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} 5732 5402 engines: {node: '>=18'} 5733 5403 hasBin: true 5734 - 5735 - pluralize@8.0.0: 5736 - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} 5737 - engines: {node: '>=4'} 5738 5404 5739 5405 pngjs@7.0.0: 5740 5406 resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} ··· 6020 5686 resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} 6021 5687 engines: {node: '>=4'} 6022 5688 6023 - refa@0.12.1: 6024 - resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} 6025 - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 6026 - 6027 5689 reflect.getprototypeof@1.0.10: 6028 5690 resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} 6029 5691 engines: {node: '>= 0.4'} ··· 6044 5706 regex@6.1.0: 6045 5707 resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} 6046 5708 6047 - regexp-ast-analysis@0.7.1: 6048 - resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} 6049 - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 6050 - 6051 5709 regexp-tree@0.1.27: 6052 5710 resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} 6053 5711 hasBin: true ··· 6074 5732 require-from-string@2.0.2: 6075 5733 resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} 6076 5734 engines: {node: '>=0.10.0'} 6077 - 6078 - reserved-identifiers@1.2.0: 6079 - resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} 6080 - engines: {node: '>=18'} 6081 5735 6082 5736 resolve-from@4.0.0: 6083 5737 resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} ··· 6197 5851 resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} 6198 5852 engines: {node: '>= 10.13.0'} 6199 5853 6200 - scslre@0.3.0: 6201 - resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} 6202 - engines: {node: ^14.0.0 || >=16.0.0} 6203 - 6204 5854 scule@1.3.0: 6205 5855 resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} 6206 5856 ··· 6355 6005 space-separated-tokens@2.0.2: 6356 6006 resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} 6357 6007 6358 - spdx-exceptions@2.5.0: 6359 - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} 6360 - 6361 - spdx-expression-parse@4.0.0: 6362 - resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} 6363 - 6364 - spdx-license-ids@3.0.22: 6365 - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} 6366 - 6367 6008 speakingurl@14.0.1: 6368 6009 resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} 6369 6010 engines: {node: '>=0.10.0'} ··· 6372 6013 resolution: {integrity: sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg==} 6373 6014 engines: {node: '>=20.16.0'} 6374 6015 hasBin: true 6375 - 6376 - stable-hash-x@0.2.0: 6377 - resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} 6378 - engines: {node: '>=12.0.0'} 6379 6016 6380 6017 stackback@0.0.2: 6381 6018 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} ··· 6468 6105 strip-final-newline@4.0.0: 6469 6106 resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} 6470 6107 engines: {node: '>=18'} 6471 - 6472 - strip-indent@4.1.1: 6473 - resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} 6474 - engines: {node: '>=12'} 6475 6108 6476 6109 strip-json-comments@3.1.1: 6477 6110 resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} ··· 6585 6218 resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 6586 6219 engines: {node: '>=12.0.0'} 6587 6220 6221 + tinypool@2.0.0: 6222 + resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==} 6223 + engines: {node: ^20.0.0 || >=22.0.0} 6224 + 6588 6225 tinyrainbow@3.0.3: 6589 6226 resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} 6590 6227 engines: {node: '>=14.0.0'} ··· 6602 6239 to-regex-range@5.0.1: 6603 6240 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 6604 6241 engines: {node: '>=8.0'} 6605 - 6606 - to-valid-identifier@1.0.0: 6607 - resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} 6608 - engines: {node: '>=20'} 6609 6242 6610 6243 toidentifier@1.0.1: 6611 6244 resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} ··· 6632 6265 trim-lines@3.0.1: 6633 6266 resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} 6634 6267 6635 - ts-api-utils@2.4.0: 6636 - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} 6637 - engines: {node: '>=18.12'} 6638 - peerDependencies: 6639 - typescript: '>=4.8.4' 6640 - 6641 6268 tslib@2.8.1: 6642 6269 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 6643 6270 ··· 6814 6441 unplugin@2.3.11: 6815 6442 resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} 6816 6443 engines: {node: '>=18.12.0'} 6817 - 6818 - unrs-resolver@1.11.1: 6819 - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} 6820 6444 6821 6445 unstorage@1.17.4: 6822 6446 resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} ··· 7093 6717 vue-devtools-stub@0.1.0: 7094 6718 resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} 7095 6719 7096 - vue-eslint-parser@10.2.0: 7097 - resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} 7098 - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 7099 - peerDependencies: 7100 - eslint: ^8.57.0 || ^9.0.0 7101 - 7102 6720 vue-flow-layout@0.2.0: 7103 6721 resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==} 7104 6722 ··· 7290 6908 resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} 7291 6909 engines: {node: '>=18'} 7292 6910 7293 - xml-name-validator@4.0.0: 7294 - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} 7295 - engines: {node: '>=12'} 7296 - 7297 6911 xml-name-validator@5.0.0: 7298 6912 resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} 7299 6913 engines: {node: '>=18'} ··· 7329 6943 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 7330 6944 engines: {node: '>=10'} 7331 6945 7332 - yocto-queue@1.2.2: 7333 - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} 7334 - engines: {node: '>=12.20'} 7335 - 7336 6946 yoctocolors@2.1.2: 7337 6947 resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} 7338 6948 engines: {node: '>=18'} ··· 7373 6983 jsonpointer: 5.0.1 7374 6984 leven: 3.1.0 7375 6985 7376 - '@apidevtools/json-schema-ref-parser@14.2.1(@types/json-schema@7.0.15)': 7377 - dependencies: 7378 - '@types/json-schema': 7.0.15 7379 - js-yaml: 4.1.1 7380 - 7381 6986 '@asamuzakjp/css-color@4.1.1': 7382 6987 dependencies: 7383 6988 '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) ··· 8107 7712 dependencies: 8108 7713 fontkitten: 1.0.2 8109 7714 8110 - '@clack/core@0.5.0': 8111 - dependencies: 8112 - picocolors: 1.1.1 8113 - sisteransi: 1.0.5 8114 - 8115 7715 '@clack/core@1.0.0-alpha.7': 8116 7716 dependencies: 8117 - picocolors: 1.1.1 8118 - sisteransi: 1.0.5 8119 - 8120 - '@clack/prompts@0.11.0': 8121 - dependencies: 8122 - '@clack/core': 0.5.0 8123 7717 picocolors: 1.1.1 8124 7718 sisteransi: 1.0.5 8125 7719 ··· 8186 7780 dependencies: 8187 7781 tslib: 2.8.1 8188 7782 optional: true 8189 - 8190 - '@es-joy/jsdoccomment@0.78.0': 8191 - dependencies: 8192 - '@types/estree': 1.0.8 8193 - '@typescript-eslint/types': 8.53.1 8194 - comment-parser: 1.4.1 8195 - esquery: 1.7.0 8196 - jsdoc-type-pratt-parser: 7.0.0 8197 - 8198 - '@es-joy/resolve.exports@1.2.0': {} 8199 7783 8200 7784 '@esbuild/aix-ppc64@0.25.12': 8201 7785 optional: true ··· 8357 7941 dependencies: 8358 7942 eslint: 9.39.2(jiti@2.6.1) 8359 7943 eslint-visitor-keys: 3.4.3 8360 - 8361 - '@eslint-community/regexpp@4.12.2': {} 7944 + optional: true 8362 7945 8363 - '@eslint/compat@1.4.1(eslint@9.39.2(jiti@2.6.1))': 8364 - dependencies: 8365 - '@eslint/core': 0.17.0 8366 - optionalDependencies: 8367 - eslint: 9.39.2(jiti@2.6.1) 7946 + '@eslint-community/regexpp@4.12.2': 7947 + optional: true 8368 7948 8369 7949 '@eslint/config-array@0.21.1': 8370 7950 dependencies: ··· 8373 7953 minimatch: 3.1.2 8374 7954 transitivePeerDependencies: 8375 7955 - supports-color 7956 + optional: true 8376 7957 8377 7958 '@eslint/config-helpers@0.4.2': 8378 7959 dependencies: 8379 7960 '@eslint/core': 0.17.0 8380 - 8381 - '@eslint/config-inspector@1.4.2(eslint@9.39.2(jiti@2.6.1))': 8382 - dependencies: 8383 - ansis: 4.2.0 8384 - bundle-require: 5.1.0(esbuild@0.27.2) 8385 - cac: 6.7.14 8386 - chokidar: 4.0.3 8387 - esbuild: 0.27.2 8388 - eslint: 9.39.2(jiti@2.6.1) 8389 - h3: 1.15.5 8390 - tinyglobby: 0.2.15 8391 - ws: 8.19.0 8392 - transitivePeerDependencies: 8393 - - bufferutil 8394 - - utf-8-validate 7961 + optional: true 8395 7962 8396 7963 '@eslint/core@0.17.0': 8397 7964 dependencies: 8398 7965 '@types/json-schema': 7.0.15 7966 + optional: true 8399 7967 8400 7968 '@eslint/eslintrc@3.3.3': 8401 7969 dependencies: ··· 8410 7978 strip-json-comments: 3.1.1 8411 7979 transitivePeerDependencies: 8412 7980 - supports-color 7981 + optional: true 8413 7982 8414 - '@eslint/js@9.39.2': {} 7983 + '@eslint/js@9.39.2': 7984 + optional: true 8415 7985 8416 - '@eslint/object-schema@2.1.7': {} 7986 + '@eslint/object-schema@2.1.7': 7987 + optional: true 8417 7988 8418 7989 '@eslint/plugin-kit@0.4.1': 8419 7990 dependencies: 8420 7991 '@eslint/core': 0.17.0 8421 7992 levn: 0.4.1 7993 + optional: true 8422 7994 8423 7995 '@exodus/bytes@1.9.0': 8424 7996 optional: true ··· 8427 7999 dependencies: 8428 8000 kleur: 4.1.5 8429 8001 8430 - '@humanfs/core@0.19.1': {} 8002 + '@humanfs/core@0.19.1': 8003 + optional: true 8431 8004 8432 8005 '@humanfs/node@0.16.7': 8433 8006 dependencies: 8434 8007 '@humanfs/core': 0.19.1 8435 8008 '@humanwhocodes/retry': 0.4.3 8009 + optional: true 8436 8010 8437 - '@humanwhocodes/module-importer@1.0.1': {} 8011 + '@humanwhocodes/module-importer@1.0.1': 8012 + optional: true 8438 8013 8439 - '@humanwhocodes/retry@0.4.3': {} 8014 + '@humanwhocodes/retry@0.4.3': 8015 + optional: true 8440 8016 8441 8017 '@iconify-json/carbon@1.2.18': 8442 8018 dependencies: ··· 8695 8271 - encoding 8696 8272 - supports-color 8697 8273 8698 - '@napi-rs/wasm-runtime@0.2.12': 8699 - dependencies: 8700 - '@emnapi/core': 1.8.1 8701 - '@emnapi/runtime': 1.8.1 8702 - '@tybys/wasm-util': 0.10.1 8703 - optional: true 8704 - 8705 8274 '@napi-rs/wasm-runtime@1.1.1': 8706 8275 dependencies: 8707 8276 '@emnapi/core': 1.8.1 ··· 8820 8389 - utf-8-validate 8821 8390 - vue 8822 8391 8823 - '@nuxt/eslint-config@1.12.1(@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 8824 - dependencies: 8825 - '@antfu/install-pkg': 1.1.0 8826 - '@clack/prompts': 0.11.0 8827 - '@eslint/js': 9.39.2 8828 - '@nuxt/eslint-plugin': 1.12.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8829 - '@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.6.1)) 8830 - '@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8831 - '@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8832 - eslint: 9.39.2(jiti@2.6.1) 8833 - eslint-config-flat-gitignore: 2.1.0(eslint@9.39.2(jiti@2.6.1)) 8834 - eslint-flat-config-utils: 2.1.4 8835 - eslint-merge-processors: 2.0.0(eslint@9.39.2(jiti@2.6.1)) 8836 - eslint-plugin-import-lite: 0.3.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8837 - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) 8838 - eslint-plugin-jsdoc: 61.7.1(eslint@9.39.2(jiti@2.6.1)) 8839 - eslint-plugin-regexp: 2.10.0(eslint@9.39.2(jiti@2.6.1)) 8840 - eslint-plugin-unicorn: 62.0.0(eslint@9.39.2(jiti@2.6.1)) 8841 - eslint-plugin-vue: 10.7.0(@stylistic/eslint-plugin@5.7.0(eslint@9.39.2(jiti@2.6.1)))(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))) 8842 - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1)) 8843 - globals: 16.5.0 8844 - local-pkg: 1.1.2 8845 - pathe: 2.0.3 8846 - vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1)) 8847 - transitivePeerDependencies: 8848 - - '@typescript-eslint/utils' 8849 - - '@vue/compiler-sfc' 8850 - - eslint-import-resolver-node 8851 - - supports-color 8852 - - typescript 8853 - 8854 - '@nuxt/eslint-plugin@1.12.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 8855 - dependencies: 8856 - '@typescript-eslint/types': 8.53.1 8857 - '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8858 - eslint: 9.39.2(jiti@2.6.1) 8859 - transitivePeerDependencies: 8860 - - supports-color 8861 - - typescript 8862 - 8863 - '@nuxt/eslint@1.12.1(@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': 8864 - dependencies: 8865 - '@eslint/config-inspector': 1.4.2(eslint@9.39.2(jiti@2.6.1)) 8866 - '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) 8867 - '@nuxt/eslint-config': 1.12.1(@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8868 - '@nuxt/eslint-plugin': 1.12.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 8869 - '@nuxt/kit': 4.3.0(magicast@0.5.1) 8870 - chokidar: 5.0.0 8871 - eslint: 9.39.2(jiti@2.6.1) 8872 - eslint-flat-config-utils: 2.1.4 8873 - eslint-typegen: 2.3.0(eslint@9.39.2(jiti@2.6.1)) 8874 - find-up: 8.0.0 8875 - get-port-please: 3.2.0 8876 - mlly: 1.8.0 8877 - pathe: 2.0.3 8878 - unimport: 5.6.0 8879 - transitivePeerDependencies: 8880 - - '@typescript-eslint/utils' 8881 - - '@vue/compiler-sfc' 8882 - - bufferutil 8883 - - eslint-import-resolver-node 8884 - - eslint-plugin-format 8885 - - magicast 8886 - - supports-color 8887 - - typescript 8888 - - utf-8-validate 8889 - - vite 8890 - 8891 8392 '@nuxt/fonts@0.13.0(db0@0.3.4)(ioredis@5.9.2)(magicast@0.5.1)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': 8892 8393 dependencies: 8893 8394 '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) ··· 8985 8486 transitivePeerDependencies: 8986 8487 - magicast 8987 8488 8988 - '@nuxt/nitro-server@4.3.0(db0@0.3.4)(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)': 8489 + '@nuxt/nitro-server@4.3.0(db0@0.3.4)(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)': 8989 8490 dependencies: 8990 8491 '@nuxt/devalue': 2.0.2 8991 8492 '@nuxt/kit': 4.3.0(magicast@0.5.1) ··· 9003 8504 klona: 2.0.6 9004 8505 mocked-exports: 0.1.1 9005 8506 nitropack: 2.13.1(rolldown@1.0.0-rc.1) 9006 - nuxt: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 8507 + nuxt: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 9007 8508 ohash: 2.0.11 9008 8509 pathe: 2.0.3 9009 8510 pkg-types: 2.3.0 ··· 9203 8704 - magicast 9204 8705 - typescript 9205 8706 9206 - '@nuxt/vite-builder@4.3.0(@types/node@25.0.10)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2)': 8707 + '@nuxt/vite-builder@4.3.0(@types/node@25.0.10)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2)': 9207 8708 dependencies: 9208 8709 '@nuxt/kit': 4.3.0(magicast@0.5.1) 9209 8710 '@rollup/plugin-replace': 6.0.3(rollup@4.56.0) ··· 9222 8723 magic-string: 0.30.21 9223 8724 mlly: 1.8.0 9224 8725 mocked-exports: 0.1.1 9225 - nuxt: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 8726 + nuxt: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 9226 8727 pathe: 2.0.3 9227 8728 pkg-types: 2.3.0 9228 8729 postcss: 8.5.6 ··· 9233 8734 unenv: 2.0.0-rc.24 9234 8735 vite: 8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) 9235 8736 vite-node: 5.3.0(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) 9236 - vite-plugin-checker: 0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3)) 8737 + vite-plugin-checker: 0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(oxlint@1.41.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3)) 9237 8738 vue: 3.5.27(typescript@5.9.3) 9238 8739 vue-bundle-renderer: 2.2.0 9239 8740 optionalDependencies: ··· 9470 8971 '@oxc-transform/binding-win32-x64-msvc@0.110.0': 9471 8972 optional: true 9472 8973 8974 + '@oxfmt/darwin-arm64@0.26.0': 8975 + optional: true 8976 + 8977 + '@oxfmt/darwin-x64@0.26.0': 8978 + optional: true 8979 + 8980 + '@oxfmt/linux-arm64-gnu@0.26.0': 8981 + optional: true 8982 + 8983 + '@oxfmt/linux-arm64-musl@0.26.0': 8984 + optional: true 8985 + 8986 + '@oxfmt/linux-x64-gnu@0.26.0': 8987 + optional: true 8988 + 8989 + '@oxfmt/linux-x64-musl@0.26.0': 8990 + optional: true 8991 + 8992 + '@oxfmt/win32-arm64@0.26.0': 8993 + optional: true 8994 + 8995 + '@oxfmt/win32-x64@0.26.0': 8996 + optional: true 8997 + 8998 + '@oxlint/darwin-arm64@1.41.0': 8999 + optional: true 9000 + 9001 + '@oxlint/darwin-x64@1.41.0': 9002 + optional: true 9003 + 9004 + '@oxlint/linux-arm64-gnu@1.41.0': 9005 + optional: true 9006 + 9007 + '@oxlint/linux-arm64-musl@1.41.0': 9008 + optional: true 9009 + 9010 + '@oxlint/linux-x64-gnu@1.41.0': 9011 + optional: true 9012 + 9013 + '@oxlint/linux-x64-musl@1.41.0': 9014 + optional: true 9015 + 9016 + '@oxlint/win32-arm64@1.41.0': 9017 + optional: true 9018 + 9019 + '@oxlint/win32-x64@1.41.0': 9020 + optional: true 9021 + 9473 9022 '@parcel/watcher-android-arm64@2.5.4': 9474 9023 optional: true 9475 9024 ··· 9905 9454 ajv: 8.17.1 9906 9455 kleur: 4.1.5 9907 9456 9908 - '@sindresorhus/base62@1.0.0': {} 9909 - 9910 9457 '@sindresorhus/is@7.2.0': {} 9911 9458 9912 9459 '@sindresorhus/merge-streams@4.0.0': {} ··· 9914 9461 '@speed-highlight/core@1.2.14': {} 9915 9462 9916 9463 '@standard-schema/spec@1.1.0': {} 9917 - 9918 - '@stylistic/eslint-plugin@5.7.0(eslint@9.39.2(jiti@2.6.1))': 9919 - dependencies: 9920 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) 9921 - '@typescript-eslint/types': 8.53.1 9922 - eslint: 9.39.2(jiti@2.6.1) 9923 - eslint-visitor-keys: 5.0.0 9924 - espree: 11.1.0 9925 - estraverse: 5.3.0 9926 - picomatch: 4.0.3 9927 9464 9928 9465 '@surma/rollup-plugin-off-main-thread@2.2.3': 9929 9466 dependencies: ··· 10005 9542 dependencies: 10006 9543 '@types/node': 24.10.9 10007 9544 10008 - '@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 10009 - dependencies: 10010 - '@eslint-community/regexpp': 4.12.2 10011 - '@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 10012 - '@typescript-eslint/scope-manager': 8.53.1 10013 - '@typescript-eslint/type-utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 10014 - '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 10015 - '@typescript-eslint/visitor-keys': 8.53.1 10016 - eslint: 9.39.2(jiti@2.6.1) 10017 - ignore: 7.0.5 10018 - natural-compare: 1.4.0 10019 - ts-api-utils: 2.4.0(typescript@5.9.3) 10020 - typescript: 5.9.3 10021 - transitivePeerDependencies: 10022 - - supports-color 10023 - 10024 - '@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 10025 - dependencies: 10026 - '@typescript-eslint/scope-manager': 8.53.1 10027 - '@typescript-eslint/types': 8.53.1 10028 - '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) 10029 - '@typescript-eslint/visitor-keys': 8.53.1 10030 - debug: 4.4.3 10031 - eslint: 9.39.2(jiti@2.6.1) 10032 - typescript: 5.9.3 10033 - transitivePeerDependencies: 10034 - - supports-color 10035 - 10036 - '@typescript-eslint/project-service@8.53.1(typescript@5.9.3)': 10037 - dependencies: 10038 - '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) 10039 - '@typescript-eslint/types': 8.53.1 10040 - debug: 4.4.3 10041 - typescript: 5.9.3 10042 - transitivePeerDependencies: 10043 - - supports-color 10044 - 10045 - '@typescript-eslint/scope-manager@8.53.1': 10046 - dependencies: 10047 - '@typescript-eslint/types': 8.53.1 10048 - '@typescript-eslint/visitor-keys': 8.53.1 10049 - 10050 - '@typescript-eslint/tsconfig-utils@8.53.1(typescript@5.9.3)': 10051 - dependencies: 10052 - typescript: 5.9.3 10053 - 10054 - '@typescript-eslint/type-utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 10055 - dependencies: 10056 - '@typescript-eslint/types': 8.53.1 10057 - '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) 10058 - '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 10059 - debug: 4.4.3 10060 - eslint: 9.39.2(jiti@2.6.1) 10061 - ts-api-utils: 2.4.0(typescript@5.9.3) 10062 - typescript: 5.9.3 10063 - transitivePeerDependencies: 10064 - - supports-color 10065 - 10066 - '@typescript-eslint/types@8.53.1': {} 10067 - 10068 - '@typescript-eslint/typescript-estree@8.53.1(typescript@5.9.3)': 10069 - dependencies: 10070 - '@typescript-eslint/project-service': 8.53.1(typescript@5.9.3) 10071 - '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) 10072 - '@typescript-eslint/types': 8.53.1 10073 - '@typescript-eslint/visitor-keys': 8.53.1 10074 - debug: 4.4.3 10075 - minimatch: 9.0.5 10076 - semver: 7.7.3 10077 - tinyglobby: 0.2.15 10078 - ts-api-utils: 2.4.0(typescript@5.9.3) 10079 - typescript: 5.9.3 10080 - transitivePeerDependencies: 10081 - - supports-color 10082 - 10083 - '@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 10084 - dependencies: 10085 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) 10086 - '@typescript-eslint/scope-manager': 8.53.1 10087 - '@typescript-eslint/types': 8.53.1 10088 - '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) 10089 - eslint: 9.39.2(jiti@2.6.1) 10090 - typescript: 5.9.3 10091 - transitivePeerDependencies: 10092 - - supports-color 10093 - 10094 - '@typescript-eslint/visitor-keys@8.53.1': 10095 - dependencies: 10096 - '@typescript-eslint/types': 8.53.1 10097 - eslint-visitor-keys: 4.2.1 10098 - 10099 9545 '@ungap/structured-clone@1.3.0': {} 10100 9546 10101 9547 '@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3))': ··· 10290 9736 webpack: 5.104.1(esbuild@0.27.2) 10291 9737 webpack-sources: 3.3.3 10292 9738 10293 - '@unrs/resolver-binding-android-arm-eabi@1.11.1': 10294 - optional: true 10295 - 10296 - '@unrs/resolver-binding-android-arm64@1.11.1': 10297 - optional: true 10298 - 10299 - '@unrs/resolver-binding-darwin-arm64@1.11.1': 10300 - optional: true 10301 - 10302 - '@unrs/resolver-binding-darwin-x64@1.11.1': 10303 - optional: true 10304 - 10305 - '@unrs/resolver-binding-freebsd-x64@1.11.1': 10306 - optional: true 10307 - 10308 - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': 10309 - optional: true 10310 - 10311 - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': 10312 - optional: true 10313 - 10314 - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': 10315 - optional: true 10316 - 10317 - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': 10318 - optional: true 10319 - 10320 - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': 10321 - optional: true 10322 - 10323 - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': 10324 - optional: true 10325 - 10326 - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': 10327 - optional: true 10328 - 10329 - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': 10330 - optional: true 10331 - 10332 - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': 10333 - optional: true 10334 - 10335 - '@unrs/resolver-binding-linux-x64-musl@1.11.1': 10336 - optional: true 10337 - 10338 - '@unrs/resolver-binding-wasm32-wasi@1.11.1': 10339 - dependencies: 10340 - '@napi-rs/wasm-runtime': 0.2.12 10341 - optional: true 10342 - 10343 - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': 10344 - optional: true 10345 - 10346 - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': 10347 - optional: true 10348 - 10349 - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': 10350 - optional: true 10351 - 10352 9739 '@vercel/nft@1.3.0(rollup@4.56.0)': 10353 9740 dependencies: 10354 9741 '@mapbox/node-pre-gyp': 2.0.3 ··· 10652 10039 10653 10040 '@vueuse/metadata@14.1.0': {} 10654 10041 10655 - '@vueuse/nuxt@14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))': 10042 + '@vueuse/nuxt@14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))': 10656 10043 dependencies: 10657 10044 '@nuxt/kit': 4.3.0(magicast@0.5.1) 10658 10045 '@vueuse/core': 14.1.0(vue@3.5.27(typescript@5.9.3)) 10659 10046 '@vueuse/metadata': 14.1.0 10660 10047 local-pkg: 1.1.2 10661 - nuxt: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 10048 + nuxt: 4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2) 10662 10049 vue: 3.5.27(typescript@5.9.3) 10663 10050 transitivePeerDependencies: 10664 10051 - magicast ··· 10766 10153 acorn-jsx@5.3.2(acorn@8.15.0): 10767 10154 dependencies: 10768 10155 acorn: 8.15.0 10156 + optional: true 10769 10157 10770 10158 acorn@8.15.0: {} 10771 10159 ··· 10786 10174 fast-json-stable-stringify: 2.1.0 10787 10175 json-schema-traverse: 0.4.1 10788 10176 uri-js: 4.4.1 10177 + optional: true 10789 10178 10790 10179 ajv@8.17.1: 10791 10180 dependencies: ··· 10840 10229 - bare-abort-controller 10841 10230 - react-native-b4a 10842 10231 10843 - are-docs-informative@0.0.2: {} 10844 - 10845 - argparse@2.0.1: {} 10232 + argparse@2.0.1: 10233 + optional: true 10846 10234 10847 10235 array-buffer-byte-length@1.0.2: 10848 10236 dependencies: ··· 10951 10339 dependencies: 10952 10340 balanced-match: 1.0.2 10953 10341 concat-map: 0.0.1 10342 + optional: true 10954 10343 10955 10344 brace-expansion@2.0.2: 10956 10345 dependencies: ··· 10981 10370 base64-js: 1.5.1 10982 10371 ieee754: 1.2.1 10983 10372 10984 - builtin-modules@5.0.0: {} 10985 - 10986 10373 bundle-name@4.1.0: 10987 10374 dependencies: 10988 10375 run-applescript: 7.1.0 10989 10376 10990 - bundle-require@5.1.0(esbuild@0.27.2): 10991 - dependencies: 10992 - esbuild: 0.27.2 10993 - load-tsconfig: 0.2.5 10994 - 10995 10377 c12@3.3.3(magicast@0.5.1): 10996 10378 dependencies: 10997 10379 chokidar: 5.0.0 ··· 11028 10410 call-bind-apply-helpers: 1.0.2 11029 10411 get-intrinsic: 1.3.0 11030 10412 11031 - callsites@3.1.0: {} 10413 + callsites@3.1.0: 10414 + optional: true 11032 10415 11033 10416 camelize@1.0.1: {} 11034 10417 ··· 11049 10432 dependencies: 11050 10433 ansi-styles: 4.3.0 11051 10434 supports-color: 7.2.0 11052 - 11053 - change-case@5.4.4: {} 10435 + optional: true 11054 10436 11055 10437 character-entities-html4@2.1.0: {} 11056 10438 ··· 11077 10459 11078 10460 chrome-trace-event@1.0.4: {} 11079 10461 11080 - ci-info@4.3.1: {} 11081 - 11082 10462 citty@0.1.6: 11083 10463 dependencies: 11084 10464 consola: 3.4.2 11085 10465 11086 10466 citty@0.2.0: {} 11087 - 11088 - clean-regexp@1.0.0: 11089 - dependencies: 11090 - escape-string-regexp: 1.0.5 11091 10467 11092 10468 cli-cursor@5.0.0: 11093 10469 dependencies: ··· 11144 10520 11145 10521 commander@2.20.3: {} 11146 10522 11147 - comment-parser@1.4.1: {} 11148 - 11149 - comment-parser@1.4.4: {} 11150 - 11151 10523 common-tags@1.8.2: {} 11152 10524 11153 10525 commondir@1.0.1: {} ··· 11162 10534 normalize-path: 3.0.0 11163 10535 readable-stream: 4.7.0 11164 10536 11165 - concat-map@0.0.1: {} 10537 + concat-map@0.0.1: 10538 + optional: true 11166 10539 11167 10540 confbox@0.1.8: {} 11168 10541 ··· 11358 10731 decode-bmp: 0.2.1 11359 10732 to-data-view: 1.1.0 11360 10733 11361 - deep-is@0.1.4: {} 10734 + deep-is@0.1.4: 10735 + optional: true 11362 10736 11363 10737 deepmerge@4.3.1: {} 11364 10738 ··· 11630 11004 11631 11005 escape-html@1.0.3: {} 11632 11006 11633 - escape-string-regexp@1.0.5: {} 11634 - 11635 11007 escape-string-regexp@4.0.0: {} 11636 11008 11637 11009 escape-string-regexp@5.0.0: {} 11638 11010 11639 - eslint-config-flat-gitignore@2.1.0(eslint@9.39.2(jiti@2.6.1)): 11640 - dependencies: 11641 - '@eslint/compat': 1.4.1(eslint@9.39.2(jiti@2.6.1)) 11642 - eslint: 9.39.2(jiti@2.6.1) 11643 - 11644 - eslint-flat-config-utils@2.1.4: 11645 - dependencies: 11646 - pathe: 2.0.3 11647 - 11648 - eslint-import-context@0.1.9(unrs-resolver@1.11.1): 11649 - dependencies: 11650 - get-tsconfig: 4.13.0 11651 - stable-hash-x: 0.2.0 11652 - optionalDependencies: 11653 - unrs-resolver: 1.11.1 11654 - 11655 - eslint-merge-processors@2.0.0(eslint@9.39.2(jiti@2.6.1)): 11656 - dependencies: 11657 - eslint: 9.39.2(jiti@2.6.1) 11658 - 11659 - eslint-plugin-import-lite@0.3.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): 11660 - dependencies: 11661 - eslint: 9.39.2(jiti@2.6.1) 11662 - optionalDependencies: 11663 - typescript: 5.9.3 11664 - 11665 - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): 11666 - dependencies: 11667 - '@typescript-eslint/types': 8.53.1 11668 - comment-parser: 1.4.4 11669 - debug: 4.4.3 11670 - eslint: 9.39.2(jiti@2.6.1) 11671 - eslint-import-context: 0.1.9(unrs-resolver@1.11.1) 11672 - is-glob: 4.0.3 11673 - minimatch: 10.1.1 11674 - semver: 7.7.3 11675 - stable-hash-x: 0.2.0 11676 - unrs-resolver: 1.11.1 11677 - optionalDependencies: 11678 - '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 11679 - transitivePeerDependencies: 11680 - - supports-color 11681 - 11682 - eslint-plugin-jsdoc@61.7.1(eslint@9.39.2(jiti@2.6.1)): 11683 - dependencies: 11684 - '@es-joy/jsdoccomment': 0.78.0 11685 - '@es-joy/resolve.exports': 1.2.0 11686 - are-docs-informative: 0.0.2 11687 - comment-parser: 1.4.1 11688 - debug: 4.4.3 11689 - escape-string-regexp: 4.0.0 11690 - eslint: 9.39.2(jiti@2.6.1) 11691 - espree: 11.1.0 11692 - esquery: 1.7.0 11693 - html-entities: 2.6.0 11694 - object-deep-merge: 2.0.0 11695 - parse-imports-exports: 0.2.4 11696 - semver: 7.7.3 11697 - spdx-expression-parse: 4.0.0 11698 - to-valid-identifier: 1.0.0 11699 - transitivePeerDependencies: 11700 - - supports-color 11701 - 11702 - eslint-plugin-regexp@2.10.0(eslint@9.39.2(jiti@2.6.1)): 11703 - dependencies: 11704 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) 11705 - '@eslint-community/regexpp': 4.12.2 11706 - comment-parser: 1.4.4 11707 - eslint: 9.39.2(jiti@2.6.1) 11708 - jsdoc-type-pratt-parser: 4.8.0 11709 - refa: 0.12.1 11710 - regexp-ast-analysis: 0.7.1 11711 - scslre: 0.3.0 11712 - 11713 - eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@2.6.1)): 11714 - dependencies: 11715 - '@babel/helper-validator-identifier': 7.28.5 11716 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) 11717 - '@eslint/plugin-kit': 0.4.1 11718 - change-case: 5.4.4 11719 - ci-info: 4.3.1 11720 - clean-regexp: 1.0.0 11721 - core-js-compat: 3.48.0 11722 - eslint: 9.39.2(jiti@2.6.1) 11723 - esquery: 1.7.0 11724 - find-up-simple: 1.0.1 11725 - globals: 16.5.0 11726 - indent-string: 5.0.0 11727 - is-builtin-module: 5.0.0 11728 - jsesc: 3.1.0 11729 - pluralize: 8.0.0 11730 - regexp-tree: 0.1.27 11731 - regjsparser: 0.13.0 11732 - semver: 7.7.3 11733 - strip-indent: 4.1.1 11734 - 11735 - eslint-plugin-vue@10.7.0(@stylistic/eslint-plugin@5.7.0(eslint@9.39.2(jiti@2.6.1)))(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))): 11736 - dependencies: 11737 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) 11738 - eslint: 9.39.2(jiti@2.6.1) 11739 - natural-compare: 1.4.0 11740 - nth-check: 2.1.1 11741 - postcss-selector-parser: 7.1.1 11742 - semver: 7.7.3 11743 - vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1)) 11744 - xml-name-validator: 4.0.0 11745 - optionalDependencies: 11746 - '@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.6.1)) 11747 - '@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 11748 - 11749 - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1)): 11750 - dependencies: 11751 - '@vue/compiler-sfc': 3.5.27 11752 - eslint: 9.39.2(jiti@2.6.1) 11753 - 11754 11011 eslint-scope@5.1.1: 11755 11012 dependencies: 11756 11013 esrecurse: 4.3.0 ··· 11760 11017 dependencies: 11761 11018 esrecurse: 4.3.0 11762 11019 estraverse: 5.3.0 11020 + optional: true 11763 11021 11764 - eslint-typegen@2.3.0(eslint@9.39.2(jiti@2.6.1)): 11765 - dependencies: 11766 - eslint: 9.39.2(jiti@2.6.1) 11767 - json-schema-to-typescript-lite: 15.0.0 11768 - ohash: 2.0.11 11022 + eslint-visitor-keys@3.4.3: 11023 + optional: true 11769 11024 11770 - eslint-visitor-keys@3.4.3: {} 11771 - 11772 - eslint-visitor-keys@4.2.1: {} 11773 - 11774 - eslint-visitor-keys@5.0.0: {} 11025 + eslint-visitor-keys@4.2.1: 11026 + optional: true 11775 11027 11776 11028 eslint@9.39.2(jiti@2.6.1): 11777 11029 dependencies: ··· 11813 11065 jiti: 2.6.1 11814 11066 transitivePeerDependencies: 11815 11067 - supports-color 11068 + optional: true 11816 11069 11817 11070 espree@10.4.0: 11818 11071 dependencies: 11819 11072 acorn: 8.15.0 11820 11073 acorn-jsx: 5.3.2(acorn@8.15.0) 11821 11074 eslint-visitor-keys: 4.2.1 11822 - 11823 - espree@11.1.0: 11824 - dependencies: 11825 - acorn: 8.15.0 11826 - acorn-jsx: 5.3.2(acorn@8.15.0) 11827 - eslint-visitor-keys: 5.0.0 11075 + optional: true 11828 11076 11829 11077 esquery@1.7.0: 11830 11078 dependencies: 11831 11079 estraverse: 5.3.0 11080 + optional: true 11832 11081 11833 11082 esrecurse@4.3.0: 11834 11083 dependencies: ··· 11909 11158 11910 11159 fast-json-stable-stringify@2.1.0: {} 11911 11160 11912 - fast-levenshtein@2.0.6: {} 11161 + fast-levenshtein@2.0.6: 11162 + optional: true 11913 11163 11914 11164 fast-npm-meta@0.4.8: {} 11915 11165 ··· 11932 11182 file-entry-cache@8.0.0: 11933 11183 dependencies: 11934 11184 flat-cache: 4.0.1 11185 + optional: true 11935 11186 11936 11187 file-uri-to-path@1.0.0: {} 11937 11188 ··· 11943 11194 dependencies: 11944 11195 to-regex-range: 5.0.1 11945 11196 11946 - find-up-simple@1.0.1: {} 11947 - 11948 11197 find-up@5.0.0: 11949 11198 dependencies: 11950 11199 locate-path: 6.0.0 11951 11200 path-exists: 4.0.0 11952 - 11953 - find-up@8.0.0: 11954 - dependencies: 11955 - locate-path: 8.0.0 11956 - unicorn-magic: 0.3.0 11201 + optional: true 11957 11202 11958 11203 fix-dts-default-cjs-exports@1.0.1: 11959 11204 dependencies: ··· 11965 11210 dependencies: 11966 11211 flatted: 3.3.3 11967 11212 keyv: 4.5.4 11213 + optional: true 11968 11214 11969 - flatted@3.3.3: {} 11215 + flatted@3.3.3: 11216 + optional: true 11970 11217 11971 11218 fontaine@0.7.0: 11972 11219 dependencies: ··· 12129 11376 get-tsconfig@4.13.0: 12130 11377 dependencies: 12131 11378 resolve-pkg-maps: 1.0.0 11379 + optional: true 12132 11380 12133 11381 giget@2.0.0: 12134 11382 dependencies: ··· 12155 11403 glob-parent@6.0.2: 12156 11404 dependencies: 12157 11405 is-glob: 4.0.3 11406 + optional: true 12158 11407 12159 11408 glob-to-regexp@0.4.1: {} 12160 11409 ··· 12188 11437 12189 11438 globals@11.12.0: {} 12190 11439 12191 - globals@14.0.0: {} 12192 - 12193 - globals@16.5.0: {} 11440 + globals@14.0.0: 11441 + optional: true 12194 11442 12195 11443 globalthis@1.0.4: 12196 11444 dependencies: ··· 12300 11548 - '@noble/hashes' 12301 11549 optional: true 12302 11550 12303 - html-entities@2.6.0: {} 12304 - 12305 11551 html-escaper@2.0.2: {} 12306 11552 12307 11553 html-validate@9.4.2(vitest@4.0.18): ··· 12367 11613 12368 11614 ieee754@1.2.1: {} 12369 11615 12370 - ignore@5.3.2: {} 11616 + ignore@5.3.2: 11617 + optional: true 12371 11618 12372 11619 ignore@7.0.5: {} 12373 11620 ··· 12379 11626 dependencies: 12380 11627 parent-module: 1.0.1 12381 11628 resolve-from: 4.0.0 11629 + optional: true 12382 11630 12383 11631 impound@1.0.0: 12384 11632 dependencies: ··· 12388 11636 unplugin: 2.3.11 12389 11637 unplugin-utils: 0.2.5 12390 11638 12391 - imurmurhash@0.1.4: {} 12392 - 12393 - indent-string@5.0.0: {} 11639 + imurmurhash@0.1.4: 11640 + optional: true 12394 11641 12395 11642 inherits@2.0.4: {} 12396 11643 ··· 12444 11691 dependencies: 12445 11692 call-bound: 1.0.4 12446 11693 has-tostringtag: 1.0.2 12447 - 12448 - is-builtin-module@5.0.0: 12449 - dependencies: 12450 - builtin-modules: 5.0.0 12451 11694 12452 11695 is-callable@1.2.7: {} 12453 11696 ··· 12662 11905 js-yaml@4.1.1: 12663 11906 dependencies: 12664 11907 argparse: 2.0.1 12665 - 12666 - jsdoc-type-pratt-parser@4.8.0: {} 12667 - 12668 - jsdoc-type-pratt-parser@7.0.0: {} 11908 + optional: true 12669 11909 12670 11910 jsdom@27.4.0: 12671 11911 dependencies: ··· 12698 11938 12699 11939 jsesc@3.1.0: {} 12700 11940 12701 - json-buffer@3.0.1: {} 11941 + json-buffer@3.0.1: 11942 + optional: true 12702 11943 12703 11944 json-parse-even-better-errors@2.3.1: {} 12704 11945 12705 - json-schema-to-typescript-lite@15.0.0: 12706 - dependencies: 12707 - '@apidevtools/json-schema-ref-parser': 14.2.1(@types/json-schema@7.0.15) 12708 - '@types/json-schema': 7.0.15 12709 - 12710 - json-schema-traverse@0.4.1: {} 11946 + json-schema-traverse@0.4.1: 11947 + optional: true 12711 11948 12712 11949 json-schema-traverse@1.0.0: {} 12713 11950 12714 11951 json-schema@0.4.0: {} 12715 11952 12716 - json-stable-stringify-without-jsonify@1.0.1: {} 11953 + json-stable-stringify-without-jsonify@1.0.1: 11954 + optional: true 12717 11955 12718 11956 json5@2.2.3: {} 12719 11957 ··· 12728 11966 keyv@4.5.4: 12729 11967 dependencies: 12730 11968 json-buffer: 3.0.1 11969 + optional: true 12731 11970 12732 11971 kleur@3.0.3: {} 12733 11972 ··· 12752 11991 dependencies: 12753 11992 prelude-ls: 1.2.1 12754 11993 type-check: 0.4.0 11994 + optional: true 12755 11995 12756 11996 lighthouse-logger@2.0.2: 12757 11997 dependencies: ··· 12855 12095 log-update: 6.1.0 12856 12096 rfdc: 1.4.1 12857 12097 wrap-ansi: 9.0.2 12858 - 12859 - load-tsconfig@0.2.5: {} 12860 12098 12861 12099 loader-runner@4.3.1: {} 12862 12100 ··· 12869 12107 locate-path@6.0.0: 12870 12108 dependencies: 12871 12109 p-locate: 5.0.0 12872 - 12873 - locate-path@8.0.0: 12874 - dependencies: 12875 - p-locate: 6.0.0 12110 + optional: true 12876 12111 12877 12112 lodash.debounce@4.0.8: {} 12878 12113 ··· 12882 12117 12883 12118 lodash.memoize@4.1.2: {} 12884 12119 12885 - lodash.merge@4.6.2: {} 12120 + lodash.merge@4.6.2: 12121 + optional: true 12886 12122 12887 12123 lodash.sortby@4.7.0: {} 12888 12124 ··· 13011 12247 minimatch@3.1.2: 13012 12248 dependencies: 13013 12249 brace-expansion: 1.1.12 12250 + optional: true 13014 12251 13015 12252 minimatch@5.1.6: 13016 12253 dependencies: ··· 13077 12314 13078 12315 nanotar@0.2.0: {} 13079 12316 13080 - napi-postinstall@0.3.4: {} 13081 - 13082 - natural-compare@1.4.0: {} 12317 + natural-compare@1.4.0: 12318 + optional: true 13083 12319 13084 12320 neo-async@2.6.2: {} 13085 12321 ··· 13289 12525 - magicast 13290 12526 - vue 13291 12527 13292 - nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2): 12528 + nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2): 13293 12529 dependencies: 13294 12530 '@dxup/nuxt': 0.3.2(magicast@0.5.1) 13295 12531 '@nuxt/cli': 3.32.0(cac@6.7.14)(magicast@0.5.1) 13296 12532 '@nuxt/devtools': 3.1.1(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)) 13297 12533 '@nuxt/kit': 4.3.0(magicast@0.5.1) 13298 - '@nuxt/nitro-server': 4.3.0(db0@0.3.4)(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3) 12534 + '@nuxt/nitro-server': 4.3.0(db0@0.3.4)(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3) 13299 12535 '@nuxt/schema': 4.3.0 13300 12536 '@nuxt/telemetry': 2.6.6(magicast@0.5.1) 13301 - '@nuxt/vite-builder': 4.3.0(@types/node@25.0.10)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2) 12537 + '@nuxt/vite-builder': 4.3.0(@types/node@25.0.10)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.4)(@types/node@25.0.10)(@vue/compiler-sfc@3.5.27)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(oxlint@1.41.0)(rolldown@1.0.0-rc.1)(rollup@4.56.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2) 13302 12538 '@unhead/vue': 2.1.2(vue@3.5.27(typescript@5.9.3)) 13303 12539 '@vue/shared': 3.5.27 13304 12540 c12: 3.3.3(magicast@0.5.1) ··· 13416 12652 citty: 0.2.0 13417 12653 pathe: 2.0.3 13418 12654 tinyexec: 1.0.2 13419 - 13420 - object-deep-merge@2.0.0: {} 13421 12655 13422 12656 object-inspect@1.13.4: {} 13423 12657 ··· 13485 12719 prelude-ls: 1.2.1 13486 12720 type-check: 0.4.0 13487 12721 word-wrap: 1.2.5 12722 + optional: true 13488 12723 13489 12724 own-keys@1.0.1: 13490 12725 dependencies: ··· 13568 12803 magic-regexp: 0.10.0 13569 12804 oxc-parser: 0.110.0 13570 12805 12806 + oxfmt@0.26.0: 12807 + dependencies: 12808 + tinypool: 2.0.0 12809 + optionalDependencies: 12810 + '@oxfmt/darwin-arm64': 0.26.0 12811 + '@oxfmt/darwin-x64': 0.26.0 12812 + '@oxfmt/linux-arm64-gnu': 0.26.0 12813 + '@oxfmt/linux-arm64-musl': 0.26.0 12814 + '@oxfmt/linux-x64-gnu': 0.26.0 12815 + '@oxfmt/linux-x64-musl': 0.26.0 12816 + '@oxfmt/win32-arm64': 0.26.0 12817 + '@oxfmt/win32-x64': 0.26.0 12818 + 12819 + oxlint@1.41.0: 12820 + optionalDependencies: 12821 + '@oxlint/darwin-arm64': 1.41.0 12822 + '@oxlint/darwin-x64': 1.41.0 12823 + '@oxlint/linux-arm64-gnu': 1.41.0 12824 + '@oxlint/linux-arm64-musl': 1.41.0 12825 + '@oxlint/linux-x64-gnu': 1.41.0 12826 + '@oxlint/linux-x64-musl': 1.41.0 12827 + '@oxlint/win32-arm64': 1.41.0 12828 + '@oxlint/win32-x64': 1.41.0 12829 + 13571 12830 p-limit@3.1.0: 13572 12831 dependencies: 13573 12832 yocto-queue: 0.1.0 13574 - 13575 - p-limit@4.0.0: 13576 - dependencies: 13577 - yocto-queue: 1.2.2 12833 + optional: true 13578 12834 13579 12835 p-locate@5.0.0: 13580 12836 dependencies: 13581 12837 p-limit: 3.1.0 13582 - 13583 - p-locate@6.0.0: 13584 - dependencies: 13585 - p-limit: 4.0.0 12838 + optional: true 13586 12839 13587 12840 package-json-from-dist@1.0.1: {} 13588 12841 ··· 13593 12846 parent-module@1.0.1: 13594 12847 dependencies: 13595 12848 callsites: 3.1.0 12849 + optional: true 13596 12850 13597 12851 parse-css-color@0.2.1: 13598 12852 dependencies: 13599 12853 color-name: 1.1.4 13600 12854 hex-rgb: 4.3.0 13601 12855 13602 - parse-imports-exports@0.2.4: 13603 - dependencies: 13604 - parse-statements: 1.0.11 13605 - 13606 12856 parse-ms@4.0.0: {} 13607 12857 13608 12858 parse-path@7.1.0: ··· 13610 12860 protocols: 2.0.2 13611 12861 13612 12862 parse-srcset@1.0.2: {} 13613 - 13614 - parse-statements@1.0.11: {} 13615 12863 13616 12864 parse-url@9.2.0: 13617 12865 dependencies: ··· 13627 12875 13628 12876 path-browserify@1.0.1: {} 13629 12877 13630 - path-exists@4.0.0: {} 12878 + path-exists@4.0.0: 12879 + optional: true 13631 12880 13632 12881 path-key@3.1.1: {} 13633 12882 ··· 13682 12931 playwright-core: 1.57.0 13683 12932 optionalDependencies: 13684 12933 fsevents: 2.3.2 13685 - 13686 - pluralize@8.0.0: {} 13687 12934 13688 12935 pngjs@7.0.0: {} 13689 12936 ··· 13856 13103 picocolors: 1.1.1 13857 13104 source-map-js: 1.2.1 13858 13105 13859 - prelude-ls@1.2.1: {} 13106 + prelude-ls@1.2.1: 13107 + optional: true 13860 13108 13861 13109 prettier@3.8.1: {} 13862 13110 ··· 13938 13186 dependencies: 13939 13187 redis-errors: 1.2.0 13940 13188 13941 - refa@0.12.1: 13942 - dependencies: 13943 - '@eslint-community/regexpp': 4.12.2 13944 - 13945 13189 reflect.getprototypeof@1.0.10: 13946 13190 dependencies: 13947 13191 call-bind: 1.0.8 ··· 13969 13213 dependencies: 13970 13214 regex-utilities: 2.3.0 13971 13215 13972 - regexp-ast-analysis@0.7.1: 13973 - dependencies: 13974 - '@eslint-community/regexpp': 4.12.2 13975 - refa: 0.12.1 13976 - 13977 13216 regexp-tree@0.1.27: {} 13978 13217 13979 13218 regexp.prototype.flags@1.5.4: ··· 14004 13243 14005 13244 require-from-string@2.0.2: {} 14006 13245 14007 - reserved-identifiers@1.2.0: {} 14008 - 14009 - resolve-from@4.0.0: {} 13246 + resolve-from@4.0.0: 13247 + optional: true 14010 13248 14011 13249 resolve-from@5.0.0: {} 14012 13250 14013 - resolve-pkg-maps@1.0.0: {} 13251 + resolve-pkg-maps@1.0.0: 13252 + optional: true 14014 13253 14015 13254 resolve@1.22.11: 14016 13255 dependencies: ··· 14174 13413 ajv: 8.17.1 14175 13414 ajv-formats: 2.1.1(ajv@8.17.1) 14176 13415 ajv-keywords: 5.1.0(ajv@8.17.1) 14177 - 14178 - scslre@0.3.0: 14179 - dependencies: 14180 - '@eslint-community/regexpp': 4.12.2 14181 - refa: 0.12.1 14182 - regexp-ast-analysis: 0.7.1 14183 13416 14184 13417 scule@1.3.0: {} 14185 13418 ··· 14415 13648 14416 13649 space-separated-tokens@2.0.2: {} 14417 13650 14418 - spdx-exceptions@2.5.0: {} 14419 - 14420 - spdx-expression-parse@4.0.0: 14421 - dependencies: 14422 - spdx-exceptions: 2.5.0 14423 - spdx-license-ids: 3.0.22 14424 - 14425 - spdx-license-ids@3.0.22: {} 14426 - 14427 13651 speakingurl@14.0.1: {} 14428 13652 14429 13653 srvx@0.10.1: {} 14430 - 14431 - stable-hash-x@0.2.0: {} 14432 13654 14433 13655 stackback@0.0.2: {} 14434 13656 ··· 14551 13773 14552 13774 strip-final-newline@4.0.0: {} 14553 13775 14554 - strip-indent@4.1.1: {} 14555 - 14556 - strip-json-comments@3.1.1: {} 13776 + strip-json-comments@3.1.1: 13777 + optional: true 14557 13778 14558 13779 strip-literal@3.1.0: 14559 13780 dependencies: ··· 14665 13886 fdir: 6.5.0(picomatch@4.0.3) 14666 13887 picomatch: 4.0.3 14667 13888 13889 + tinypool@2.0.0: {} 13890 + 14668 13891 tinyrainbow@3.0.3: {} 14669 13892 14670 13893 tldts-core@7.0.19: ··· 14681 13904 dependencies: 14682 13905 is-number: 7.0.0 14683 13906 14684 - to-valid-identifier@1.0.0: 14685 - dependencies: 14686 - '@sindresorhus/base62': 1.0.0 14687 - reserved-identifiers: 1.2.0 14688 - 14689 13907 toidentifier@1.0.1: {} 14690 13908 14691 13909 totalist@3.0.1: {} ··· 14708 13926 14709 13927 trim-lines@3.0.1: {} 14710 13928 14711 - ts-api-utils@2.4.0(typescript@5.9.3): 14712 - dependencies: 14713 - typescript: 5.9.3 14714 - 14715 13929 tslib@2.8.1: {} 14716 13930 14717 13931 tsx@4.21.0: ··· 14725 13939 type-check@0.4.0: 14726 13940 dependencies: 14727 13941 prelude-ls: 1.2.1 13942 + optional: true 14728 13943 14729 13944 type-fest@0.16.0: {} 14730 13945 ··· 14993 14208 picomatch: 4.0.3 14994 14209 webpack-virtual-modules: 0.6.2 14995 14210 14996 - unrs-resolver@1.11.1: 14997 - dependencies: 14998 - napi-postinstall: 0.3.4 14999 - optionalDependencies: 15000 - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 15001 - '@unrs/resolver-binding-android-arm64': 1.11.1 15002 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 15003 - '@unrs/resolver-binding-darwin-x64': 1.11.1 15004 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 15005 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 15006 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 15007 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 15008 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 15009 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 15010 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 15011 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 15012 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 15013 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 15014 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 15015 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 15016 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 15017 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 15018 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 15019 - 15020 14211 unstorage@1.17.4(db0@0.3.4)(ioredis@5.9.2): 15021 14212 dependencies: 15022 14213 anymatch: 3.1.3 ··· 15067 14258 uri-js@4.4.1: 15068 14259 dependencies: 15069 14260 punycode: 2.3.1 14261 + optional: true 15070 14262 15071 14263 util-deprecate@1.0.2: {} 15072 14264 ··· 15114 14306 - tsx 15115 14307 - yaml 15116 14308 15117 - vite-plugin-checker@0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3)): 14309 + vite-plugin-checker@0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(oxlint@1.41.0)(typescript@5.9.3)(vite@8.0.0-beta.10(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.2.2(typescript@5.9.3)): 15118 14310 dependencies: 15119 14311 '@babel/code-frame': 7.28.6 15120 14312 chokidar: 4.0.3 ··· 15128 14320 optionalDependencies: 15129 14321 eslint: 9.39.2(jiti@2.6.1) 15130 14322 optionator: 0.9.4 14323 + oxlint: 1.41.0 15131 14324 typescript: 5.9.3 15132 14325 vue-tsc: 3.2.2(typescript@5.9.3) 15133 14326 ··· 15257 14450 15258 14451 vue-devtools-stub@0.1.0: {} 15259 14452 15260 - vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)): 15261 - dependencies: 15262 - debug: 4.4.3 15263 - eslint: 9.39.2(jiti@2.6.1) 15264 - eslint-scope: 8.4.0 15265 - eslint-visitor-keys: 4.2.1 15266 - espree: 10.4.0 15267 - esquery: 1.7.0 15268 - semver: 7.7.3 15269 - transitivePeerDependencies: 15270 - - supports-color 15271 - 15272 14453 vue-flow-layout@0.2.0: {} 15273 14454 15274 14455 vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)): ··· 15424 14605 siginfo: 2.0.0 15425 14606 stackback: 0.0.2 15426 14607 15427 - word-wrap@1.2.5: {} 14608 + word-wrap@1.2.5: 14609 + optional: true 15428 14610 15429 14611 workbox-background-sync@7.4.0: 15430 14612 dependencies: ··· 15563 14745 dependencies: 15564 14746 is-wsl: 3.1.0 15565 14747 15566 - xml-name-validator@4.0.0: {} 15567 - 15568 14748 xml-name-validator@5.0.0: 15569 14749 optional: true 15570 14750 ··· 15591 14771 y18n: 5.0.8 15592 14772 yargs-parser: 21.1.1 15593 14773 15594 - yocto-queue@0.1.0: {} 15595 - 15596 - yocto-queue@1.2.2: {} 14774 + yocto-queue@0.1.0: 14775 + optional: true 15597 14776 15598 14777 yoctocolors@2.1.2: {} 15599 14778
+1 -3
renovate.json
··· 1 1 { 2 2 "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 3 "prCreation": "immediate", 4 - "extends": [ 5 - "github>danielroe/renovate" 6 - ] 4 + "extends": ["github>danielroe/renovate"] 7 5 }
+2 -3
server/api/registry/[...pkg].get.ts
··· 1 1 export default defineCachedEventHandler( 2 - async (event) => { 2 + async event => { 3 3 const pkg = getRouterParam(event, 'pkg') 4 4 if (!pkg) { 5 5 throw createError({ statusCode: 400, message: 'Package name is required' }) ··· 9 9 10 10 try { 11 11 return await fetchNpmPackage(packageName) 12 - } 13 - catch (error) { 12 + } catch (error) { 14 13 if (error && typeof error === 'object' && 'statusCode' in error) { 15 14 throw error 16 15 }
+3 -4
server/api/registry/downloads/[...pkg].get.ts
··· 1 1 export default defineCachedEventHandler( 2 - async (event) => { 2 + async event => { 3 3 const pkg = getRouterParam(event, 'pkg') 4 4 if (!pkg) { 5 5 throw createError({ statusCode: 400, message: 'Package name is required' }) ··· 17 17 18 18 try { 19 19 return await fetchNpmDownloads(packageName, period) 20 - } 21 - catch { 20 + } catch { 22 21 throw createError({ statusCode: 502, message: 'Failed to fetch download counts' }) 23 22 } 24 23 }, 25 24 { 26 25 maxAge: 60 * 60, 27 - getKey: (event) => { 26 + getKey: event => { 28 27 const pkg = getRouterParam(event, 'pkg') ?? '' 29 28 const query = getQuery(event) 30 29 return `${pkg}:${query.period ?? 'last-week'}`
+25 -12
server/api/registry/file/[...pkg].get.ts
··· 5 5 6 6 // Languages that benefit from import linking 7 7 const IMPORT_LANGUAGES = new Set([ 8 - 'javascript', 'typescript', 'jsx', 'tsx', 9 - 'vue', 'svelte', 'astro', 8 + 'javascript', 9 + 'typescript', 10 + 'jsx', 11 + 'tsx', 12 + 'vue', 13 + 'svelte', 14 + 'astro', 10 15 ]) 11 16 12 17 interface PackageJson { ··· 24 29 const url = `https://cdn.jsdelivr.net/npm/${packageName}@${version}/package.json` 25 30 const response = await fetch(url) 26 31 if (!response.ok) return null 27 - return await response.json() as PackageJson 28 - } 29 - catch { 32 + return (await response.json()) as PackageJson 33 + } catch { 30 34 return null 31 35 } 32 36 } ··· 34 38 /** 35 39 * Fetch file content from jsDelivr CDN. 36 40 */ 37 - async function fetchFileContent(packageName: string, version: string, filePath: string): Promise<string> { 41 + async function fetchFileContent( 42 + packageName: string, 43 + version: string, 44 + filePath: string, 45 + ): Promise<string> { 38 46 const url = `https://cdn.jsdelivr.net/npm/${packageName}@${version}/${filePath}` 39 47 const response = await fetch(url) 40 48 ··· 75 83 * - /api/registry/file/@scope/packageName/v/1.2.3/path/to/file.ts 76 84 */ 77 85 export default defineCachedEventHandler( 78 - async (event) => { 86 + async event => { 79 87 const segments = getRouterParam(event, 'pkg')?.split('/') ?? [] 80 88 if (segments.length === 0) { 81 - throw createError({ statusCode: 400, message: 'Package name, version, and file path are required' }) 89 + throw createError({ 90 + statusCode: 400, 91 + message: 'Package name, version, and file path are required', 92 + }) 82 93 } 83 94 84 95 // Parse: [pkg, 'v', version, ...filePath] or [@scope, pkg, 'v', version, ...filePath] ··· 97 108 const filePath = versionAndPath.slice(1).join('/') 98 109 99 110 if (!packageName || !version || !filePath) { 100 - throw createError({ statusCode: 400, message: 'Package name, version, and file path are required' }) 111 + throw createError({ 112 + statusCode: 400, 113 + message: 'Package name, version, and file path are required', 114 + }) 101 115 } 102 116 103 117 try { ··· 152 166 html, 153 167 lines: content.split('\n').length, 154 168 } 155 - } 156 - catch (error) { 169 + } catch (error) { 157 170 if (error && typeof error === 'object' && 'statusCode' in error) { 158 171 throw error 159 172 } ··· 162 175 }, 163 176 { 164 177 maxAge: 60 * 60, // Cache for 1 hour (files don't change for a given version) 165 - getKey: (event) => { 178 + getKey: event => { 166 179 const pkg = getRouterParam(event, 'pkg') ?? '' 167 180 return `file:v${CACHE_VERSION}:${pkg}` 168 181 },
+3 -4
server/api/registry/files/[...pkg].get.ts
··· 8 8 * - /api/registry/files/@scope/packageName/v/1.2.3 - scoped package 9 9 */ 10 10 export default defineCachedEventHandler( 11 - async (event) => { 11 + async event => { 12 12 const segments = getRouterParam(event, 'pkg')?.split('/') ?? [] 13 13 if (segments.length === 0) { 14 14 throw createError({ statusCode: 400, message: 'Package name and version are required' }) ··· 38 38 default: jsDelivrData.default ?? undefined, 39 39 tree, 40 40 } satisfies PackageFileTreeResponse 41 - } 42 - catch (error) { 41 + } catch (error) { 43 42 if (error && typeof error === 'object' && 'statusCode' in error) { 44 43 throw error 45 44 } ··· 48 47 }, 49 48 { 50 49 maxAge: 60 * 60, // Cache for 1 hour (files don't change for a given version) 51 - getKey: (event) => { 50 + getKey: event => { 52 51 const pkg = getRouterParam(event, 'pkg') ?? '' 53 52 return `files:${pkg}` 54 53 },
+11 -12
server/api/registry/readme/[...pkg].get.ts
··· 2 2 * Fetch README from jsdelivr CDN for a specific package version. 3 3 * Falls back through common README filenames. 4 4 */ 5 - async function fetchReadmeFromJsdelivr(packageName: string, version?: string): Promise<string | null> { 5 + async function fetchReadmeFromJsdelivr( 6 + packageName: string, 7 + version?: string, 8 + ): Promise<string | null> { 6 9 const filenames = ['README.md', 'readme.md', 'Readme.md', 'README', 'readme'] 7 10 const versionSuffix = version ? `@${version}` : '' 8 11 ··· 13 16 if (response.ok) { 14 17 return await response.text() 15 18 } 16 - } 17 - catch { 19 + } catch { 18 20 // Try next filename 19 21 } 20 22 } ··· 32 34 * - /api/registry/readme/@scope/packageName/v/1.2.3 - scoped package, specific version 33 35 */ 34 36 export default defineCachedEventHandler( 35 - async (event) => { 37 + async event => { 36 38 const segments = getRouterParam(event, 'pkg')?.split('/') ?? [] 37 39 if (segments.length === 0) { 38 40 throw createError({ statusCode: 400, message: 'Package name is required' }) ··· 47 49 if (vIndex !== -1 && vIndex < segments.length - 1) { 48 50 packageName = segments.slice(0, vIndex).join('/') 49 51 version = segments.slice(vIndex + 1).join('/') 50 - } 51 - else { 52 + } else { 52 53 packageName = segments.join('/') 53 54 } 54 55 ··· 67 68 if (versionData) { 68 69 readmeContent = versionData.readme 69 70 } 70 - } 71 - else { 71 + } else { 72 72 // Use the packument-level readme (from latest version) 73 73 readmeContent = packageData.readme 74 74 } 75 75 76 76 // If no README in packument, try fetching from jsdelivr (package tarball) 77 77 if (!readmeContent || readmeContent === 'ERROR: No README data found!') { 78 - readmeContent = await fetchReadmeFromJsdelivr(packageName, version) ?? undefined 78 + readmeContent = (await fetchReadmeFromJsdelivr(packageName, version)) ?? undefined 79 79 } 80 80 81 81 if (!readmeContent) { ··· 84 84 85 85 const html = await renderReadmeHtml(readmeContent, packageName) 86 86 return { html } 87 - } 88 - catch (error) { 87 + } catch (error) { 89 88 if (error && typeof error === 'object' && 'statusCode' in error) { 90 89 throw error 91 90 } ··· 94 93 }, 95 94 { 96 95 maxAge: 60 * 10, 97 - getKey: (event) => { 96 + getKey: event => { 98 97 const pkg = getRouterParam(event, 'pkg') ?? '' 99 98 return `readme:${pkg}` 100 99 },
+3 -4
server/api/registry/search.get.ts
··· 1 1 export default defineCachedEventHandler( 2 - async (event) => { 2 + async event => { 3 3 const query = getQuery(event) 4 4 const text = String(query.q ?? '') 5 5 const size = Math.min(Number(query.size) || 20, 250) ··· 11 11 12 12 try { 13 13 return await fetchNpmSearch(text, size, from) 14 - } 15 - catch { 14 + } catch { 16 15 throw createError({ statusCode: 502, message: 'Failed to search npm registry' }) 17 16 } 18 17 }, 19 18 { 20 19 maxAge: 60 * 2, 21 - getKey: (event) => { 20 + getKey: event => { 22 21 const query = getQuery(event) 23 22 return `${query.q}:${query.size}:${query.from}` 24 23 },
+26 -16
server/utils/code-highlight.ts
··· 231 231 232 232 // Languages that support import/export statements 233 233 const IMPORT_LANGUAGES = new Set([ 234 - 'javascript', 'typescript', 'jsx', 'tsx', 235 - 'vue', 'svelte', 'astro', 234 + 'javascript', 235 + 'typescript', 236 + 'jsx', 237 + 'tsx', 238 + 'vue', 239 + 'svelte', 240 + 'astro', 236 241 ]) 237 242 238 243 export interface HighlightOptions { ··· 246 251 * Highlight code using Shiki with line-by-line output for line highlighting. 247 252 * Each line is wrapped in a span.line for individual line highlighting. 248 253 */ 249 - export async function highlightCode(code: string, language: string, options?: HighlightOptions): Promise<string> { 254 + export async function highlightCode( 255 + code: string, 256 + language: string, 257 + options?: HighlightOptions, 258 + ): Promise<string> { 250 259 const shiki = await getShikiHighlighter() 251 260 const loadedLangs = shiki.getLoadedLanguages() 252 261 ··· 279 288 if (codeMatch?.[1]) { 280 289 const codeContent = codeMatch[1] 281 290 const lines = codeContent.split('\n') 282 - const wrappedLines = lines.map((line: string, i: number) => { 283 - if (i === lines.length - 1 && line === '') return null 284 - return `<span class="line">${line}</span>` 285 - }).filter((line: string | null): line is string => line !== null).join('') 291 + const wrappedLines = lines 292 + .map((line: string, i: number) => { 293 + if (i === lines.length - 1 && line === '') return null 294 + return `<span class="line">${line}</span>` 295 + }) 296 + .filter((line: string | null): line is string => line !== null) 297 + .join('') 286 298 287 299 return html.replace(codeMatch[1], wrappedLines) 288 300 } 289 301 290 302 return html 291 - } 292 - catch { 303 + } catch { 293 304 // Fall back to plain 294 305 } 295 306 } 296 307 297 308 // Plain code for unknown languages - also wrap lines 298 309 const lines = code.split('\n') 299 - const wrappedLines = lines.map((line) => { 300 - const escaped = line 301 - .replace(/&/g, '&amp;') 302 - .replace(/</g, '&lt;') 303 - .replace(/>/g, '&gt;') 304 - return `<span class="line">${escaped}</span>` 305 - }).join('') // No newlines - display:block handles it 310 + const wrappedLines = lines 311 + .map(line => { 312 + const escaped = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') 313 + return `<span class="line">${escaped}</span>` 314 + }) 315 + .join('') // No newlines - display:block handles it 306 316 307 317 return `<pre class="shiki github-dark"><code>${wrappedLines}</code></pre>` 308 318 }
+19 -6
server/utils/file-tree.ts
··· 1 - import type { JsDelivrPackageResponse, JsDelivrFileNode, PackageFileTree, PackageFileTreeResponse } from '#shared/types' 1 + import type { 2 + JsDelivrPackageResponse, 3 + JsDelivrFileNode, 4 + PackageFileTree, 5 + PackageFileTreeResponse, 6 + } from '#shared/types' 2 7 3 8 /** 4 9 * Fetch the file tree from jsDelivr API. 5 10 * Returns a nested tree structure of all files in the package. 6 11 */ 7 - export async function fetchFileTree(packageName: string, version: string): Promise<JsDelivrPackageResponse> { 12 + export async function fetchFileTree( 13 + packageName: string, 14 + version: string, 15 + ): Promise<JsDelivrPackageResponse> { 8 16 const url = `https://data.jsdelivr.com/v1/packages/npm/${packageName}@${version}` 9 17 const response = await fetch(url) 10 18 ··· 21 29 /** 22 30 * Convert jsDelivr nested structure to our PackageFileTree format 23 31 */ 24 - export function convertToFileTree(nodes: JsDelivrFileNode[], parentPath: string = ''): PackageFileTree[] { 32 + export function convertToFileTree( 33 + nodes: JsDelivrFileNode[], 34 + parentPath: string = '', 35 + ): PackageFileTree[] { 25 36 const result: PackageFileTree[] = [] 26 37 27 38 for (const node of nodes) { ··· 34 45 type: 'directory', 35 46 children: node.files ? convertToFileTree(node.files, path) : [], 36 47 }) 37 - } 38 - else { 48 + } else { 39 49 result.push({ 40 50 name: node.name, 41 51 path, ··· 60 70 * Fetch and convert file tree for a package version. 61 71 * Returns the full response including tree and metadata. 62 72 */ 63 - export async function getPackageFileTree(packageName: string, version: string): Promise<PackageFileTreeResponse> { 73 + export async function getPackageFileTree( 74 + packageName: string, 75 + version: string, 76 + ): Promise<PackageFileTreeResponse> { 64 77 const jsDelivrData = await fetchFileTree(packageName, version) 65 78 const tree = convertToFileTree(jsDelivrData.files) 66 79
+10 -53
server/utils/import-resolver.ts
··· 16 16 for (const node of nodes) { 17 17 if (node.type === 'file') { 18 18 files.add(node.path) 19 - } 20 - else if (node.children) { 19 + } else if (node.children) { 21 20 traverse(node.children) 22 21 } 23 22 } ··· 40 39 } 41 40 if (part === '..') { 42 41 result.pop() 43 - } 44 - else { 42 + } else { 45 43 result.push(part) 46 44 } 47 45 } ··· 77 75 78 76 // TypeScript files 79 77 if (ext === 'ts' || ext === 'tsx') { 80 - return [ 81 - [], 82 - ['.ts', '.tsx'], 83 - ['.d.ts'], 84 - ['.js', '.jsx'], 85 - ['.json'], 86 - ] 78 + return [[], ['.ts', '.tsx'], ['.d.ts'], ['.js', '.jsx'], ['.json']] 87 79 } 88 80 89 81 if (ext === 'mts') { 90 - return [ 91 - [], 92 - ['.mts'], 93 - ['.d.mts', '.d.ts'], 94 - ['.mjs', '.js'], 95 - ['.json'], 96 - ] 82 + return [[], ['.mts'], ['.d.mts', '.d.ts'], ['.mjs', '.js'], ['.json']] 97 83 } 98 84 99 85 if (ext === 'cts') { 100 - return [ 101 - [], 102 - ['.cts'], 103 - ['.d.cts', '.d.ts'], 104 - ['.cjs', '.js'], 105 - ['.json'], 106 - ] 86 + return [[], ['.cts'], ['.d.cts', '.d.ts'], ['.cjs', '.js'], ['.json']] 107 87 } 108 88 109 89 // JavaScript files 110 90 if (ext === 'js' || ext === 'jsx') { 111 - return [ 112 - [], 113 - ['.js', '.jsx'], 114 - ['.ts', '.tsx'], 115 - ['.json'], 116 - ] 91 + return [[], ['.js', '.jsx'], ['.ts', '.tsx'], ['.json']] 117 92 } 118 93 119 94 if (ext === 'mjs') { 120 - return [ 121 - [], 122 - ['.mjs'], 123 - ['.js'], 124 - ['.mts', '.ts'], 125 - ['.json'], 126 - ] 95 + return [[], ['.mjs'], ['.js'], ['.mts', '.ts'], ['.json']] 127 96 } 128 97 129 98 if (ext === 'cjs') { 130 - return [ 131 - [], 132 - ['.cjs'], 133 - ['.js'], 134 - ['.cts', '.ts'], 135 - ['.json'], 136 - ] 99 + return [[], ['.cjs'], ['.js'], ['.cts', '.ts'], ['.json']] 137 100 } 138 101 139 102 // Default for other files (vue, svelte, etc.) 140 - return [ 141 - [], 142 - ['.ts', '.js'], 143 - ['.d.ts'], 144 - ['.json'], 145 - ] 103 + return [[], ['.ts', '.js'], ['.d.ts'], ['.json']] 146 104 } 147 105 148 106 /** ··· 220 178 if (files.has(basePath)) { 221 179 return { path: basePath } 222 180 } 223 - } 224 - else { 181 + } else { 225 182 // Try with extensions 226 183 for (const ext of extensions) { 227 184 const pathWithExt = basePath + ext
+5 -4
server/utils/npm.ts
··· 57 57 */ 58 58 function constraintIncludesPrerelease(constraint: string): boolean { 59 59 // Look for prerelease identifiers in the constraint 60 - return /-(alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) 61 - || /-\d/.test(constraint) // e.g., -0, -1 60 + return ( 61 + /-(alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) || 62 + /-\d/.test(constraint) 63 + ) // e.g., -0, -1 62 64 } 63 65 64 66 /** ··· 82 84 } 83 85 84 86 return maxSatisfying(versions, constraint) 85 - } 86 - catch { 87 + } catch { 87 88 return null 88 89 } 89 90 }
+37 -17
server/utils/readme.ts
··· 5 5 // only allow h3-h6 since we shift README headings down by 2 levels 6 6 // (page h1 = package name, h2 = "Readme" section, so README h1 → h3) 7 7 const ALLOWED_TAGS = [ 8 - 'h3', 'h4', 'h5', 'h6', 9 - 'p', 'br', 'hr', 10 - 'ul', 'ol', 'li', 11 - 'blockquote', 'pre', 'code', 12 - 'a', 'strong', 'em', 'del', 's', 13 - 'table', 'thead', 'tbody', 'tr', 'th', 'td', 14 - 'img', 'picture', 'source', 15 - 'details', 'summary', 16 - 'div', 'span', 17 - 'sup', 'sub', 18 - 'kbd', 'mark', 8 + 'h3', 9 + 'h4', 10 + 'h5', 11 + 'h6', 12 + 'p', 13 + 'br', 14 + 'hr', 15 + 'ul', 16 + 'ol', 17 + 'li', 18 + 'blockquote', 19 + 'pre', 20 + 'code', 21 + 'a', 22 + 'strong', 23 + 'em', 24 + 'del', 25 + 's', 26 + 'table', 27 + 'thead', 28 + 'tbody', 29 + 'tr', 30 + 'th', 31 + 'td', 32 + 'img', 33 + 'picture', 34 + 'source', 35 + 'details', 36 + 'summary', 37 + 'div', 38 + 'span', 39 + 'sup', 40 + 'sub', 41 + 'kbd', 42 + 'mark', 19 43 ] 20 44 21 45 const ALLOWED_ATTR: Record<string, string[]> = { ··· 91 115 lang: language, 92 116 theme: 'github-dark', 93 117 }) 94 - } 95 - catch { 118 + } catch { 96 119 // Fall back to plain code block 97 120 } 98 121 } 99 122 100 123 // Plain code block for unknown languages 101 - const escaped = text 102 - .replace(/&/g, '&amp;') 103 - .replace(/</g, '&lt;') 104 - .replace(/>/g, '&gt;') 124 + const escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') 105 125 return `<pre><code class="language-${language}">${escaped}</code></pre>\n` 106 126 } 107 127
+3 -9
server/utils/shiki.ts
··· 6 6 export async function getShikiHighlighter(): Promise<HighlighterCore> { 7 7 if (!highlighter) { 8 8 highlighter = await createHighlighterCore({ 9 - themes: [ 10 - import('@shikijs/themes/github-dark'), 11 - ], 9 + themes: [import('@shikijs/themes/github-dark')], 12 10 langs: [ 13 11 // Core web languages 14 12 import('@shikijs/langs/javascript'), ··· 61 59 lang: language, 62 60 theme: 'github-dark', 63 61 }) 64 - } 65 - catch { 62 + } catch { 66 63 // Fall back to plain 67 64 } 68 65 } 69 66 70 67 // Plain code block for unknown languages 71 - const escaped = code 72 - .replace(/&/g, '&amp;') 73 - .replace(/</g, '&lt;') 74 - .replace(/>/g, '&gt;') 68 + const escaped = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') 75 69 return `<pre><code class="language-${language}">${escaped}</code></pre>\n` 76 70 }
+10 -4
shared/types/npm-registry.ts
··· 7 7 */ 8 8 9 9 // Re-export official npm types for packument/manifest 10 - export type { Packument, PackumentVersion, Manifest, ManifestVersion, PackageJSON } from '@npm/types' 10 + export type { 11 + Packument, 12 + PackumentVersion, 13 + Manifest, 14 + ManifestVersion, 15 + PackageJSON, 16 + } from '@npm/types' 11 17 12 18 /** 13 19 * Slimmed down Packument for client-side use. ··· 23 29 'description'?: string 24 30 'dist-tags': { latest?: string } & Record<string, string> 25 31 /** Only includes time for dist-tag versions + modified/created */ 26 - 'time': { modified?: string, created?: string } & Record<string, string> 32 + 'time': { modified?: string; created?: string } & Record<string, string> 27 33 'maintainers'?: NpmPerson[] 28 34 'author'?: NpmPerson 29 35 'license'?: string 30 36 'homepage'?: string 31 37 'keywords'?: string[] 32 - 'repository'?: { type?: string, url?: string, directory?: string } 33 - 'bugs'?: { url?: string, email?: string } 38 + 'repository'?: { type?: string; url?: string; directory?: string } 39 + 'bugs'?: { url?: string; email?: string } 34 40 /** Only includes dist-tag versions */ 35 41 'versions': Record<string, import('@npm/types').PackumentVersion> 36 42 }
+1 -1
test/unit/index.spec.ts
··· 1 1 import { describe, expect, it } from 'vitest' 2 2 3 3 describe('work', () => { 4 - it('should ', () => { 4 + it('should work', () => { 5 5 expect(true).toBe(true) 6 6 }) 7 7 })
+3 -1
tests/og-image.spec.ts
··· 10 10 expect(ogImageUrl).toBeTruthy() 11 11 12 12 const ogImagePath = new URL(ogImageUrl!).pathname 13 - const localUrl = baseURL?.endsWith('/') ? `${baseURL}${ogImagePath.slice(1)}` : `${baseURL}${ogImagePath}` 13 + const localUrl = baseURL?.endsWith('/') 14 + ? `${baseURL}${ogImagePath.slice(1)}` 15 + : `${baseURL}${ogImagePath}` 14 16 const response = await page.request.get(localUrl) 15 17 16 18 expect(response.status()).toBe(200)
+47 -21
uno.config.ts
··· 1 - import { defineConfig, presetIcons, presetWind4, transformerDirectives, transformerVariantGroup } from 'unocss' 1 + import { 2 + defineConfig, 3 + presetIcons, 4 + presetWind4, 5 + transformerDirectives, 6 + transformerVariantGroup, 7 + } from 'unocss' 2 8 import type { Theme } from '@unocss/preset-wind4/theme' 3 9 4 10 export default defineConfig({ ··· 8 14 scale: 1.2, 9 15 }), 10 16 ], 11 - transformers: [ 12 - transformerDirectives(), 13 - transformerVariantGroup(), 14 - ], 17 + transformers: [transformerDirectives(), transformerVariantGroup()], 15 18 theme: { 16 19 font: { 17 - mono: '\'Geist Mono\', monospace', 18 - sans: '\'Geist\', system-ui, -apple-system, sans-serif', 20 + mono: "'Geist Mono', monospace", 21 + sans: "'Geist', system-ui, -apple-system, sans-serif", 19 22 }, 20 23 colors: { 21 24 // Minimal black & white palette with subtle grays ··· 51 54 keyframes: { 52 55 'skeleton-pulse': '{0%, 100% { opacity: 0.4 } 50% { opacity: 0.7 }}', 53 56 'fade-in': '{from { opacity: 0 } to { opacity: 1 }}', 54 - 'slide-up': '{from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: translateY(0) }}', 55 - 'scale-in': '{from { opacity: 0; transform: scale(0.95) } to { opacity: 1; transform: scale(1) }}', 57 + 'slide-up': 58 + '{from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: translateY(0) }}', 59 + 'scale-in': 60 + '{from { opacity: 0; transform: scale(0.95) } to { opacity: 1; transform: scale(1) }}', 56 61 }, 57 62 durations: { 58 63 'skeleton-pulse': '2s', ··· 79 84 ['focus-ring', 'outline-none focus-visible:(ring-2 ring-fg/20 ring-offset-2 ring-offset-bg)'], 80 85 81 86 // Buttons 82 - ['btn', 'inline-flex items-center justify-center px-4 py-2 font-mono text-sm border border-border rounded-md bg-transparent text-fg transition-all duration-200 hover:(bg-fg hover:text-bg border-fg) focus-ring active:scale-98 disabled:(opacity-40 cursor-not-allowed hover:bg-transparent hover:text-fg)'], 83 - ['btn-ghost', 'inline-flex items-center justify-center px-3 py-1.5 font-mono text-sm text-fg-muted bg-transparent transition-all duration-200 hover:text-fg focus-ring'], 87 + [ 88 + 'btn', 89 + 'inline-flex items-center justify-center px-4 py-2 font-mono text-sm border border-border rounded-md bg-transparent text-fg transition-all duration-200 hover:(bg-fg hover:text-bg border-fg) focus-ring active:scale-98 disabled:(opacity-40 cursor-not-allowed hover:bg-transparent hover:text-fg)', 90 + ], 91 + [ 92 + 'btn-ghost', 93 + 'inline-flex items-center justify-center px-3 py-1.5 font-mono text-sm text-fg-muted bg-transparent transition-all duration-200 hover:text-fg focus-ring', 94 + ], 84 95 85 96 // Links 86 - ['link', 'text-fg underline-offset-4 decoration-border hover:(decoration-fg underline) transition-colors duration-200 focus-ring'], 97 + [ 98 + 'link', 99 + 'text-fg underline-offset-4 decoration-border hover:(decoration-fg underline) transition-colors duration-200 focus-ring', 100 + ], 87 101 ['link-subtle', 'text-fg-muted hover:text-fg transition-colors duration-200 focus-ring'], 88 102 89 103 // Cards ··· 91 105 ['card-interactive', 'card hover:(border-border-hover bg-bg-muted) cursor-pointer'], 92 106 93 107 // Form elements 94 - ['input-base', 'w-full bg-bg-subtle border border-border rounded-md px-4 py-3 font-mono text-sm text-fg placeholder:text-fg-subtle transition-all duration-200 focus:(border-fg/40 outline-none ring-1 ring-fg/10)'], 108 + [ 109 + 'input-base', 110 + 'w-full bg-bg-subtle border border-border rounded-md px-4 py-3 font-mono text-sm text-fg placeholder:text-fg-subtle transition-all duration-200 focus:(border-fg/40 outline-none ring-1 ring-fg/10)', 111 + ], 95 112 96 113 // Tags/badges 97 - ['tag', 'inline-flex items-center px-2 py-0.5 text-xs font-mono text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200 hover:(text-fg border-border-hover)'], 114 + [ 115 + 'tag', 116 + 'inline-flex items-center px-2 py-0.5 text-xs font-mono text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200 hover:(text-fg border-border-hover)', 117 + ], 98 118 99 119 // Code blocks 100 - ['code-block', 'bg-bg-muted border border-border rounded-md p-4 font-mono text-sm overflow-x-auto'], 120 + [ 121 + 'code-block', 122 + 'bg-bg-muted border border-border rounded-md p-4 font-mono text-sm overflow-x-auto', 123 + ], 101 124 102 125 // Skeleton loading 103 126 ['skeleton', 'bg-bg-elevated rounded animate-skeleton-pulse'], ··· 113 136 ['scale-98', { transform: 'scale(0.98)' }], 114 137 115 138 // Subtle text gradient for headings 116 - ['text-gradient', { 117 - 'background': 'linear-gradient(to right, #fafafa, #a1a1a1)', 118 - '-webkit-background-clip': 'text', 119 - '-webkit-text-fill-color': 'transparent', 120 - 'background-clip': 'text', 121 - }], 139 + [ 140 + 'text-gradient', 141 + { 142 + 'background': 'linear-gradient(to right, #fafafa, #a1a1a1)', 143 + '-webkit-background-clip': 'text', 144 + '-webkit-text-fill-color': 'transparent', 145 + 'background-clip': 'text', 146 + }, 147 + ], 122 148 123 149 // Ensures elements start in initial state during delay 124 150 ['animate-fill-both', { 'animation-fill-mode': 'both' }],
+1 -3
vitest.config.ts
··· 32 32 browser: { 33 33 enabled: true, 34 34 provider: playwright(), 35 - instances: [ 36 - { browser: 'chromium' }, 37 - ], 35 + instances: [{ browser: 'chromium' }], 38 36 }, 39 37 }, 40 38 }),