tangled
alpha
login
or
join now
davidwindham.com
/
npmx.dev
forked from
npmx.dev/npmx.dev
0
fork
atom
[READ-ONLY] a fast, modern browser for the npm registry
0
fork
atom
overview
issues
pulls
pipelines
chore: use oxlint + oxfmt
Daniel Roe
2 months ago
904138ea
23815611
+1472
-2845
67 changed files
expand all
collapse all
unified
split
.github
workflows
autofix.yml
.oxfmtrc.json
.oxlintrc.json
.vscode
extensions.json
README.md
app
app.vue
components
AppFooter.vue
AppHeader.vue
CodeDirectoryListing.vue
CodeFileTree.vue
CodeMobileTreeDrawer.vue
CodeViewer.vue
ConnectorModal.vue
ConnectorStatus.client.vue
LoadingSpinner.vue
OgImage
Default.vue
Package.vue
OperationsQueue.vue
OrgMembersPanel.vue
OrgTeamsPanel.vue
PackageAccessControls.vue
PackageCard.vue
PackageDependencies.vue
PackageList.vue
PackageMaintainers.vue
PackageSkeleton.vue
PackageVersions.vue
ProvenanceBadge.vue
UserCombobox.vue
composables
useConnector.ts
useNpmRegistry.ts
error.vue
pages
index.vue
org
[name].vue
package
[...name].vue
code
[...path].vue
search.vue
~[username].vue
plugins
vercel-analytics.client.ts
cli
src
cli.ts
npm-client.ts
server.ts
types.ts
tsconfig.json
eslint.config.mjs
modules
cache.ts
nuxt.config.ts
package.json
pnpm-lock.yaml
renovate.json
server
api
registry
[...pkg].get.ts
downloads
[...pkg].get.ts
file
[...pkg].get.ts
files
[...pkg].get.ts
readme
[...pkg].get.ts
search.get.ts
utils
code-highlight.ts
file-tree.ts
import-resolver.ts
npm.ts
readme.ts
shiki.ts
shared
types
npm-registry.ts
test
unit
index.spec.ts
tests
og-image.spec.ts
uno.config.ts
vitest.config.ts
+1
-1
.github/workflows/autofix.yml
···
19
19
- uses: actions/setup-node@v6
20
20
with:
21
21
node-version: lts/*
22
22
-
cache: "pnpm"
22
22
+
cache: 'pnpm'
23
23
24
24
- name: 📦 Install dependencies
25
25
run: pnpm install
+8
.oxfmtrc.json
···
1
1
+
{
2
2
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
3
3
+
"semi": false,
4
4
+
"singleQuote": true,
5
5
+
"arrowParens": "avoid",
6
6
+
"quoteProps": "consistent",
7
7
+
"experimentalSortPackageJson": false
8
8
+
}
+26
.oxlintrc.json
···
1
1
+
{
2
2
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
3
3
+
"plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"],
4
4
+
"categories": {
5
5
+
"correctness": "error",
6
6
+
"suspicious": "warn",
7
7
+
"perf": "warn"
8
8
+
},
9
9
+
"rules": {
10
10
+
"no-console": "warn",
11
11
+
"no-await-in-loop": "off",
12
12
+
"unicorn/no-array-sort": "off"
13
13
+
},
14
14
+
"ignorePatterns": [
15
15
+
".output/**",
16
16
+
".data/**",
17
17
+
".nuxt/**",
18
18
+
".nitro/**",
19
19
+
".cache/**",
20
20
+
"dist/**",
21
21
+
"node_modules/**",
22
22
+
"coverage/**",
23
23
+
"playwright-report/**",
24
24
+
"test-results/**"
25
25
+
]
26
26
+
}
+3
.vscode/extensions.json
···
1
1
+
{
2
2
+
"recommendations": ["oxc.oxc-vscode", "Vue.volar"]
3
3
+
}
+7
-7
README.md
···
31
31
32
32
npmx.dev supports npm permalink patterns:
33
33
34
34
-
| Pattern | Example |
35
35
-
|---------|---------|
36
36
-
| `/package/<name>` | [`/package/nuxt`](https://npmx.dev/package/nuxt) |
37
37
-
| `/package/@scope/name` | [`/package/@nuxt/kit`](https://npmx.dev/package/@nuxt/kit) |
34
34
+
| Pattern | Example |
35
35
+
| ----------------------------- | -------------------------------------------------------------- |
36
36
+
| `/package/<name>` | [`/package/nuxt`](https://npmx.dev/package/nuxt) |
37
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
39
-
| `/search?q=<query>` | [`/search?q=vue`](https://npmx.dev/search?q=vue) |
40
40
-
| `/~<username>` | [`/~sindresorhus`](https://npmx.dev/~sindresorhus) |
41
41
-
| `/org/<name>` | [`/org/nuxt`](https://npmx.dev/org/nuxt) |
39
39
+
| `/search?q=<query>` | [`/search?q=vue`](https://npmx.dev/search?q=vue) |
40
40
+
| `/~<username>` | [`/~sindresorhus`](https://npmx.dev/~sindresorhus) |
41
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
8
-
titleTemplate: (titleChunk) => {
8
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
17
-
if (
18
18
-
target.tagName === 'INPUT'
19
19
-
|| target.tagName === 'TEXTAREA'
20
20
-
|| target.isContentEditable
21
21
-
) {
17
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
35
-
}
36
36
-
else {
31
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
54
-
<a
55
55
-
href="#main-content"
56
56
-
class="skip-link font-mono"
57
57
-
>Skip to main content</a>
49
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
61
-
<div
62
62
-
id="main-content"
63
63
-
class="flex-1"
64
64
-
>
53
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
74
-
*, *::before, *::after {
63
63
+
*,
64
64
+
*::before,
65
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
97
-
transition: color 0.2s ease, text-decoration-color 0.2s ease;
88
88
+
transition:
89
89
+
color 0.2s ease,
90
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
193
-
.readme-content [data-level="1"] { font-size: 1.5rem; }
194
194
-
.readme-content [data-level="2"] { font-size: 1.25rem; padding-bottom: 0.5rem; border-bottom: 1px solid #262626; }
195
195
-
.readme-content [data-level="3"] { font-size: 1.125rem; }
196
196
-
.readme-content [data-level="4"] { font-size: 1rem; }
197
197
-
.readme-content [data-level="5"] { font-size: 0.925rem; }
198
198
-
.readme-content [data-level="6"] { font-size: 0.875rem; }
186
186
+
.readme-content [data-level='1'] {
187
187
+
font-size: 1.5rem;
188
188
+
}
189
189
+
.readme-content [data-level='2'] {
190
190
+
font-size: 1.25rem;
191
191
+
padding-bottom: 0.5rem;
192
192
+
border-bottom: 1px solid #262626;
193
193
+
}
194
194
+
.readme-content [data-level='3'] {
195
195
+
font-size: 1.125rem;
196
196
+
}
197
197
+
.readme-content [data-level='4'] {
198
198
+
font-size: 1rem;
199
199
+
}
200
200
+
.readme-content [data-level='5'] {
201
201
+
font-size: 0.925rem;
202
202
+
}
203
203
+
.readme-content [data-level='6'] {
204
204
+
font-size: 0.875rem;
205
205
+
}
199
206
200
207
.readme-content p {
201
208
margin-bottom: 1rem;
···
310
317
}
311
318
312
319
/* Note - blue */
313
313
-
.readme-content blockquote[data-callout="note"] {
320
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
317
-
.readme-content blockquote[data-callout="note"]::before {
318
318
-
content: "Note";
324
324
+
.readme-content blockquote[data-callout='note']::before {
325
325
+
content: 'Note';
319
326
color: #3b82f6;
320
327
}
321
328
322
329
/* Tip - green */
323
323
-
.readme-content blockquote[data-callout="tip"] {
330
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
327
-
.readme-content blockquote[data-callout="tip"]::before {
328
328
-
content: "Tip";
334
334
+
.readme-content blockquote[data-callout='tip']::before {
335
335
+
content: 'Tip';
329
336
color: #22c55e;
330
337
}
331
338
332
339
/* Important - purple */
333
333
-
.readme-content blockquote[data-callout="important"] {
340
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
337
-
.readme-content blockquote[data-callout="important"]::before {
338
338
-
content: "Important";
344
344
+
.readme-content blockquote[data-callout='important']::before {
345
345
+
content: 'Important';
339
346
color: #a855f7;
340
347
}
341
348
342
349
/* Warning - yellow/orange */
343
343
-
.readme-content blockquote[data-callout="warning"] {
350
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
347
-
.readme-content blockquote[data-callout="warning"]::before {
348
348
-
content: "Warning";
354
354
+
.readme-content blockquote[data-callout='warning']::before {
355
355
+
content: 'Warning';
349
356
color: #eab308;
350
357
}
351
358
352
359
/* Caution - red */
353
353
-
.readme-content blockquote[data-callout="caution"] {
360
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
357
-
.readme-content blockquote[data-callout="caution"]::before {
358
358
-
content: "Caution";
364
364
+
.readme-content blockquote[data-callout='caution']::before {
365
365
+
content: 'Caution';
359
366
color: #ef4444;
360
367
}
361
368
···
424
431
}
425
432
426
433
/* Safari search input fixes */
427
427
-
input[type="search"] {
434
434
+
input[type='search'] {
428
435
-webkit-appearance: none;
429
436
appearance: none;
430
437
}
431
438
432
432
-
input[type="search"]::-webkit-search-decoration,
433
433
-
input[type="search"]::-webkit-search-cancel-button,
434
434
-
input[type="search"]::-webkit-search-results-button,
435
435
-
input[type="search"]::-webkit-search-results-decoration {
439
439
+
input[type='search']::-webkit-search-decoration,
440
440
+
input[type='search']::-webkit-search-cancel-button,
441
441
+
input[type='search']::-webkit-search-results-button,
442
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
5
-
<p class="font-mono m-0">
6
6
-
a better browser for the npm registry
7
7
-
</p>
5
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
17
-
<a
18
18
-
href="https://roe.dev"
19
19
-
rel="noopener noreferrer"
20
20
-
class="link-subtle font-mono text-xs"
21
21
-
>
15
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
2
-
withDefaults(defineProps<{
3
3
-
showLogo?: boolean
4
4
-
showConnector?: boolean
5
5
-
}>(), {
6
6
-
showLogo: true,
7
7
-
showConnector: true,
8
8
-
})
2
2
+
withDefaults(
3
3
+
defineProps<{
4
4
+
showLogo?: boolean
5
5
+
showConnector?: boolean
6
6
+
}>(),
7
7
+
{
8
8
+
showLogo: true,
9
9
+
showConnector: true,
10
10
+
},
11
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
13
-
<nav
14
14
-
aria-label="Main navigation"
15
15
-
class="container h-14 flex items-center justify-between"
16
16
-
>
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
23
-
<span class="text-fg-subtle"><span style="letter-spacing: -0.2em;">.</span>/</span>npmx
23
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
26
-
<span
27
27
-
v-else
28
28
-
class="w-1"
29
29
-
/>
26
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
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
35
+
<kbd
36
36
+
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
37
37
+
>/</kbd
38
38
+
>
39
39
</NuxtLink>
40
40
</li>
41
41
-
<li
42
42
-
v-if="showConnector"
43
43
-
class="flex"
44
44
-
>
41
41
+
<li v-if="showConnector" class="flex">
45
42
<ConnectorStatus />
46
43
</li>
47
47
-
<li
48
48
-
v-else
49
49
-
class="flex"
50
50
-
>
44
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
50
-
<div
51
51
-
v-if="currentContents.length === 0"
52
52
-
class="py-20 text-center text-fg-muted"
53
53
-
>
50
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
58
-
<table
59
59
-
v-else
60
60
-
class="w-full"
61
61
-
>
55
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
102
-
<span
103
103
-
v-else
104
104
-
class="w-4 h-4"
105
105
-
:class="getFileIcon(node.name)"
106
106
-
/>
96
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
25
-
watch(() => props.currentPath, (path) => {
26
26
-
if (!path) return
27
27
-
const parts = path.split('/')
28
28
-
for (let i = 1; i <= parts.length; i++) {
29
29
-
expandedDirs.value.add(parts.slice(0, i).join('/'))
30
30
-
}
31
31
-
}, { immediate: true })
25
25
+
watch(
26
26
+
() => props.currentPath,
27
27
+
path => {
28
28
+
if (!path) return
29
29
+
const parts = path.split('/')
30
30
+
for (let i = 1; i <= parts.length; i++) {
31
31
+
expandedDirs.value.add(parts.slice(0, i).join('/'))
32
32
+
}
33
33
+
},
34
34
+
{ immediate: true },
35
35
+
)
32
36
33
37
function toggleDir(path: string) {
34
38
if (expandedDirs.value.has(path)) {
35
39
expandedDirs.value.delete(path)
36
36
-
}
37
37
-
else {
40
40
+
} else {
38
41
expandedDirs.value.add(path)
39
42
}
40
43
}
···
45
48
</script>
46
49
47
50
<template>
48
48
-
<ul
49
49
-
class="list-none m-0 p-0"
50
50
-
:class="depth === 0 ? 'py-2' : ''"
51
51
-
>
52
52
-
<li
53
53
-
v-for="node in tree"
54
54
-
:key="node.path"
55
55
-
>
51
51
+
<ul class="list-none m-0 p-0" :class="depth === 0 ? 'py-2' : ''">
52
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
66
-
:class="[
67
67
-
isExpanded(node.path) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right',
68
68
-
]"
63
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
72
-
:class="isExpanded(node.path) ? 'i-carbon-folder-open text-yellow-500' : 'i-carbon-folder text-yellow-600'"
67
67
+
:class="
68
68
+
isExpanded(node.path)
69
69
+
? 'i-carbon-folder-open text-yellow-500'
70
70
+
: 'i-carbon-folder text-yellow-600'
71
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
93
-
<span
94
94
-
class="w-4 h-4 shrink-0"
95
95
-
:class="getFileIcon(node.name)"
96
96
-
/>
92
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
14
-
watch(() => route.fullPath, () => {
15
15
-
isOpen.value = false
16
16
-
})
14
14
+
watch(
15
15
+
() => route.fullPath,
16
16
+
() => {
17
17
+
isOpen.value = false
18
18
+
},
19
19
+
)
17
20
18
21
// Prevent body scroll when drawer is open
19
19
-
watch(isOpen, (open) => {
22
22
+
watch(isOpen, open => {
20
23
if (open) {
21
24
document.body.style.overflow = 'hidden'
22
22
-
}
23
23
-
else {
25
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
41
-
<span
42
42
-
class="w-5 h-5"
43
43
-
:class="isOpen ? 'i-carbon-close' : 'i-carbon-folder'"
44
44
-
/>
43
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
56
-
<div
57
57
-
v-if="isOpen"
58
58
-
class="md:hidden fixed inset-0 z-40 bg-black/50"
59
59
-
@click="isOpen = false"
60
60
-
/>
55
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
76
-
<div class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-between">
71
71
+
<div
72
72
+
class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-between"
73
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
86
-
<CodeFileTree
87
87
-
:tree="tree"
88
88
-
:current-path="currentPath"
89
89
-
:base-url="baseUrl"
90
90
-
/>
83
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
5
-
selectedLines: { start: number, end: number } | null
5
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
40
-
}
41
41
-
else {
40
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
85
-
<div
86
86
-
ref="codeRef"
87
87
-
class="code-lines"
88
88
-
v-html="html"
89
89
-
/>
84
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
4
-
const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } = useConnector()
4
4
+
const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } =
5
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
23
-
watch(open, (isOpen) => {
24
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
38
-
<div
39
39
-
v-if="open"
40
40
-
class="fixed inset-0 z-50 flex items-center justify-center p-4"
41
41
-
>
39
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
60
-
<h2
61
61
-
id="connector-modal-title"
62
62
-
class="font-mono text-lg font-medium"
63
63
-
>
58
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
72
-
<span
73
73
-
class="i-carbon-close block w-5 h-5"
74
74
-
aria-hidden="true"
75
75
-
/>
67
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
80
-
<div
81
81
-
v-if="isConnected"
82
82
-
class="space-y-4"
83
83
-
>
72
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
85
-
<span
86
86
-
class="w-3 h-3 rounded-full bg-green-500"
87
87
-
aria-hidden="true"
88
88
-
/>
74
74
+
<span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" />
89
75
<div>
90
90
-
<p class="font-mono text-sm text-fg">
91
91
-
Connected
92
92
-
</p>
93
93
-
<p
94
94
-
v-if="npmUser"
95
95
-
class="font-mono text-xs text-fg-muted"
96
96
-
>
76
76
+
<p class="font-mono text-sm text-fg">Connected</p>
77
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
105
-
<div
106
106
-
v-if="!hasOperations"
107
107
-
class="text-sm text-fg-muted"
108
108
-
>
109
109
-
You can now manage packages, organizations, and teams through the npmx.dev interface.
86
86
+
<div v-if="!hasOperations" class="text-sm text-fg-muted">
87
87
+
You can now manage packages, organizations, and teams through the npmx.dev
88
88
+
interface.
110
89
</div>
111
90
112
91
<button
···
119
98
</div>
120
99
121
100
<!-- Disconnected state -->
122
122
-
<form
123
123
-
v-else
124
124
-
class="space-y-4"
125
125
-
@submit.prevent="handleConnect"
126
126
-
>
101
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
136
-
<p class="text-sm text-fg-muted">
137
137
-
Then paste the token shown in your terminal:
138
138
-
</p>
111
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
157
-
>
130
130
+
/>
158
131
</div>
159
132
160
133
<details class="text-sm">
161
161
-
<summary class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200">
134
134
+
<summary
135
135
+
class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200"
136
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
179
-
>
154
154
+
/>
180
155
</div>
181
156
</details>
182
157
</div>
+3
-4
app/components/ConnectorStatus.client.vue
···
1
1
<script setup lang="ts">
2
2
-
const { isConnected, isConnecting, npmUser, error, activeOperations, hasPendingOperations } = useConnector()
2
2
+
const { isConnected, isConnecting, npmUser, error, activeOperations, hasPendingOperations } =
3
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
76
-
<ConnectorModal
77
77
-
v-model:open="showModal"
78
78
-
/>
77
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
9
-
<div
10
10
-
aria-busy="true"
11
11
-
class="flex items-center gap-3 text-fg-muted font-mono text-sm py-8"
12
12
-
>
9
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
2
-
<div
3
3
-
class="h-full w-full flex flex-col justify-center items-center bg-[#0a0a0a] text-[#fafafa]"
4
4
-
>
5
5
-
<h1
6
6
-
class="text-6xl font-medium tracking-tight"
7
7
-
style="font-family: 'Geist Mono'"
8
8
-
>
9
9
-
<span
10
10
-
class="text-[#666666]"
11
11
-
style="letter-spacing: -0.1em"
12
12
-
>./</span> npmx
2
2
+
<div class="h-full w-full flex flex-col justify-center items-center bg-[#0a0a0a] text-[#fafafa]">
3
3
+
<h1 class="text-6xl font-medium tracking-tight" style="font-family: 'Geist Mono'">
4
4
+
<span class="text-[#666666]" style="letter-spacing: -0.1em">./</span> npmx
13
5
</h1>
14
14
-
<h1 class="text-3xl font-medium tracking-tight">
15
15
-
a better browser for the npm registry
16
16
-
</h1>
6
6
+
<h1 class="text-3xl font-medium tracking-tight">a better browser for the npm registry</h1>
17
7
18
18
-
<p class="absolute bottom-12 text-lg text-[#404040]">
19
19
-
npmx.dev
20
20
-
</p>
8
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
2
-
withDefaults(defineProps<{
3
3
-
name: string
4
4
-
version: string
5
5
-
downloads?: string
6
6
-
license?: string
7
7
-
}>(), {
8
8
-
downloads: '',
9
9
-
license: '',
10
10
-
})
2
2
+
withDefaults(
3
3
+
defineProps<{
4
4
+
name: string
5
5
+
version: string
6
6
+
downloads?: string
7
7
+
license?: string
8
8
+
}>(),
9
9
+
{
10
10
+
downloads: '',
11
11
+
license: '',
12
12
+
},
13
13
+
)
11
14
</script>
12
15
13
16
<template>
···
25
28
<span v-if="license">{{ license }}</span>
26
29
</div>
27
30
28
28
-
<p class="absolute bottom-12 text-lg text-[#404040]">
29
29
-
npmx.dev
30
30
-
</p>
31
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
29
-
activeOperations.value.some((op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp),
29
29
+
activeOperations.value.some(
30
30
+
(op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp,
31
31
+
),
30
32
)
31
33
32
34
async function handleApproveAll() {
···
37
39
isExecuting.value = true
38
40
try {
39
41
await executeOperations(otp)
40
40
-
}
41
41
-
finally {
42
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
72
-
case 'pending': return 'bg-yellow-500'
73
73
-
case 'approved': return 'bg-blue-500'
74
74
-
case 'running': return 'bg-purple-500'
75
75
-
case 'completed': return 'bg-green-500'
76
76
-
case 'failed': return 'bg-red-500'
77
77
-
default: return 'bg-fg-subtle'
73
73
+
case 'pending':
74
74
+
return 'bg-yellow-500'
75
75
+
case 'approved':
76
76
+
return 'bg-blue-500'
77
77
+
case 'running':
78
78
+
return 'bg-purple-500'
79
79
+
case 'completed':
80
80
+
return 'bg-green-500'
81
81
+
case 'failed':
82
82
+
return 'bg-red-500'
83
83
+
default:
84
84
+
return 'bg-fg-subtle'
78
85
}
79
86
}
80
87
81
88
function getStatusIcon(status: string): string {
82
89
switch (status) {
83
83
-
case 'pending': return 'i-carbon-time'
84
84
-
case 'approved': return 'i-carbon-checkmark'
85
85
-
case 'running': return 'i-carbon-rotate'
86
86
-
case 'completed': return 'i-carbon-checkmark-filled'
87
87
-
case 'failed': return 'i-carbon-close-filled'
88
88
-
default: return 'i-carbon-help'
90
90
+
case 'pending':
91
91
+
return 'i-carbon-time'
92
92
+
case 'approved':
93
93
+
return 'i-carbon-checkmark'
94
94
+
case 'running':
95
95
+
return 'i-carbon-rotate'
96
96
+
case 'completed':
97
97
+
return 'i-carbon-checkmark-filled'
98
98
+
case 'failed':
99
99
+
return 'i-carbon-close-filled'
100
100
+
default:
101
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
94
-
watch(isExecuting, (executing) => {
107
107
+
watch(isExecuting, executing => {
95
108
if (executing) {
96
109
refreshInterval = setInterval(() => refreshState(), 1000)
97
97
-
}
98
98
-
else if (refreshInterval) {
110
110
+
} else if (refreshInterval) {
99
111
clearInterval(refreshInterval)
100
112
refreshInterval = null
101
113
}
···
109
121
</script>
110
122
111
123
<template>
112
112
-
<div
113
113
-
v-if="isConnected"
114
114
-
class="space-y-4"
115
115
-
>
124
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
120
-
<span
121
121
-
v-if="hasActiveOperations"
122
122
-
class="text-fg-muted"
123
123
-
>({{ activeOperations.length }})</span>
129
129
+
<span v-if="hasActiveOperations" class="text-fg-muted"
130
130
+
>({{ activeOperations.length }})</span
131
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
141
-
<span
142
142
-
class="i-carbon-renew block w-4 h-4"
143
143
-
aria-hidden="true"
144
144
-
/>
149
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
150
-
<div
151
151
-
v-if="!hasActiveOperations && !hasCompletedOperations"
152
152
-
class="py-8 text-center"
153
153
-
>
154
154
-
<p class="font-mono text-sm text-fg-subtle">
155
155
-
No operations queued
156
156
-
</p>
157
157
-
<p class="font-mono text-xs text-fg-subtle mt-1">
158
158
-
Add operations from package or org pages
159
159
-
</p>
155
155
+
<div v-if="!hasActiveOperations && !hasCompletedOperations" class="py-8 text-center">
156
156
+
<p class="font-mono text-sm text-fg-subtle">No operations queued</p>
157
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
163
-
<ul
164
164
-
v-if="hasActiveOperations"
165
165
-
class="space-y-2"
166
166
-
aria-label="Active operations"
167
167
-
>
161
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
205
-
<pre
206
206
-
v-if="op.result.stdout"
207
207
-
class="text-fg-muted whitespace-pre-wrap"
208
208
-
>{{ op.result.stdout }}</pre>
209
209
-
<pre
210
210
-
v-if="op.result.stderr"
211
211
-
class="text-red-400 whitespace-pre-wrap"
212
212
-
>{{ op.result.stderr }}</pre>
199
199
+
<pre v-if="op.result.stdout" class="text-fg-muted whitespace-pre-wrap">{{
200
200
+
op.result.stdout
201
201
+
}}</pre>
202
202
+
<pre v-if="op.result.stderr" class="text-red-400 whitespace-pre-wrap">{{
203
203
+
op.result.stderr
204
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
225
-
<span
226
226
-
class="i-carbon-checkmark block w-4 h-4"
227
227
-
aria-hidden="true"
228
228
-
/>
217
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
237
-
<span
238
238
-
class="i-carbon-close block w-4 h-4"
239
239
-
aria-hidden="true"
240
240
-
/>
226
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
253
-
<span
254
254
-
class="i-carbon-locked block w-4 h-4 text-amber-400 shrink-0"
255
255
-
aria-hidden="true"
256
256
-
/>
257
257
-
<span class="font-mono text-sm text-amber-400">
258
258
-
Enter OTP to continue
259
259
-
</span>
239
239
+
<span class="i-carbon-locked block w-4 h-4 text-amber-400 shrink-0" aria-hidden="true" />
240
240
+
<span class="font-mono text-sm text-amber-400"> Enter OTP to continue </span>
260
241
</div>
261
261
-
<form
262
262
-
class="flex items-center gap-2"
263
263
-
@submit.prevent="handleRetryWithOtp"
264
264
-
>
265
265
-
<label
266
266
-
for="otp-input"
267
267
-
class="sr-only"
268
268
-
>One-time password</label>
242
242
+
<form class="flex items-center gap-2" @submit.prevent="handleRetryWithOtp">
243
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
280
-
>
255
255
+
/>
281
256
<button
282
257
type="submit"
283
258
:disabled="!otpInput.trim() || isExecuting"
···
289
264
</div>
290
265
291
266
<!-- Action buttons -->
292
292
-
<div
293
293
-
v-if="hasActiveOperations"
294
294
-
class="flex items-center gap-2 pt-2"
295
295
-
>
267
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
316
-
<details
317
317
-
v-if="hasCompletedOperations"
318
318
-
class="mt-4 border-t border-border pt-4"
319
319
-
>
320
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
288
+
<details v-if="hasCompletedOperations" class="mt-4 border-t border-border pt-4">
289
289
+
<summary
290
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
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
327
-
<ul
328
328
-
class="mt-2 space-y-1"
329
329
-
aria-label="Completed operations log"
330
330
-
>
298
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
338
-
:class="op.status === 'completed' ? 'i-carbon-checkmark-filled text-green-500' : 'i-carbon-close-filled text-red-500'"
306
306
+
:class="
307
307
+
op.status === 'completed'
308
308
+
? 'i-carbon-checkmark-filled text-green-500'
309
309
+
: 'i-carbon-close-filled text-red-500'
310
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
348
-
>{{ op.result.stderr }}</pre>
320
320
+
>{{ op.result.stderr }}</pre
321
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
356
-
<span
357
357
-
class="i-carbon-close block w-3 h-3"
358
358
-
aria-hidden="true"
359
359
-
/>
329
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
95
-
}
96
96
-
else {
95
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
125
-
}
126
126
-
else {
124
124
+
} else {
127
125
error.value = connectorError.value || 'Failed to load members'
128
126
}
129
129
-
}
130
130
-
finally {
127
127
+
} finally {
131
128
isLoading.value = false
132
129
}
133
130
}
···
151
148
})
152
149
await Promise.all(teamPromises)
153
150
}
154
154
-
}
155
155
-
finally {
151
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
201
-
}
202
202
-
finally {
197
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
242
-
}
243
243
-
else {
237
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
252
-
case 'owner': return 'bg-purple-500/20 text-purple-400 border-purple-500/30'
253
253
-
case 'admin': return 'bg-blue-500/20 text-blue-400 border-blue-500/30'
254
254
-
default: return 'bg-fg-subtle/20 text-fg-muted border-border'
246
246
+
case 'owner':
247
247
+
return 'bg-purple-500/20 text-purple-400 border-purple-500/30'
248
248
+
case 'admin':
249
249
+
return 'bg-blue-500/20 text-blue-400 border-blue-500/30'
250
250
+
default:
251
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
264
-
watch(isConnected, (connected) => {
265
265
-
if (connected) {
266
266
-
loadMembers()
267
267
-
loadTeamMemberships()
268
268
-
}
269
269
-
}, { immediate: true })
261
261
+
watch(
262
262
+
isConnected,
263
263
+
connected => {
264
264
+
if (connected) {
265
265
+
loadMembers()
266
266
+
loadTeamMemberships()
267
267
+
}
268
268
+
},
269
269
+
{ immediate: true },
270
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
288
-
<h2
289
289
-
id="members-heading"
290
290
-
class="font-mono text-sm font-medium flex items-center gap-2"
291
291
-
>
292
292
-
<span
293
293
-
class="i-carbon-user-multiple w-4 h-4 text-fg-muted"
294
294
-
aria-hidden="true"
295
295
-
/>
289
289
+
<h2 id="members-heading" class="font-mono text-sm font-medium flex items-center gap-2">
290
290
+
<span class="i-carbon-user-multiple w-4 h-4 text-fg-muted" aria-hidden="true" />
296
291
Members
297
297
-
<span
298
298
-
v-if="memberList.length > 0"
299
299
-
class="text-fg-muted"
300
300
-
>({{ memberList.length }})</span>
292
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
307
-
@click="loadMembers(); loadTeamMemberships()"
299
299
+
@click="
300
300
+
loadMembers()
301
301
+
loadTeamMemberships()
302
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
324
-
<label
325
325
-
for="members-search"
326
326
-
class="sr-only"
327
327
-
>Filter members</label>
319
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
336
-
>
328
328
+
/>
337
329
</div>
338
338
-
<div
339
339
-
class="flex items-center gap-1"
340
340
-
role="group"
341
341
-
aria-label="Filter by role"
342
342
-
>
330
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
353
-
<span
354
354
-
v-if="role !== 'all'"
355
355
-
class="text-fg-subtle"
356
356
-
>({{ roleCounts[role] }})</span>
341
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
361
-
<label
362
362
-
for="team-filter"
363
363
-
class="sr-only"
364
364
-
>Filter by team</label>
346
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
371
-
<option :value="null">
372
372
-
all teams
373
373
-
</option>
374
374
-
<option
375
375
-
v-for="team in teamNames"
376
376
-
:key="team"
377
377
-
:value="team"
378
378
-
>
353
353
+
<option :value="null">all teams</option>
354
354
+
<option v-for="team in teamNames" :key="team" :value="team">
379
355
{{ team }}
380
356
</option>
381
357
</select>
382
358
</div>
383
383
-
<div
384
384
-
class="flex items-center gap-1 text-xs"
385
385
-
role="group"
386
386
-
aria-label="Sort by"
387
387
-
>
359
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
412
-
<div
413
413
-
v-if="isLoading && memberList.length === 0"
414
414
-
class="p-8 text-center"
415
415
-
>
384
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
420
-
<p class="font-mono text-sm text-fg-muted mt-2">
421
421
-
Loading members…
422
422
-
</p>
389
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
426
-
<div
427
427
-
v-else-if="error"
428
428
-
class="p-4 text-center"
429
429
-
role="alert"
430
430
-
>
393
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
444
-
<div
445
445
-
v-else-if="memberList.length === 0"
446
446
-
class="p-8 text-center"
447
447
-
>
448
448
-
<p class="font-mono text-sm text-fg-muted">
449
449
-
No members found
450
450
-
</p>
407
407
+
<div v-else-if="memberList.length === 0" class="p-8 text-center">
408
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
481
-
<label
482
482
-
:for="`role-${member.name}`"
483
483
-
class="sr-only"
484
484
-
>Change role for {{ member.name }}</label>
439
439
+
<label :for="`role-${member.name}`" class="sr-only"
440
440
+
>Change role for {{ member.name }}</label
441
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
490
-
@change="handleChangeRole(member.name, ($event.target as HTMLSelectElement).value as 'developer' | 'admin' | 'owner')"
447
447
+
@change="
448
448
+
handleChangeRole(
449
449
+
member.name,
450
450
+
($event.target as HTMLSelectElement).value as 'developer' | 'admin' | 'owner',
451
451
+
)
452
452
+
"
491
453
>
492
492
-
<option value="developer">
493
493
-
developer
494
494
-
</option>
495
495
-
<option value="admin">
496
496
-
admin
497
497
-
</option>
498
498
-
<option value="owner">
499
499
-
owner
500
500
-
</option>
454
454
+
<option value="developer">developer</option>
455
455
+
<option value="admin">admin</option>
456
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
509
-
<span
510
510
-
class="i-carbon-close block w-4 h-4"
511
511
-
aria-hidden="true"
512
512
-
/>
465
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
517
-
<div
518
518
-
v-if="member.teams.length > 0"
519
519
-
class="flex flex-wrap gap-1 pl-0"
520
520
-
>
470
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
536
-
<div
537
537
-
v-if="memberList.length > 0 && filteredMembers.length === 0"
538
538
-
class="p-4 text-center"
539
539
-
>
540
540
-
<p class="font-mono text-sm text-fg-muted">
541
541
-
No members match your filters
542
542
-
</p>
486
486
+
<div v-if="memberList.length > 0 && filteredMembers.length === 0" class="p-4 text-center">
487
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
548
-
<form
549
549
-
class="space-y-2"
550
550
-
@submit.prevent="handleAddMember"
551
551
-
>
552
552
-
<label
553
553
-
for="new-member-username"
554
554
-
class="sr-only"
555
555
-
>Username</label>
493
493
+
<form class="space-y-2" @submit.prevent="handleAddMember">
494
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
565
-
>
504
504
+
/>
566
505
<div class="flex items-center gap-2">
567
567
-
<label
568
568
-
for="new-member-role"
569
569
-
class="sr-only"
570
570
-
>Role</label>
506
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
577
-
<option value="developer">
578
578
-
developer
579
579
-
</option>
580
580
-
<option value="admin">
581
581
-
admin
582
582
-
</option>
583
583
-
<option value="owner">
584
584
-
owner
585
585
-
</option>
513
513
+
<option value="developer">developer</option>
514
514
+
<option value="admin">admin</option>
515
515
+
<option value="owner">owner</option>
586
516
</select>
587
517
<!-- Team selection -->
588
588
-
<label
589
589
-
for="new-member-team"
590
590
-
class="sr-only"
591
591
-
>Team</label>
518
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
598
-
<option value="">
599
599
-
no team
600
600
-
</option>
601
601
-
<option
602
602
-
v-for="team in teamNames"
603
603
-
:key="team"
604
604
-
:value="team"
605
605
-
>
525
525
+
<option value="">no team</option>
526
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
622
-
<span
623
623
-
class="i-carbon-close block w-4 h-4"
624
624
-
aria-hidden="true"
625
625
-
/>
543
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
60
-
}
61
61
-
else {
60
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
88
-
}
89
89
-
else {
87
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
96
-
}
97
97
-
finally {
94
94
+
} finally {
98
95
isLoadingTeams.value = false
99
96
}
100
97
}
···
111
108
if (result) {
112
109
teamUsers.value[teamName] = result
113
110
}
114
114
-
}
115
115
-
finally {
111
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
124
-
}
125
125
-
else {
120
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
154
-
}
155
155
-
finally {
149
149
+
} finally {
156
150
isCreatingTeam.value = false
157
151
}
158
152
}
···
212
206
213
207
newUserUsername.value = ''
214
208
showAddUserFor.value = null
215
215
-
}
216
216
-
finally {
209
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
238
-
}
239
239
-
else {
231
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
246
-
watch(isConnected, (connected) => {
247
247
-
if (connected) {
248
248
-
loadTeams()
249
249
-
}
250
250
-
}, { immediate: true })
238
238
+
watch(
239
239
+
isConnected,
240
240
+
connected => {
241
241
+
if (connected) {
242
242
+
loadTeams()
243
243
+
}
244
244
+
},
245
245
+
{ immediate: true },
246
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
268
-
<h2
269
269
-
id="teams-heading"
270
270
-
class="font-mono text-sm font-medium flex items-center gap-2"
271
271
-
>
272
272
-
<span
273
273
-
class="i-carbon-group w-4 h-4 text-fg-muted"
274
274
-
aria-hidden="true"
275
275
-
/>
264
264
+
<h2 id="teams-heading" class="font-mono text-sm font-medium flex items-center gap-2">
265
265
+
<span class="i-carbon-group w-4 h-4 text-fg-muted" aria-hidden="true" />
276
266
Teams
277
277
-
<span
278
278
-
v-if="teams.length > 0"
279
279
-
class="text-fg-muted"
280
280
-
>({{ teams.length }})</span>
267
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
304
-
<label
305
305
-
for="teams-search"
306
306
-
class="sr-only"
307
307
-
>Filter teams</label>
291
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
316
-
>
300
300
+
/>
317
301
</div>
318
318
-
<div
319
319
-
class="flex items-center gap-1 text-xs"
320
320
-
role="group"
321
321
-
aria-label="Sort by"
322
322
-
>
302
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
347
-
<div
348
348
-
v-if="isLoadingTeams && teams.length === 0"
349
349
-
class="p-8 text-center"
350
350
-
>
327
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
355
-
<p class="font-mono text-sm text-fg-muted mt-2">
356
356
-
Loading teams…
357
357
-
</p>
332
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
361
-
<div
362
362
-
v-else-if="error"
363
363
-
class="p-4 text-center"
364
364
-
role="alert"
365
365
-
>
336
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
379
-
<div
380
380
-
v-else-if="teams.length === 0"
381
381
-
class="p-8 text-center"
382
382
-
>
383
383
-
<p class="font-mono text-sm text-fg-muted">
384
384
-
No teams found
385
385
-
</p>
350
350
+
<div v-else-if="teams.length === 0" class="p-8 text-center">
351
351
+
<p class="font-mono text-sm text-fg-muted">No teams found</p>
386
352
</div>
387
353
388
354
<!-- Teams list -->
389
389
-
<ul
390
390
-
v-else
391
391
-
class="divide-y divide-border"
392
392
-
aria-label="Organization teams"
393
393
-
>
394
394
-
<li
395
395
-
v-for="teamName in filteredTeams"
396
396
-
:key="teamName"
397
397
-
class="bg-bg"
398
398
-
>
355
355
+
<ul v-else class="divide-y divide-border" aria-label="Organization teams">
356
356
+
<li v-for="teamName in filteredTeams" :key="teamName" class="bg-bg">
399
357
<!-- Team header -->
400
400
-
<div class="flex items-center justify-between p-3 hover:bg-bg-subtle transition-colors duration-200">
358
358
+
<div
359
359
+
class="flex items-center justify-between p-3 hover:bg-bg-subtle transition-colors duration-200"
360
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
417
-
<span
418
418
-
v-if="teamUsers[teamName]"
419
419
-
class="font-mono text-xs text-fg-subtle"
420
420
-
>
421
421
-
({{ teamUsers[teamName].length }} member{{ teamUsers[teamName].length === 1 ? '' : 's' }})
377
377
+
<span v-if="teamUsers[teamName]" class="font-mono text-xs text-fg-subtle">
378
378
+
({{ teamUsers[teamName].length }} member{{
379
379
+
teamUsers[teamName].length === 1 ? '' : 's'
380
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
435
-
<span
436
436
-
class="i-carbon-trash-can block w-4 h-4"
437
437
-
aria-hidden="true"
438
438
-
/>
394
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
471
-
<span
472
472
-
class="i-carbon-close block w-3.5 h-3.5"
473
473
-
aria-hidden="true"
474
474
-
/>
427
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
478
-
<p
479
479
-
v-else-if="!isLoadingUsers[teamName]"
480
480
-
class="font-mono text-xs text-fg-subtle py-1"
481
481
-
>
431
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
486
-
<div
487
487
-
v-if="showAddUserFor === teamName"
488
488
-
class="mt-2"
489
489
-
>
490
490
-
<form
491
491
-
class="flex items-center gap-2"
492
492
-
@submit.prevent="handleAddUser(teamName)"
493
493
-
>
494
494
-
<label
495
495
-
:for="`add-user-${teamName}`"
496
496
-
class="sr-only"
497
497
-
>Username to add to {{ teamName }}</label>
436
436
+
<div v-if="showAddUserFor === teamName" class="mt-2">
437
437
+
<form class="flex items-center gap-2" @submit.prevent="handleAddUser(teamName)">
438
438
+
<label :for="`add-user-${teamName}`" class="sr-only"
439
439
+
>Username to add to {{ teamName }}</label
440
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
507
-
>
450
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
521
-
<span
522
522
-
class="i-carbon-close block w-4 h-4"
523
523
-
aria-hidden="true"
524
524
-
/>
464
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
541
-
<div
542
542
-
v-if="teams.length > 0 && filteredTeams.length === 0"
543
543
-
class="p-4 text-center"
544
544
-
>
545
545
-
<p class="font-mono text-sm text-fg-muted">
546
546
-
No teams match "{{ searchQuery }}"
547
547
-
</p>
481
481
+
<div v-if="teams.length > 0 && filteredTeams.length === 0" class="p-4 text-center">
482
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
553
-
<form
554
554
-
class="flex items-center gap-2"
555
555
-
@submit.prevent="handleCreateTeam"
556
556
-
>
488
488
+
<form class="flex items-center gap-2" @submit.prevent="handleCreateTeam">
557
489
<div class="flex-1 flex items-center">
558
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
490
+
<span
491
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
492
+
>
559
493
{{ orgName }}:
560
494
</span>
561
561
-
<label
562
562
-
for="new-team-name"
563
563
-
class="sr-only"
564
564
-
>Team name</label>
495
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
574
-
>
505
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
589
-
<span
590
590
-
class="i-carbon-close block w-4 h-4"
591
591
-
aria-hidden="true"
592
592
-
/>
520
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
39
-
return Object.entries(collaborators.value).map(([name, perm]) => {
40
40
-
// Check if this looks like a team (org:team format) or user
41
41
-
const isTeam = name.includes(':')
42
42
-
return {
43
43
-
name,
44
44
-
permission: perm,
45
45
-
isTeam,
46
46
-
displayName: isTeam ? name.split(':')[1] : name,
47
47
-
}
48
48
-
}).sort((a, b) => {
49
49
-
// Teams first, then users
50
50
-
if (a.isTeam !== b.isTeam) return a.isTeam ? -1 : 1
51
51
-
return a.name.localeCompare(b.name)
52
52
-
})
39
39
+
return Object.entries(collaborators.value)
40
40
+
.map(([name, perm]) => {
41
41
+
// Check if this looks like a team (org:team format) or user
42
42
+
const isTeam = name.includes(':')
43
43
+
return {
44
44
+
name,
45
45
+
permission: perm,
46
46
+
isTeam,
47
47
+
displayName: isTeam ? name.split(':')[1] : name,
48
48
+
}
49
49
+
})
50
50
+
.sort((a, b) => {
51
51
+
// Teams first, then users
52
52
+
if (a.isTeam !== b.isTeam) return a.isTeam ? -1 : 1
53
53
+
return a.name.localeCompare(b.name)
54
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
66
-
}
67
67
-
else {
68
68
+
} else {
68
69
error.value = connectorError.value || 'Failed to load collaborators'
69
70
}
70
70
-
}
71
71
-
finally {
71
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
88
-
}
89
89
-
finally {
88
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
115
-
}
116
116
-
finally {
114
114
+
} finally {
117
115
isGranting.value = false
118
116
}
119
117
}
···
138
136
}
139
137
140
138
// Load on mount when connected
141
141
-
watch(isConnected, (connected) => {
142
142
-
if (connected && orgName.value) {
143
143
-
loadCollaborators()
144
144
-
loadTeams()
145
145
-
}
146
146
-
}, { immediate: true })
139
139
+
watch(
140
140
+
isConnected,
141
141
+
connected => {
142
142
+
if (connected && orgName.value) {
143
143
+
loadCollaborators()
144
144
+
loadTeams()
145
145
+
}
146
146
+
},
147
147
+
{ immediate: true },
148
148
+
)
147
149
148
150
// Reload when package changes
149
149
-
watch(() => props.packageName, () => {
150
150
-
if (isConnected.value && orgName.value) {
151
151
-
loadCollaborators()
152
152
-
loadTeams()
153
153
-
}
154
154
-
})
151
151
+
watch(
152
152
+
() => props.packageName,
153
153
+
() => {
154
154
+
if (isConnected.value && orgName.value) {
155
155
+
loadCollaborators()
156
156
+
loadTeams()
157
157
+
}
158
158
+
},
159
159
+
)
155
160
156
161
// Refresh data when operations complete
157
162
watch(lastExecutionTime, () => {
···
163
168
</script>
164
169
165
170
<template>
166
166
-
<section
167
167
-
v-if="isConnected && orgName"
168
168
-
aria-labelledby="access-heading"
169
169
-
>
171
171
+
<section v-if="isConnected && orgName" aria-labelledby="access-heading">
170
172
<div class="flex items-center justify-between mb-3">
171
171
-
<h2
172
172
-
id="access-heading"
173
173
-
class="text-xs text-fg-subtle uppercase tracking-wider"
174
174
-
>
173
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
193
-
<div
194
194
-
v-if="isLoadingCollaborators && collaboratorList.length === 0"
195
195
-
class="py-4 text-center"
196
196
-
>
192
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
204
-
<div
205
205
-
v-else-if="error"
206
206
-
class="text-xs text-red-400 mb-2"
207
207
-
role="alert"
208
208
-
>
200
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
213
-
<ul
214
214
-
v-if="collaboratorList.length > 0"
215
215
-
class="space-y-1 mb-3"
216
216
-
aria-label="Team access list"
217
217
-
>
205
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
239
-
:class="collab.permission === 'read-write'
240
240
-
? 'bg-green-500/20 text-green-400'
241
241
-
: 'bg-fg-subtle/20 text-fg-muted'"
227
227
+
:class="
228
228
+
collab.permission === 'read-write'
229
229
+
? 'bg-green-500/20 text-green-400'
230
230
+
: 'bg-fg-subtle/20 text-fg-muted'
231
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
254
-
<span
255
255
-
class="i-carbon-close block w-3.5 h-3.5"
256
256
-
aria-hidden="true"
257
257
-
/>
244
244
+
<span class="i-carbon-close block w-3.5 h-3.5" aria-hidden="true" />
258
245
</button>
259
259
-
<span
260
260
-
v-else
261
261
-
class="text-xs text-fg-subtle"
262
262
-
>
263
263
-
owner
264
264
-
</span>
246
246
+
<span v-else class="text-xs text-fg-subtle"> owner </span>
265
247
</li>
266
248
</ul>
267
249
268
268
-
<p
269
269
-
v-else-if="!isLoadingCollaborators && !error"
270
270
-
class="text-xs text-fg-subtle mb-3"
271
271
-
>
250
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
277
-
<form
278
278
-
class="space-y-2"
279
279
-
@submit.prevent="handleGrantAccess"
280
280
-
>
256
256
+
<form class="space-y-2" @submit.prevent="handleGrantAccess">
281
257
<div class="flex items-center gap-2">
282
282
-
<label
283
283
-
for="grant-team-select"
284
284
-
class="sr-only"
285
285
-
>Select team</label>
258
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
293
-
<option
294
294
-
value=""
295
295
-
disabled
296
296
-
>
266
266
+
<option value="" disabled>
297
267
{{ isLoadingTeams ? 'Loading teams…' : 'Select team' }}
298
268
</option>
299
299
-
<option
300
300
-
v-for="team in teams"
301
301
-
:key="team"
302
302
-
:value="team"
303
303
-
>
269
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
309
-
<label
310
310
-
for="grant-permission-select"
311
311
-
class="sr-only"
312
312
-
>Permission level</label>
275
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
319
-
<option value="read-only">
320
320
-
read-only
321
321
-
</option>
322
322
-
<option value="read-write">
323
323
-
read-write
324
324
-
</option>
282
282
+
<option value="read-only">read-only</option>
283
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
339
-
<span
340
340
-
class="i-carbon-close block w-4 h-4"
341
341
-
aria-hidden="true"
342
342
-
/>
298
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
38
-
<span
39
39
-
v-if="result.package.version"
40
40
-
class="font-mono text-xs text-fg-subtle"
41
41
-
>
38
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
54
-
<p
55
55
-
v-if="result.package.description"
56
56
-
class="text-fg-muted text-sm line-clamp-2 mb-3"
57
57
-
>
51
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
62
-
<dl
63
63
-
v-if="showPublisher || result.package.date"
64
64
-
class="flex items-center gap-4 m-0"
65
65
-
>
56
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
70
-
<dt class="sr-only">
71
71
-
Publisher
72
72
-
</dt>
73
73
-
<dd class="font-mono">
74
74
-
@{{ result.package.publisher.username }}
75
75
-
</dd>
61
61
+
<dt class="sr-only">Publisher</dt>
62
62
+
<dd class="font-mono">@{{ result.package.publisher.username }}</dd>
76
63
</div>
77
77
-
<div
78
78
-
v-if="result.package.date"
79
79
-
class="flex items-center gap-1.5"
80
80
-
>
81
81
-
<dt class="sr-only">
82
82
-
Updated
83
83
-
</dt>
64
64
+
<div v-if="result.package.date" class="flex items-center gap-1.5">
65
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
97
-
<li
98
98
-
v-for="keyword in result.package.keywords.slice(0, 5)"
99
99
-
:key="keyword"
100
100
-
>
79
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
16
-
return Object.entries(props.dependencies)
17
17
-
.sort(([a], [b]) => a.localeCompare(b))
16
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
52
-
<section
53
53
-
v-if="sortedDependencies.length > 0"
54
54
-
aria-labelledby="dependencies-heading"
55
55
-
>
51
51
+
<section v-if="sortedDependencies.length > 0" aria-labelledby="dependencies-heading">
56
52
<div class="flex items-center justify-between mb-3">
57
57
-
<h2
58
58
-
id="dependencies-heading"
59
59
-
class="text-xs text-fg-subtle uppercase tracking-wider"
60
60
-
>
53
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
71
-
<span class="text-xs uppercase tracking-wider">
72
72
-
Graph
73
73
-
</span>
64
64
+
<span class="text-xs uppercase tracking-wider"> Graph </span>
74
65
</a>
75
66
</div>
76
76
-
<ul
77
77
-
class="space-y-1 list-none m-0 p-0"
78
78
-
aria-label="Package dependencies"
79
79
-
>
67
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
111
-
<section
112
112
-
v-if="sortedPeerDependencies.length > 0"
113
113
-
aria-labelledby="peer-dependencies-heading"
114
114
-
>
99
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
121
-
<ul
122
122
-
class="space-y-1 list-none m-0 p-0"
123
123
-
aria-label="Package peer dependencies"
124
124
-
>
106
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
22
-
<PackageCard
23
23
-
:result="result"
24
24
-
:heading-level="headingLevel"
25
25
-
:show-publisher="showPublisher"
26
26
-
/>
22
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
6
-
maintainers?: Array<{ name?: string, email?: string }>
6
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
41
-
return props.maintainers.map((maintainer) => {
41
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
100
-
}
101
101
-
finally {
100
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
125
-
}
126
126
-
finally {
124
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
146
-
watch([isConnected, () => props.packageName], ([connected]) => {
147
147
-
if (connected && orgName.value) {
148
148
-
loadAccessInfo()
149
149
-
}
150
150
-
}, { immediate: true })
144
144
+
watch(
145
145
+
[isConnected, () => props.packageName],
146
146
+
([connected]) => {
147
147
+
if (connected && orgName.value) {
148
148
+
loadAccessInfo()
149
149
+
}
150
150
+
},
151
151
+
{ immediate: true },
152
152
+
)
151
153
152
154
// Refresh data when operations complete
153
155
watch(lastExecutionTime, () => {
···
158
160
</script>
159
161
160
162
<template>
161
161
-
<section
162
162
-
v-if="maintainers?.length"
163
163
-
aria-labelledby="maintainers-heading"
164
164
-
>
165
165
-
<h2
166
166
-
id="maintainers-heading"
167
167
-
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
168
168
-
>
163
163
+
<section v-if="maintainers?.length" aria-labelledby="maintainers-heading">
164
164
+
<h2 id="maintainers-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
169
165
Maintainers
170
166
</h2>
171
171
-
<ul
172
172
-
class="space-y-2 list-none m-0 p-0"
173
173
-
aria-label="Package maintainers"
174
174
-
>
167
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
188
-
<span
189
189
-
v-else
190
190
-
class="font-mono text-sm text-fg-muted"
191
191
-
>{{ maintainer.email }}</span>
181
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
203
-
>(you)</span>
193
193
+
>(you)</span
194
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
214
-
<span
215
215
-
class="i-carbon-close block w-3.5 h-3.5"
216
216
-
aria-hidden="true"
217
217
-
/>
205
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
223
-
<div
224
224
-
v-if="canManageOwners"
225
225
-
class="mt-3"
226
226
-
>
211
211
+
<div v-if="canManageOwners" class="mt-3">
227
212
<div v-if="showAddOwner">
228
228
-
<form
229
229
-
class="flex items-center gap-2"
230
230
-
@submit.prevent="handleAddOwner"
231
231
-
>
232
232
-
<label
233
233
-
for="add-owner-username"
234
234
-
class="sr-only"
235
235
-
>Username to add as owner</label>
213
213
+
<form class="flex items-center gap-2" @submit.prevent="handleAddOwner">
214
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
245
-
>
224
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
259
-
<span
260
260
-
class="i-carbon-close block w-4 h-4"
261
261
-
aria-hidden="true"
262
262
-
/>
238
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
2
-
<article
3
3
-
aria-busy="true"
4
4
-
aria-label="Loading package details"
5
5
-
class="animate-fade-in"
6
6
-
>
2
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
33
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
34
34
-
License
35
35
-
</dt>
29
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
43
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
44
44
-
Weekly
45
45
-
</dt>
37
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
53
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
54
54
-
Size
55
55
-
</dt>
45
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
63
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
64
64
-
Deps
65
65
-
</dt>
53
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
73
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
74
74
-
Updated
75
75
-
</dt>
61
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
83
-
<nav
84
84
-
aria-label="Package links"
85
85
-
class="mt-6"
86
86
-
>
69
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
105
-
<section
106
106
-
aria-labelledby="install-heading-skeleton"
107
107
-
class="mb-8"
108
108
-
>
88
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
27
-
function parseVersion(version: string): { major: number, minor: number, patch: number, prerelease: string } {
27
27
+
function parseVersion(version: string): {
28
28
+
major: number
29
29
+
minor: number
30
30
+
patch: number
31
31
+
prerelease: string
32
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
99
-
const otherMajorGroups = ref<Array<{ major: number, versions: VersionDisplay[], expanded: boolean }>>([])
104
104
+
const otherMajorGroups = ref<
105
105
+
Array<{ major: number; versions: VersionDisplay[]; expanded: boolean }>
106
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
118
-
await new Promise<void>((resolve) => {
119
119
-
const unwatch = watch(allVersionsCache, (val) => {
125
125
+
await new Promise<void>(resolve => {
126
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
151
-
}
152
152
-
finally {
158
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
172
-
.filter((v) => {
178
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
238
-
}
239
239
-
catch (error) {
244
244
+
} catch (error) {
240
245
console.error('Failed to load versions:', error)
241
241
-
}
242
242
-
finally {
246
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
264
-
}
265
265
-
catch (error) {
268
268
+
} catch (error) {
266
269
console.error('Failed to load versions:', error)
267
267
-
}
268
268
-
finally {
270
270
+
} finally {
269
271
otherVersionsLoading.value = false
270
272
}
271
273
}
···
296
298
</script>
297
299
298
300
<template>
299
299
-
<section
300
300
-
v-if="initialTagRows.length > 0"
301
301
-
aria-labelledby="versions-heading"
302
302
-
>
303
303
-
<h2
304
304
-
id="versions-heading"
305
305
-
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
306
306
-
>
301
301
+
<section v-if="initialTagRows.length > 0" aria-labelledby="versions-heading">
302
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
312
-
<div
313
313
-
v-for="row in initialTagRows"
314
314
-
:key="row.id"
315
315
-
>
308
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
326
-
<span
327
327
-
v-if="loadingTags.has(row.tag)"
328
328
-
class="i-carbon-rotate w-3 h-3 animate-spin"
329
329
-
/>
319
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
333
-
:class="expandedTags.has(row.tag) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'"
323
323
+
:class="
324
324
+
expandedTags.has(row.tag) ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right'
325
325
+
"
334
326
/>
335
327
</button>
336
336
-
<span
337
337
-
v-else
338
338
-
class="w-4"
339
339
-
/>
328
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
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
339
+
<span
340
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
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
397
-
<time
398
398
-
v-if="v.time"
399
399
-
:datetime="v.time"
400
400
-
class="text-[10px] text-fg-subtle"
401
401
-
>
388
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
423
-
<span class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg transition-colors">
424
424
-
<span
425
425
-
v-if="otherVersionsLoading"
426
426
-
class="i-carbon-rotate w-3 h-3 animate-spin"
427
427
-
/>
410
410
+
<span
411
411
+
class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg transition-colors"
412
412
+
>
413
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
434
-
<span class="text-xs text-fg-muted py-1.5">
435
435
-
Other versions
436
436
-
</span>
420
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
440
-
<div
441
441
-
v-if="otherVersionsExpanded"
442
442
-
class="ml-4 pl-2 border-l border-border space-y-0.5"
443
443
-
>
424
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
445
-
<div
446
446
-
v-for="(group, groupIndex) in otherMajorGroups"
447
447
-
:key="group.major"
448
448
-
>
426
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
472
-
<div
473
473
-
v-else
474
474
-
class="flex items-center gap-2 py-1"
475
475
-
>
450
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
492
-
<div
493
493
-
v-if="group.expanded && group.versions.length > 1"
494
494
-
class="ml-5 space-y-0.5"
495
495
-
>
467
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
516
-
<time
517
517
-
v-if="v.time"
518
518
-
:datetime="v.time"
519
519
-
class="text-[10px] text-fg-subtle"
520
520
-
>
488
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
534
-
<div
535
535
-
v-else-if="hasLoadedAll"
536
536
-
class="py-1 text-xs text-fg-subtle"
537
537
-
>
502
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
26
-
:title="provider ? `Verified: published via ${providerLabels[provider] ?? provider}` : 'Verified provenance'"
26
26
+
:title="
27
27
+
provider
28
28
+
? `Verified: published via ${providerLabels[provider] ?? provider}`
29
29
+
: 'Verified provenance'
30
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
32
-
<span
33
33
-
v-if="!compact"
34
34
-
class="sr-only sm:not-sr-only"
35
35
-
>verified</span>
36
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
40
-
:title="provider ? `Verified: published via ${providerLabels[provider] ?? provider}` : 'Verified provenance'"
41
41
+
:title="
42
42
+
provider
43
43
+
? `Verified: published via ${providerLabels[provider] ?? provider}`
44
44
+
: 'Verified provenance'
45
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
46
-
<span
47
47
-
v-if="!compact"
48
48
-
class="sr-only sm:not-sr-only"
49
49
-
>verified</span>
51
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
34
-
return props.suggestions
35
35
-
.filter(s => s.toLowerCase().includes(query))
36
36
-
.slice(0, 10)
34
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
85
-
const inSuggestions = props.suggestions.some(
86
86
-
s => s.toLowerCase() === username.toLowerCase(),
87
87
-
)
83
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
120
-
}
121
121
-
else {
116
116
+
} else {
122
117
handleSubmit()
123
118
}
124
119
break
···
131
126
}
132
127
133
128
// Scroll highlighted item into view
134
134
-
watch(highlightedIndex, (index) => {
129
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
150
-
<label
151
151
-
v-if="label"
152
152
-
:for="inputId"
153
153
-
class="sr-only"
154
154
-
>{{ label }}</label>
145
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
169
-
:aria-activedescendant="highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined"
160
160
+
:aria-activedescendant="
161
161
+
highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined
162
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
175
-
>
168
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
202
-
:class="highlightedIndex === index ? 'bg-bg-muted text-fg' : 'text-fg-muted hover:bg-bg-subtle hover:text-fg'"
195
195
+
:class="
196
196
+
highlightedIndex === index
197
197
+
? 'bg-bg-muted text-fg'
198
198
+
: 'text-fg-muted hover:bg-bg-subtle hover:text-fg'
199
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
57
-
const config = useState<{ token: string, port: number } | null>('connector-config', () => null)
57
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
96
-
}
97
97
-
catch {
96
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
125
-
}
126
126
-
else {
124
124
+
} else {
127
125
state.value.error = response.error ?? 'Connection failed'
128
126
return false
129
127
}
130
130
-
}
131
131
-
catch (err) {
128
128
+
} catch (err) {
132
129
const message = err instanceof Error ? err.message : 'Connection failed'
133
133
-
if (message.includes('fetch') || message.includes('network') || message.includes('ECONNREFUSED')) {
130
130
+
if (
131
131
+
message.includes('fetch') ||
132
132
+
message.includes('network') ||
133
133
+
message.includes('ECONNREFUSED')
134
134
+
) {
134
135
state.value.error = 'Could not reach connector. Is it running?'
135
135
-
}
136
136
-
else if (message.includes('401') || message.includes('Unauthorized')) {
136
136
+
} else if (message.includes('401') || message.includes('Unauthorized')) {
137
137
state.value.error = 'Invalid token'
138
138
-
}
139
139
-
else {
138
138
+
} else {
140
139
state.value.error = message
141
140
}
142
141
return false
143
143
-
}
144
144
-
finally {
142
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
183
-
}
184
184
-
catch {
181
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
193
-
options: { method?: 'GET' | 'POST' | 'DELETE', body?: Record<string, unknown> } = {},
190
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
207
-
}
208
208
-
catch (err) {
204
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
295
-
async function executeOperations(otp?: string): Promise<{ success: boolean, otpRequired?: boolean }> {
296
296
-
const response = await connectorFetch<ApiResponse<{ results: unknown[], otpRequired?: boolean }>>('/execute', {
291
291
+
async function executeOperations(
292
292
+
otp?: string,
293
293
+
): Promise<{ success: boolean; otpRequired?: boolean }> {
294
294
+
const response = await connectorFetch<
295
295
+
ApiResponse<{ results: unknown[]; otpRequired?: boolean }>
296
296
+
>('/execute', {
297
297
method: 'POST',
298
298
body: otp ? { otp } : undefined,
299
299
})
···
311
311
312
312
// Data fetching functions
313
313
314
314
-
async function listOrgUsers(org: string): Promise<Record<string, 'developer' | 'admin' | 'owner'> | null> {
315
315
-
const response = await connectorFetch<ApiResponse<Record<string, 'developer' | 'admin' | 'owner'>>>(`/org/${encodeURIComponent(org)}/users`)
316
316
-
return response?.success ? response.data ?? null : null
314
314
+
async function listOrgUsers(
315
315
+
org: string,
316
316
+
): Promise<Record<string, 'developer' | 'admin' | 'owner'> | null> {
317
317
+
const response = await connectorFetch<
318
318
+
ApiResponse<Record<string, 'developer' | 'admin' | 'owner'>>
319
319
+
>(`/org/${encodeURIComponent(org)}/users`)
320
320
+
return response?.success ? (response.data ?? null) : null
317
321
}
318
322
319
323
async function listOrgTeams(org: string): Promise<string[] | null> {
320
320
-
const response = await connectorFetch<ApiResponse<string[]>>(`/org/${encodeURIComponent(org)}/teams`)
321
321
-
return response?.success ? response.data ?? null : null
324
324
+
const response = await connectorFetch<ApiResponse<string[]>>(
325
325
+
`/org/${encodeURIComponent(org)}/teams`,
326
326
+
)
327
327
+
return response?.success ? (response.data ?? null) : null
322
328
}
323
329
324
330
async function listTeamUsers(scopeTeam: string): Promise<string[] | null> {
325
325
-
const response = await connectorFetch<ApiResponse<string[]>>(`/team/${encodeURIComponent(scopeTeam)}/users`)
326
326
-
return response?.success ? response.data ?? null : null
331
331
+
const response = await connectorFetch<ApiResponse<string[]>>(
332
332
+
`/team/${encodeURIComponent(scopeTeam)}/users`,
333
333
+
)
334
334
+
return response?.success ? (response.data ?? null) : null
327
335
}
328
336
329
329
-
async function listPackageCollaborators(pkg: string): Promise<Record<string, 'read-only' | 'read-write'> | null> {
330
330
-
const response = await connectorFetch<ApiResponse<Record<string, 'read-only' | 'read-write'>>>(`/package/${encodeURIComponent(pkg)}/collaborators`)
331
331
-
return response?.success ? response.data ?? null : null
337
337
+
async function listPackageCollaborators(
338
338
+
pkg: string,
339
339
+
): Promise<Record<string, 'read-only' | 'read-write'> | null> {
340
340
+
const response = await connectorFetch<ApiResponse<Record<string, 'read-only' | 'read-write'>>>(
341
341
+
`/package/${encodeURIComponent(pkg)}/collaborators`,
342
342
+
)
343
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
343
-
state.value.operations.filter(op =>
344
344
-
op.status === 'completed'
345
345
-
|| (op.status === 'failed' && !op.result?.requiresOtp),
355
355
+
state.value.operations.filter(
356
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
350
-
state.value.operations.filter(op =>
351
351
-
op.status === 'pending'
352
352
-
|| op.status === 'approved'
353
353
-
|| op.status === 'running'
354
354
-
|| (op.status === 'failed' && op.result?.requiresOtp),
361
361
+
state.value.operations.filter(
362
362
+
op =>
363
363
+
op.status === 'pending' ||
364
364
+
op.status === 'approved' ||
365
365
+
op.status === 'running' ||
366
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
123
-
export function usePackage(name: MaybeRefOrGetter<string>, requestedVersion?: MaybeRefOrGetter<string | null>) {
123
123
+
export function usePackage(
124
124
+
name: MaybeRefOrGetter<string>,
125
125
+
requestedVersion?: MaybeRefOrGetter<string | null>,
126
126
+
) {
124
127
return useLazyAsyncData(
125
128
() => `package:${toValue(name)}:${toValue(requestedVersion) ?? ''}`,
126
126
-
() => fetchNpmPackage(toValue(name)).then(r => transformPackument(r, toValue(requestedVersion))),
129
129
+
() =>
130
130
+
fetchNpmPackage(toValue(name)).then(r => transformPackument(r, toValue(requestedVersion))),
127
131
)
128
132
}
129
133
···
137
141
)
138
142
}
139
143
140
140
-
const emptySearchResponse = { objects: [], total: 0, time: new Date().toISOString() } satisfies NpmSearchResponse
144
144
+
const emptySearchResponse = {
145
145
+
objects: [],
146
146
+
total: 0,
147
147
+
time: new Date().toISOString(),
148
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
158
-
return lastSearch = await searchNpmPackages(q, toValue(options))
166
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
12
-
case 404: return 'Page not found'
13
13
-
case 500: return 'Internal server error'
14
14
-
case 503: return 'Service unavailable'
15
15
-
default: return 'Something went wrong'
12
12
+
case 404:
13
13
+
return 'Page not found'
14
14
+
case 500:
15
15
+
return 'Internal server error'
16
16
+
case 503:
17
17
+
return 'Service unavailable'
18
18
+
default:
19
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
15
-
description: 'A better browser for the npm registry. Search, browse, and explore packages with a modern interface.',
15
15
+
description:
16
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
24
-
<header class="min-h-[calc(100vh-12rem)] flex flex-col items-center justify-center text-center py-20">
25
25
+
<header
26
26
+
class="min-h-[calc(100vh-12rem)] flex flex-col items-center justify-center text-center py-20"
27
27
+
>
25
28
<!-- Animated title -->
26
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
27
-
<span class="text-fg-subtle"><span style="letter-spacing: -0.2em;">.</span>/</span>npmx
29
29
+
<h1
30
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
31
+
>
32
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
42
-
<form
43
43
-
role="search"
44
44
-
class="relative"
45
45
-
@submit.prevent="handleSearch"
46
46
-
>
47
47
-
<label
48
48
-
for="home-search"
49
49
-
class="sr-only"
50
50
-
>Search npm packages</label>
47
47
+
<form role="search" class="relative" @submit.prevent="handleSearch">
48
48
+
<label for="home-search" class="sr-only">Search npm packages</label>
51
49
52
50
<!-- Search input with glow effect on focus -->
53
53
-
<div
54
54
-
class="relative group"
55
55
-
:class="{ 'is-focused': isSearchFocused }"
56
56
-
>
51
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
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
58
+
<span
59
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
60
+
>
64
61
/
65
62
</span>
66
63
···
75
72
@input="handleSearch"
76
73
@focus="isSearchFocused = true"
77
74
@blur="isSearchFocused = false"
78
78
-
>
75
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
107
-
<span class="w-1 h-1 rounded-full bg-fg-subtle group-hover:bg-fg transition-colors duration-200" />
104
104
+
<span
105
105
+
class="w-1 h-1 rounded-full bg-fg-subtle group-hover:bg-fg transition-colors duration-200"
106
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
36
-
description: () => scopedPackages.value.length ? `${scopedPackages.value.length} packages` : 'npm organization',
36
36
+
description: () =>
37
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
50
-
<span class="text-2xl text-fg-subtle font-mono">{{ orgName.charAt(0).toUpperCase() }}</span>
51
51
+
<span class="text-2xl text-fg-subtle font-mono">{{
52
52
+
orgName.charAt(0).toUpperCase()
53
53
+
}}</span>
51
54
</div>
52
55
<div>
53
53
-
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
54
54
-
@{{ orgName }}
55
55
-
</h1>
56
56
-
<p
57
57
-
v-if="status === 'success'"
58
58
-
class="text-fg-muted text-sm mt-1"
59
59
-
>
56
56
+
<h1 class="font-mono text-2xl sm:text-3xl font-medium">@{{ orgName }}</h1>
57
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
81
-
<section
82
82
-
v-if="isConnected"
83
83
-
class="mb-8"
84
84
-
aria-label="Organization management"
85
85
-
>
79
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
91
-
:class="activeTab === 'members'
92
92
-
? 'bg-bg-subtle text-fg border border-border border-b-0'
93
93
-
: 'text-fg-muted hover:text-fg'"
85
85
+
:class="
86
86
+
activeTab === 'members'
87
87
+
? 'bg-bg-subtle text-fg border border-border border-b-0'
88
88
+
: 'text-fg-muted hover:text-fg'
89
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
101
-
:class="activeTab === 'teams'
102
102
-
? 'bg-bg-subtle text-fg border border-border border-b-0'
103
103
-
: 'text-fg-muted hover:text-fg'"
97
97
+
:class="
98
98
+
activeTab === 'teams'
99
99
+
? 'bg-bg-subtle text-fg border border-border border-b-0'
100
100
+
: 'text-fg-muted hover:text-fg'
101
101
+
"
104
102
@click="activeTab = 'teams'"
105
103
>
106
104
Teams
···
108
106
</div>
109
107
110
108
<!-- Tab content -->
111
111
-
<OrgMembersPanel
112
112
-
v-if="activeTab === 'members'"
113
113
-
:org-name="orgName"
114
114
-
/>
115
115
-
<OrgTeamsPanel
116
116
-
v-else
117
117
-
:org-name="orgName"
118
118
-
/>
109
109
+
<OrgMembersPanel v-if="activeTab === 'members'" :org-name="orgName" />
110
110
+
<OrgTeamsPanel v-else :org-name="orgName" />
119
111
</section>
120
112
</ClientOnly>
121
113
122
114
<!-- Loading state -->
123
123
-
<LoadingSpinner
124
124
-
v-if="status === 'pending'"
125
125
-
text="Loading packages..."
126
126
-
/>
115
115
+
<LoadingSpinner v-if="status === 'pending'" text="Loading packages..." />
127
116
128
117
<!-- Error state -->
129
129
-
<div
130
130
-
v-else-if="status === 'error'"
131
131
-
role="alert"
132
132
-
class="py-12 text-center"
133
133
-
>
118
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
137
-
<NuxtLink
138
138
-
to="/"
139
139
-
class="btn"
140
140
-
>
141
141
-
Go back home
142
142
-
</NuxtLink>
122
122
+
<NuxtLink to="/" class="btn"> Go back home </NuxtLink>
143
123
</div>
144
124
145
125
<!-- Empty state -->
146
146
-
<div
147
147
-
v-else-if="packageCount === 0"
148
148
-
class="py-12 text-center"
149
149
-
>
126
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
159
-
<section
160
160
-
v-else-if="scopedPackages.length > 0"
161
161
-
aria-label="Organization packages"
162
162
-
>
163
163
-
<h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4">
164
164
-
Packages
165
165
-
</h2>
136
136
+
<section v-else-if="scopedPackages.length > 0" aria-label="Organization packages">
137
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
14
-
const segments = Array.isArray(route.params.name)
15
15
-
? route.params.name
16
16
-
: [route.params.name ?? '']
14
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
49
-
const { data: readmeData } = useLazyFetch<{ html: string }>(() => {
50
50
-
const base = `/api/registry/readme/${packageName.value}`
51
51
-
const version = requestedVersion.value
52
52
-
return version ? `${base}/v/${version}` : base
53
53
-
}, { default: () => ({ html: '' }) })
47
47
+
const { data: readmeData } = useLazyFetch<{ html: string }>(
48
48
+
() => {
49
49
+
const base = `/api/registry/readme/${packageName.value}`
50
50
+
const version = requestedVersion.value
51
51
+
return version ? `${base}/v/${version}` : base
52
52
+
},
53
53
+
{ default: () => ({ html: '' }) },
54
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
148
-
type PackageManagerId = typeof packageManagers[number]['id']
149
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
160
-
watch(selectedPM, (value) => {
161
161
+
watch(selectedPM, value => {
161
162
localStorage.setItem('npmx-pm', value)
162
163
})
163
164
164
164
-
const currentPM = computed(() => packageManagers.find(p => p.id === selectedPM.value) || packageManagers[0])
165
165
+
const currentPM = computed(
166
166
+
() => packageManagers.find(p => p.id === selectedPM.value) || packageManagers[0],
167
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
188
-
setTimeout(() => copied.value = false, 2000)
191
191
+
setTimeout(() => (copied.value = false), 2000)
189
192
}
190
193
191
194
// Expandable description
···
204
207
}
205
208
}
206
209
207
207
-
watch(() => pkg.value?.description, () => {
208
208
-
descriptionExpanded.value = false
209
209
-
nextTick(checkDescriptionOverflow)
210
210
-
})
210
210
+
watch(
211
211
+
() => pkg.value?.description,
212
212
+
() => {
213
213
+
descriptionExpanded.value = false
214
214
+
nextTick(checkDescriptionOverflow)
215
215
+
},
216
216
+
)
211
217
212
218
onMounted(() => {
213
219
nextTick(checkDescriptionOverflow)
214
220
})
215
221
216
222
useSeoMeta({
217
217
-
title: () => pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx',
223
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
224
-
downloads: () => downloads.value ? formatNumber(downloads.value.downloads) : '',
230
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
233
-
<article
234
234
-
v-else-if="status === 'success' && pkg"
235
235
-
class="animate-fade-in"
236
236
-
>
239
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
247
-
>@{{ orgName }}</NuxtLink><span v-if="orgName">/</span>{{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }}
250
250
+
>@{{ orgName }}</NuxtLink
251
251
+
><span v-if="orgName">/</span
252
252
+
>{{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }}
248
253
</h1>
249
254
<a
250
255
v-if="displayVersion"
251
251
-
:href="hasProvenance(displayVersion) ? `https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance` : undefined"
256
256
+
:href="
257
257
+
hasProvenance(displayVersion)
258
258
+
? `https://www.npmjs.com/package/${pkg.name}/v/${displayVersion.version}#provenance`
259
259
+
: undefined
260
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
255
-
:class="hasProvenance(displayVersion) ? 'hover:border-border-hover cursor-pointer' : 'cursor-default'"
264
264
+
:class="
265
265
+
hasProvenance(displayVersion)
266
266
+
? 'hover:border-border-hover cursor-pointer'
267
267
+
: 'cursor-default'
268
268
+
"
256
269
:title="hasProvenance(displayVersion) ? 'Verified provenance' : undefined"
257
270
>
258
271
v{{ displayVersion.version }}
259
272
<span
260
260
-
v-if="requestedVersion && latestVersion && displayVersion.version !== latestVersion.version"
273
273
+
v-if="
274
274
+
requestedVersion &&
275
275
+
latestVersion &&
276
276
+
displayVersion.version !== latestVersion.version
277
277
+
"
261
278
class="text-fg-subtle"
262
262
-
>(not latest)</span>
279
279
+
>(not latest)</span
280
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
271
-
<div
272
272
-
ref="descriptionRef"
273
273
-
class="relative max-w-2xl min-h-[4.5rem]"
274
274
-
>
289
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
282
-
<p
283
283
-
v-else
284
284
-
class="text-fg-subtle text-base m-0 italic"
285
285
-
>
286
286
-
No description provided
287
287
-
</p>
297
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
306
-
<div
307
307
-
v-if="pkg.license"
308
308
-
class="space-y-1"
309
309
-
>
310
310
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
311
311
-
License
312
312
-
</dt>
316
316
+
<div v-if="pkg.license" class="space-y-1">
317
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
318
-
<div
319
319
-
v-if="downloads"
320
320
-
class="space-y-1"
321
321
-
>
322
322
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
323
323
-
Weekly
324
324
-
</dt>
323
323
+
<div v-if="downloads" class="space-y-1">
324
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
330
-
<div
331
331
-
v-if="displayVersion?.dist?.unpackedSize"
332
332
-
class="space-y-1"
333
333
-
>
334
334
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
335
335
-
Size
336
336
-
</dt>
330
330
+
<div v-if="displayVersion?.dist?.unpackedSize" class="space-y-1">
331
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
342
-
<div
343
343
-
v-if="getDependencyCount(displayVersion) > 0"
344
344
-
class="space-y-1"
345
345
-
>
346
346
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
347
347
-
Deps
348
348
-
</dt>
337
337
+
<div v-if="getDependencyCount(displayVersion) > 0" class="space-y-1">
338
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
354
-
<div
355
355
-
v-if="pkg.time?.modified"
356
356
-
class="space-y-1 col-span-2"
357
357
-
>
358
358
-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">
359
359
-
Updated
360
360
-
</dt>
344
344
+
<div v-if="pkg.time?.modified" class="space-y-1 col-span-2">
345
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
368
-
<nav
369
369
-
aria-label="Package links"
370
370
-
class="mt-6"
371
371
-
>
353
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
464
-
<section
465
465
-
aria-labelledby="install-heading"
466
466
-
class="mb-8"
467
467
-
>
446
446
+
<section aria-labelledby="install-heading" class="mb-8">
468
447
<div class="flex items-center justify-between mb-3">
469
469
-
<h2
470
470
-
id="install-heading"
471
471
-
class="text-xs text-fg-subtle uppercase tracking-wider"
472
472
-
>
448
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
488
-
:class="selectedPM === pm.id
489
489
-
? 'bg-bg-elevated text-fg'
490
490
-
: 'text-fg-subtle hover:text-fg-muted'"
464
464
+
:class="
465
465
+
selectedPM === pm.id
466
466
+
? 'bg-bg-elevated text-fg'
467
467
+
: 'text-fg-subtle hover:text-fg-muted'
468
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
518
-
<code class="font-mono text-sm"><ClientOnly><span class="text-fg">{{ selectedPMLabel }}</span> <span class="text-fg-muted">{{ selectedPMAction }}</span><span
519
519
-
v-if="selectedPM !== 'deno'"
520
520
-
class="text-fg-muted"
521
521
-
> {{ pkg.name }}</span><span
522
522
-
v-else
523
523
-
class="text-fg-muted"
524
524
-
>{{ pkg.name }}</span><span
525
525
-
v-if="requestedVersion"
526
526
-
class="text-fg-muted"
527
527
-
>@{{ requestedVersion }}</span><template #fallback><span class="text-fg">npm</span> <span class="text-fg-muted">install {{ pkg.name }}</span></template></ClientOnly></code>
496
496
+
<code class="font-mono text-sm"
497
497
+
><ClientOnly
498
498
+
><span class="text-fg">{{ selectedPMLabel }}</span>
499
499
+
<span class="text-fg-muted">{{ selectedPMAction }}</span
500
500
+
><span v-if="selectedPM !== 'deno'" class="text-fg-muted"
501
501
+
> {{ pkg.name }}</span
502
502
+
><span v-else class="text-fg-muted">{{ pkg.name }}</span
503
503
+
><span v-if="requestedVersion" class="text-fg-muted">@{{ requestedVersion }}</span
504
504
+
><template #fallback
505
505
+
><span class="text-fg">npm</span> <span class="text-fg-muted"
506
506
+
>install {{ pkg.name }}</span
507
507
+
></template
508
508
+
></ClientOnly
509
509
+
></code
510
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
544
-
<h2
545
545
-
id="readme-heading"
546
546
-
class="text-xs text-fg-subtle uppercase tracking-wider mb-4"
547
547
-
>
527
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
556
-
<p
557
557
-
v-else
558
558
-
class="text-fg-subtle italic"
559
559
-
>
536
536
+
<p v-else class="text-fg-subtle italic">
560
537
No README available.
561
561
-
<a
562
562
-
v-if="repositoryUrl"
563
563
-
:href="repositoryUrl"
564
564
-
rel="noopener noreferrer"
565
565
-
class="link"
566
566
-
>View on GitHub</a>
538
538
+
<a v-if="repositoryUrl" :href="repositoryUrl" rel="noopener noreferrer" class="link"
539
539
+
>View on GitHub</a
540
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
574
-
<PackageMaintainers
575
575
-
:package-name="pkg.name"
576
576
-
:maintainers="pkg.maintainers"
577
577
-
/>
548
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
585
-
<section
586
586
-
v-if="displayVersion?.keywords?.length"
587
587
-
aria-labelledby="keywords-heading"
588
588
-
>
589
589
-
<h2
590
590
-
id="keywords-heading"
591
591
-
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
592
592
-
>
556
556
+
<section v-if="displayVersion?.keywords?.length" aria-labelledby="keywords-heading">
557
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
596
-
<li
597
597
-
v-for="keyword in displayVersion.keywords.slice(0, 15)"
598
598
-
:key="keyword"
599
599
-
>
600
600
-
<NuxtLink
601
601
-
:to="`/search?q=keywords:${encodeURIComponent(keyword)}`"
602
602
-
class="tag"
603
603
-
>
561
561
+
<li v-for="keyword in displayVersion.keywords.slice(0, 15)" :key="keyword">
562
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
611
-
v-if="displayVersion?.engines && (displayVersion.engines.node || displayVersion.engines.npm)"
570
570
+
v-if="
571
571
+
displayVersion?.engines && (displayVersion.engines.node || displayVersion.engines.npm)
572
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
625
-
<dt class="text-fg-muted text-sm">
626
626
-
node
627
627
-
</dt>
586
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
632
-
<div
633
633
-
v-if="displayVersion.engines.npm"
634
634
-
class="flex items-center justify-between py-1"
635
635
-
>
636
636
-
<dt class="text-fg-muted text-sm">
637
637
-
npm
638
638
-
</dt>
591
591
+
<div v-if="displayVersion.engines.npm" class="flex items-center justify-between py-1">
592
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
668
-
<div
669
669
-
v-else-if="status === 'error'"
670
670
-
role="alert"
671
671
-
class="py-20 text-center"
672
672
-
>
673
673
-
<h1 class="font-mono text-2xl font-medium mb-4">
674
674
-
Package Not Found
675
675
-
</h1>
622
622
+
<div v-else-if="status === 'error'" role="alert" class="py-20 text-center">
623
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
679
-
<NuxtLink
680
680
-
to="/"
681
681
-
class="btn"
682
682
-
>
683
683
-
Go back home
684
684
-
</NuxtLink>
627
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
2
-
import type { PackageFileTree, PackageFileTreeResponse, PackageFileContentResponse } from '#shared/types'
2
2
+
import type {
3
3
+
PackageFileTree,
4
4
+
PackageFileTreeResponse,
5
5
+
PackageFileContentResponse,
6
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
13
-
const segments = Array.isArray(route.params.path)
14
14
-
? route.params.path
15
15
-
: [route.params.path ?? '']
17
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
52
-
const taggedList = Object.entries(distTags)
53
53
-
.map(([tag, ver]) => ({ version: ver, tag }))
54
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
147
-
watch(() => route.hash, (hash) => {
148
148
-
currentHash.value = hash
149
149
-
})
148
148
+
watch(
149
149
+
() => route.hash,
150
150
+
hash => {
151
151
+
currentHash.value = hash
152
152
+
},
153
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
187
-
const result: { name: string, path: string }[] = []
191
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
231
-
}
232
232
-
else {
235
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
277
-
<span
278
278
-
v-if="orgName"
279
279
-
class="text-fg-muted"
280
280
-
>@{{ orgName }}/</span>{{ orgName ? packageName.replace(`@${orgName}/`, '') : packageName }}
280
280
+
<span v-if="orgName" class="text-fg-muted">@{{ orgName }}/</span
281
281
+
>{{ orgName ? packageName.replace(`@${orgName}/`, '') : packageName }}
281
282
</NuxtLink>
282
283
<!-- Version selector -->
283
283
-
<div
284
284
-
v-if="version && availableVersions.length > 0"
285
285
-
class="relative"
286
286
-
>
284
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
292
-
<option
293
293
-
v-for="v in availableVersions"
294
294
-
:key="v.version"
295
295
-
:value="v.version"
296
296
-
>
290
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
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
294
+
<span
295
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
296
+
/>
301
297
</div>
302
298
<span
303
299
v-else-if="version"
···
321
317
>
322
318
root
323
319
</NuxtLink>
324
324
-
<template
325
325
-
v-for="(crumb, i) in breadcrumbs"
326
326
-
:key="crumb.path"
327
327
-
>
320
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
336
-
<span
337
337
-
v-else
338
338
-
class="text-fg"
339
339
-
>{{ crumb.name }}</span>
329
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
346
-
<div
347
347
-
v-if="!version"
348
348
-
class="container py-20 text-center"
349
349
-
>
350
350
-
<p class="text-fg-muted mb-4">
351
351
-
Version is required to browse code
352
352
-
</p>
353
353
-
<NuxtLink
354
354
-
:to="`/package/${packageName}`"
355
355
-
class="btn"
356
356
-
>
357
357
-
Go to package
358
358
-
</NuxtLink>
336
336
+
<div v-if="!version" class="container py-20 text-center">
337
337
+
<p class="text-fg-muted mb-4">Version is required to browse code</p>
338
338
+
<NuxtLink :to="`/package/${packageName}`" class="btn"> Go to package </NuxtLink>
359
339
</div>
360
340
361
341
<!-- Loading state -->
362
362
-
<div
363
363
-
v-else-if="treeStatus === 'pending'"
364
364
-
class="container py-20 text-center"
365
365
-
>
342
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
367
-
<p class="mt-4 text-fg-muted">
368
368
-
Loading file tree...
369
369
-
</p>
344
344
+
<p class="mt-4 text-fg-muted">Loading file tree...</p>
370
345
</div>
371
346
372
347
<!-- Error state -->
373
373
-
<div
374
374
-
v-else-if="treeStatus === 'error'"
375
375
-
class="container py-20 text-center"
376
376
-
role="alert"
377
377
-
>
378
378
-
<p class="text-fg-muted mb-4">
379
379
-
Failed to load files for this package version
380
380
-
</p>
381
381
-
<NuxtLink
382
382
-
:to="`/package/${packageName}${version ? `/v/${version}` : ''}`"
383
383
-
class="btn"
384
384
-
>
348
348
+
<div v-else-if="treeStatus === 'error'" class="container py-20 text-center" role="alert">
349
349
+
<p class="text-fg-muted mb-4">Failed to load files for this package version</p>
350
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
390
-
<div
391
391
-
v-else-if="fileTree"
392
392
-
class="flex-1 flex min-h-0"
393
393
-
>
356
356
+
<div v-else-if="fileTree" class="flex-1 flex min-h-0">
394
357
<!-- File tree sidebar -->
395
395
-
<aside class="w-64 lg:w-72 border-r border-border overflow-y-auto shrink-0 hidden md:block bg-bg-subtle">
358
358
+
<aside
359
359
+
class="w-64 lg:w-72 border-r border-border overflow-y-auto shrink-0 hidden md:block bg-bg-subtle"
360
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
407
-
<div class="sticky top-0 bg-bg border-b border-border px-4 py-2 flex items-center justify-between">
372
372
+
<div
373
373
+
class="sticky top-0 bg-bg border-b border-border px-4 py-2 flex items-center justify-between"
374
374
+
>
408
375
<div class="flex items-center gap-3 text-sm">
409
376
<span class="text-fg-muted">{{ fileContent.lines }} lines</span>
410
410
-
<span
411
411
-
v-if="currentNode?.size"
412
412
-
class="text-fg-subtle"
413
413
-
>{{ formatBytes(currentNode.size) }}</span>
377
377
+
<span v-if="currentNode?.size" class="text-fg-subtle">{{
378
378
+
formatBytes(currentNode.size)
379
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
443
-
<div
444
444
-
v-else-if="isViewingFile && isFileTooLarge"
445
445
-
class="py-20 text-center"
446
446
-
>
409
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
448
-
<p class="text-fg-muted mb-2">
449
449
-
File too large to preview
450
450
-
</p>
411
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
452
-
{{ formatBytes(currentNode?.size ?? 0) }} exceeds the 500KB limit for syntax highlighting
413
413
+
{{ formatBytes(currentNode?.size ?? 0) }} exceeds the 500KB limit for syntax
414
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
474
-
<div
475
475
-
v-for="n in 20"
476
476
-
:key="n"
477
477
-
class="px-3 h-6 flex items-center justify-end"
478
478
-
>
436
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
508
-
<div
509
509
-
v-else-if="filePath && fileStatus === 'error'"
510
510
-
class="py-20 text-center"
511
511
-
role="alert"
512
512
-
>
466
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
514
-
<p class="text-fg-muted mb-2">
515
515
-
Failed to load file
516
516
-
</p>
517
517
-
<p class="text-fg-subtle text-sm mb-4">
518
518
-
The file may be too large or unavailable
519
519
-
</p>
468
468
+
<p class="text-fg-muted mb-2">Failed to load file</p>
469
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
17
-
watch(inputValue, (value) => {
17
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
25
-
watch(() => route.query.q, (urlQuery) => {
26
26
-
const value = (urlQuery as string) ?? ''
27
27
-
if (inputValue.value !== value) {
28
28
-
inputValue.value = value
29
29
-
}
30
30
-
})
25
25
+
watch(
26
26
+
() => route.query.q,
27
27
+
urlQuery => {
28
28
+
const value = (urlQuery as string) ?? ''
29
29
+
if (inputValue.value !== value) {
30
30
+
inputValue.value = value
31
31
+
}
32
32
+
},
33
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
92
-
watch([results, status, () => needsScrollRestore.value], ([newResults, newStatus, shouldScroll]) => {
93
93
-
if (shouldScroll && newStatus === 'success' && newResults && newResults.objects.length > 0) {
94
94
-
needsScrollRestore.value = false
95
95
-
// Scroll to the first item of the target page
96
96
-
nextTick(() => {
97
97
-
const targetItemIndex = (initialPage.value - 1) * pageSize
98
98
-
const listItems = resultsListRef.value?.children
99
99
-
if (listItems && listItems[targetItemIndex]) {
100
100
-
listItems[targetItemIndex].scrollIntoView({
101
101
-
behavior: 'instant',
102
102
-
block: 'start',
103
103
-
})
104
104
-
}
105
105
-
})
106
106
-
}
107
107
-
})
95
95
+
watch(
96
96
+
[results, status, () => needsScrollRestore.value],
97
97
+
([newResults, newStatus, shouldScroll]) => {
98
98
+
if (shouldScroll && newStatus === 'success' && newResults && newResults.objects.length > 0) {
99
99
+
needsScrollRestore.value = false
100
100
+
// Scroll to the first item of the target page
101
101
+
nextTick(() => {
102
102
+
const targetItemIndex = (initialPage.value - 1) * pageSize
103
103
+
const listItems = resultsListRef.value?.children
104
104
+
if (listItems && listItems[targetItemIndex]) {
105
105
+
listItems[targetItemIndex].scrollIntoView({
106
106
+
behavior: 'instant',
107
107
+
block: 'start',
108
108
+
})
109
109
+
}
110
110
+
})
111
111
+
}
112
112
+
},
113
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
170
-
(entries) => {
176
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
190
-
title: () => query.value ? `Search: ${query.value} - npmx` : 'Search Packages - npmx',
196
196
+
title: () => (query.value ? `Search: ${query.value} - npmx` : 'Search Packages - npmx'),
191
197
})
192
198
193
199
defineOgImageComponent('Default', {
194
200
title: 'npmx',
195
195
-
description: () => query.value ? `Search results for "${query.value}"` : 'Search npm packages',
201
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
202
-
<h1 class="font-mono text-2xl sm:text-3xl font-medium mb-6">
203
203
-
search
204
204
-
</h1>
208
208
+
<h1 class="font-mono text-2xl sm:text-3xl font-medium mb-6">search</h1>
205
209
206
210
<search>
207
207
-
<form
208
208
-
role="search"
209
209
-
class="relative"
210
210
-
@submit.prevent
211
211
-
>
212
212
-
<label
213
213
-
for="search-input"
214
214
-
class="sr-only"
215
215
-
>Search npm packages</label>
211
211
+
<form role="search" class="relative" @submit.prevent>
212
212
+
<label for="search-input" class="sr-only">Search npm packages</label>
216
213
217
217
-
<div
218
218
-
class="relative group"
219
219
-
:class="{ 'is-focused': isSearchFocused }"
220
220
-
>
214
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
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
221
+
<span
222
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
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
241
-
>
237
237
+
/>
242
238
<!-- Hidden submit button for accessibility (form must have submit button per WCAG) -->
243
243
-
<button
244
244
-
type="submit"
245
245
-
class="sr-only"
246
246
-
>
247
247
-
Search
248
248
-
</button>
239
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
255
-
<section
256
256
-
v-if="query"
257
257
-
aria-label="Search results"
258
258
-
>
246
246
+
<section v-if="query" aria-label="Search results">
259
247
<!-- Initial loading (only after user interaction, not during view transition) -->
260
260
-
<LoadingSpinner
261
261
-
v-if="showSearching"
262
262
-
text="Searching..."
263
263
-
/>
248
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
272
-
<span
273
273
-
v-if="status === 'pending'"
274
274
-
class="text-fg-subtle"
275
275
-
>(updating...)</span>
257
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
283
-
No packages found for "<span class="text-fg">{{ query }}</span>"
265
265
+
No packages found for "<span class="text-fg">{{ query }}</span
266
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
297
-
<PackageCard
298
298
-
:result="result"
299
299
-
heading-level="h2"
300
300
-
show-publisher
301
301
-
/>
280
280
+
<PackageCard :result="result" heading-level="h2" show-publisher />
302
281
</li>
303
282
</ol>
304
283
305
284
<!-- Infinite scroll trigger -->
306
306
-
<div
307
307
-
ref="loadMoreTrigger"
308
308
-
class="py-8 flex items-center justify-center"
309
309
-
>
285
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
327
-
<section
328
328
-
v-else
329
329
-
class="py-20 text-center"
330
330
-
>
331
331
-
<p class="text-fg-subtle font-mono text-sm">
332
332
-
Start typing to search packages
333
333
-
</p>
303
303
+
<section v-else class="py-20 text-center">
304
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
26
-
description: () => results.value ? `${results.value.total} packages` : 'npm user profile',
26
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
40
-
<span class="text-2xl text-fg-subtle font-mono">{{ username.charAt(0).toUpperCase() }}</span>
40
40
+
<span class="text-2xl text-fg-subtle font-mono">{{
41
41
+
username.charAt(0).toUpperCase()
42
42
+
}}</span>
41
43
</div>
42
44
<div>
43
43
-
<h1 class="font-mono text-2xl sm:text-3xl font-medium">
44
44
-
@{{ username }}
45
45
-
</h1>
46
46
-
<p
47
47
-
v-if="results?.total"
48
48
-
class="text-fg-muted text-sm mt-1"
49
49
-
>
45
45
+
<h1 class="font-mono text-2xl sm:text-3xl font-medium">@{{ username }}</h1>
46
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
70
-
<LoadingSpinner
71
71
-
v-if="status === 'pending'"
72
72
-
text="Loading packages..."
73
73
-
/>
67
67
+
<LoadingSpinner v-if="status === 'pending'" text="Loading packages..." />
74
68
75
69
<!-- Error state -->
76
76
-
<div
77
77
-
v-else-if="status === 'error'"
78
78
-
role="alert"
79
79
-
class="py-12 text-center"
80
80
-
>
70
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
84
-
<NuxtLink
85
85
-
to="/"
86
86
-
class="btn"
87
87
-
>
88
88
-
Go back home
89
89
-
</NuxtLink>
74
74
+
<NuxtLink to="/" class="btn"> Go back home </NuxtLink>
90
75
</div>
91
76
92
77
<!-- Empty state -->
93
93
-
<div
94
94
-
v-else-if="results && results.total === 0"
95
95
-
class="py-12 text-center"
96
96
-
>
78
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
100
-
<p class="text-fg-subtle text-sm mt-2">
101
101
-
This user may not exist or has no public packages.
102
102
-
</p>
82
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
106
-
<section
107
107
-
v-else-if="results && sortedPackages.length > 0"
108
108
-
aria-label="User packages"
109
109
-
>
110
110
-
<h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-4">
111
111
-
Packages
112
112
-
</h2>
86
86
+
<section v-else-if="results && sortedPackages.length > 0" aria-label="User packages">
87
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
10
-
(window.vaq = window.vaq || []).push(params)
10
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
13
-
return new Promise((resolve) => {
13
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
19
-
child.on('close', (code) => {
19
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
56
-
options: { otp?: string, silent?: boolean } = {},
56
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
66
-
const displayCmd = options.otp
67
67
-
? ['npm', ...args, '--otp', '******'].join(' ')
68
68
-
: cmd.join(' ')
66
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
87
-
}
88
88
-
catch (error) {
89
89
-
const err = error as { stdout?: string, stderr?: string, code?: number }
85
85
+
} catch (error) {
86
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
97
-
}
98
98
-
else if (authFailure) {
94
94
+
} else if (authFailure) {
99
95
logError('Authentication required - please run "npm login" and restart the connector')
100
100
-
}
101
101
-
else {
96
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
145
-
export async function teamCreate(
146
146
-
scopeTeam: string,
147
147
-
otp?: string,
148
148
-
): Promise<NpmExecResult> {
140
140
+
export async function teamCreate(scopeTeam: string, otp?: string): Promise<NpmExecResult> {
149
141
return execNpm(['team', 'create', scopeTeam], { otp })
150
142
}
151
143
152
152
-
export async function teamDestroy(
153
153
-
scopeTeam: string,
154
154
-
otp?: string,
155
155
-
): Promise<NpmExecResult> {
144
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
192
-
export async function ownerAdd(
193
193
-
user: string,
194
194
-
pkg: string,
195
195
-
otp?: string,
196
196
-
): Promise<NpmExecResult> {
181
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
200
-
export async function ownerRemove(
201
201
-
user: string,
202
202
-
pkg: string,
203
203
-
otp?: string,
204
204
-
): Promise<NpmExecResult> {
185
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
5
-
import { createApp, createRouter, eventHandler, readBody, getQuery, createError, getHeader, setResponseHeaders, getRouterParam } from 'h3'
6
6
-
import type {
7
7
-
ConnectorState,
8
8
-
PendingOperation,
9
9
-
OperationType,
10
10
-
ApiResponse,
11
11
-
} from './types.ts'
5
5
+
import {
6
6
+
createApp,
7
7
+
createRouter,
8
8
+
eventHandler,
9
9
+
readBody,
10
10
+
getQuery,
11
11
+
createError,
12
12
+
getHeader,
13
13
+
setResponseHeaders,
14
14
+
getRouterParam,
15
15
+
} from 'h3'
16
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
38
-
}
39
39
-
catch {
43
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
93
-
eventHandler(async (event) => {
97
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
115
-
eventHandler((event) => {
119
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
133
-
eventHandler(async (event) => {
137
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
168
-
eventHandler(async (event) => {
172
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
206
-
eventHandler(async (event) => {
210
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
235
-
eventHandler(async (event) => {
239
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
255
-
eventHandler(async (event) => {
259
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
286
-
eventHandler(async (event) => {
290
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
297
-
const results: Array<{ id: string, result: NpmExecResult }> = []
301
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
306
-
const readyOps = approvedOps.filter((op) => {
310
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
332
-
const runningOps = readyOps.map(async (op) => {
336
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
340
-
}
341
341
-
else {
344
344
+
} else {
342
345
failedIds.add(op.id)
343
346
}
344
347
···
369
372
370
373
router.delete(
371
374
'/operations',
372
372
-
eventHandler(async (event) => {
375
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
399
-
eventHandler(async (event) => {
402
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
419
-
eventHandler(async (event) => {
422
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
444
-
}
445
445
-
catch {
447
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
456
-
eventHandler(async (event) => {
458
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
481
-
}
482
482
-
catch {
483
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
493
-
eventHandler(async (event) => {
494
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
521
-
}
522
522
-
catch {
522
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
533
-
eventHandler(async (event) => {
533
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
556
-
const collaborators = JSON.parse(result.stdout) as Record<string, 'read-only' | 'read-write'>
556
556
+
const collaborators = JSON.parse(result.stdout) as Record<
557
557
+
string,
558
558
+
'read-only' | 'read-write'
559
559
+
>
557
560
return {
558
561
success: true,
559
562
data: collaborators,
560
563
} as ApiResponse
561
561
-
}
562
562
-
catch {
564
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
575
-
async function executeOperation(
576
576
-
op: PendingOperation,
577
577
-
otp?: string,
578
578
-
): Promise<NpmExecResult> {
577
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
12
-
export type OperationType
13
13
-
= | 'org:add-user'
14
14
-
| 'org:rm-user'
15
15
-
| 'org:set-role'
16
16
-
| 'team:create'
17
17
-
| 'team:destroy'
18
18
-
| 'team:add-user'
19
19
-
| 'team:rm-user'
20
20
-
| 'access:grant'
21
21
-
| 'access:revoke'
22
22
-
| 'owner:add'
23
23
-
| 'owner:rm'
12
12
+
export type OperationType =
13
13
+
| 'org:add-user'
14
14
+
| 'org:rm-user'
15
15
+
| 'org:set-role'
16
16
+
| 'team:create'
17
17
+
| 'team:destroy'
18
18
+
| 'team:add-user'
19
19
+
| 'team:rm-user'
20
20
+
| 'access:grant'
21
21
+
| 'access:revoke'
22
22
+
| 'owner:add'
23
23
+
| 'owner:rm'
24
24
25
25
-
export type OperationStatus
26
26
-
= | 'pending'
27
27
-
| 'approved'
28
28
-
| 'running'
29
29
-
| 'completed'
30
30
-
| 'failed'
31
31
-
| 'cancelled'
25
25
+
export type OperationStatus =
26
26
+
| 'pending'
27
27
+
| 'approved'
28
28
+
| 'running'
29
29
+
| 'completed'
30
30
+
| 'failed'
31
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
13
-
"include": [
14
14
-
"src/**/*.ts"
15
15
-
],
16
16
-
"exclude": [
17
17
-
"node_modules",
18
18
-
"dist"
19
19
-
]
13
13
+
"include": ["src/**/*.ts"],
14
14
+
"exclude": ["node_modules", "dist"]
20
15
}
-6
eslint.config.mjs
···
1
1
-
// @ts-check
2
2
-
import withNuxt from './.nuxt/eslint.config.mjs'
3
3
-
4
4
-
export default withNuxt(
5
5
-
// Your custom configs here
6
6
-
)
+1
-1
modules/cache.ts
···
10
10
return
11
11
}
12
12
13
13
-
nuxt.hook('nitro:config', (nitroConfig) => {
13
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
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
69
-
},
70
70
-
},
71
71
-
72
72
-
eslint: {
73
73
-
config: {
74
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
14
-
"lint": "eslint .",
14
14
+
"lint": "oxlint && oxfmt --check .",
15
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
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
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
65
+
"oxfmt": "^0.26.0",
66
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
90
-
"*.{js,ts,mjs,cjs,json,.*rc}": [
91
91
-
"npx eslint --fix"
91
91
+
"*.{js,ts,mjs,cjs,vue}": [
92
92
+
"oxlint --fix"
93
93
+
],
94
94
+
"*.{js,ts,mjs,cjs,vue,json,md,html,css}": [
95
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
19
-
'@nuxt/eslint':
20
20
-
specifier: ^1.12.1
21
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
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
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
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
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
113
-
eslint:
114
114
-
specifier: 9.39.2
115
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
119
+
oxfmt:
120
120
+
specifier: ^0.26.0
121
121
+
version: 0.26.0
122
122
+
oxlint:
123
123
+
specifier: ^1.41.0
124
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
197
-
198
198
-
'@apidevtools/json-schema-ref-parser@14.2.1':
199
199
-
resolution: {integrity: sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==}
200
200
-
engines: {node: '>= 20'}
201
201
-
peerDependencies:
202
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
765
-
'@clack/core@0.5.0':
766
766
-
resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}
767
767
-
768
759
'@clack/core@1.0.0-alpha.7':
769
760
resolution: {integrity: sha512-3vdh6Ar09D14rVxJZIm3VQJkU+ZOKKT5I5cC0cOVazy70CNyYYjiwRj9unwalhESndgxx6bGc/m6Hhs4EKF5XQ==}
770
770
-
771
771
-
'@clack/prompts@0.11.0':
772
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
827
-
828
828
-
'@es-joy/jsdoccomment@0.78.0':
829
829
-
resolution: {integrity: sha512-rQkU5u8hNAq2NVRzHnIUUvR6arbO0b6AOlvpTNS48CkiKSn/xtNfOzBK23JE4SiW89DgvU7GtxLVgV4Vn2HBAw==}
830
830
-
engines: {node: '>=20.11.0'}
831
831
-
832
832
-
'@es-joy/resolve.exports@1.2.0':
833
833
-
resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==}
834
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
1158
-
'@eslint/compat@1.4.1':
1159
1159
-
resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==}
1160
1160
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1161
1161
-
peerDependencies:
1162
1162
-
eslint: ^8.40 || 9
1163
1163
-
peerDependenciesMeta:
1164
1164
-
eslint:
1165
1165
-
optional: true
1166
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
1174
-
1175
1175
-
'@eslint/config-inspector@1.4.2':
1176
1176
-
resolution: {integrity: sha512-Ay8QcvV/Tq6YDeoltwZDQsQTrcS5flPkOp4ylk1WdV7L2UGotINwjatjbAIEqBTmP3G0g3Ah8dnuHC8DsnKPYQ==}
1177
1177
-
hasBin: true
1178
1178
-
peerDependencies:
1179
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
1536
-
'@napi-rs/wasm-runtime@0.2.12':
1537
1537
-
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
1538
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
1585
-
'@nuxt/eslint-config@1.12.1':
1586
1586
-
resolution: {integrity: sha512-fsKKtIIvVwQ5OGE30lJEhzwXxXj40ol7vR6h3eTH8sSBVZLOdmPn2BHrhoOjHTDXpLPw1AZ/8GcQfJZ2o3gcHQ==}
1587
1587
-
peerDependencies:
1588
1588
-
eslint: ^9.0.0
1589
1589
-
eslint-plugin-format: '*'
1590
1590
-
peerDependenciesMeta:
1591
1591
-
eslint-plugin-format:
1592
1592
-
optional: true
1593
1593
-
1594
1594
-
'@nuxt/eslint-plugin@1.12.1':
1595
1595
-
resolution: {integrity: sha512-9EBWZTgJC2oclDIL53YG6paEoaTU2SDWVPybEQ0Pe2Bm/5YSbHd//6EGLvdGwAgN+xJQmEsPunUpd4Y+NX2OCQ==}
1596
1596
-
peerDependencies:
1597
1597
-
eslint: ^9.0.0
1598
1598
-
1599
1599
-
'@nuxt/eslint@1.12.1':
1600
1600
-
resolution: {integrity: sha512-weXMt09C2XsWo7mpkVciApTXXaNUYQ1IbvrURNtnhpJcvcb2WkQutIOc/+pIhTsmb2O3T1t23HL76+Ll+7bpFQ==}
1601
1601
-
peerDependencies:
1602
1602
-
eslint: ^9.0.0
1603
1603
-
eslint-webpack-plugin: ^4.1.0
1604
1604
-
vite-plugin-eslint2: ^5.0.0
1605
1605
-
peerDependenciesMeta:
1606
1606
-
eslint-webpack-plugin:
1607
1607
-
optional: true
1608
1608
-
vite-plugin-eslint2:
1609
1609
-
optional: true
1610
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
1698
-
resolution: {integrity: sha512-T7DnXM09HsMWu43qcXpeNW1KsH230b/OYlvpRD49RKocyv/6XjATiQw1zWeNnOVw4fPULIeMlrrL5TX3c1jGWg==, tarball: https://pkg.pr.new/@nuxt/test-utils@1499a48}
1634
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
2051
+
'@oxfmt/darwin-arm64@0.26.0':
2052
2052
+
resolution: {integrity: sha512-AAGc+8CffkiWeVgtWf4dPfQwHEE5c/j/8NWH7VGVxxJRCZFdmWcqCXprvL2H6qZFewvDLrFbuSPRCqYCpYGaTQ==}
2053
2053
+
cpu: [arm64]
2054
2054
+
os: [darwin]
2055
2055
+
2056
2056
+
'@oxfmt/darwin-x64@0.26.0':
2057
2057
+
resolution: {integrity: sha512-xFx5ijCTjw577wJvFlZEMmKDnp3HSCcbYdCsLRmC5i3TZZiDe9DEYh3P46uqhzj8BkEw1Vm1ZCWdl48aEYAzvQ==}
2058
2058
+
cpu: [x64]
2059
2059
+
os: [darwin]
2060
2060
+
2061
2061
+
'@oxfmt/linux-arm64-gnu@0.26.0':
2062
2062
+
resolution: {integrity: sha512-GubkQeQT5d3B/Jx/IiR7NMkSmXrCZcVI0BPh1i7mpFi8HgD1hQ/LbhiBKAMsMqs5bbugdQOgBEl8bOhe8JhW1g==}
2063
2063
+
cpu: [arm64]
2064
2064
+
os: [linux]
2065
2065
+
2066
2066
+
'@oxfmt/linux-arm64-musl@0.26.0':
2067
2067
+
resolution: {integrity: sha512-OEypUwK69bFPj+aa3/LYCnlIUPgoOLu//WNcriwpnWNmt47808Ht7RJSg+MNK8a7pSZHpXJ5/E6CRK/OTwFdaQ==}
2068
2068
+
cpu: [arm64]
2069
2069
+
os: [linux]
2070
2070
+
2071
2071
+
'@oxfmt/linux-x64-gnu@0.26.0':
2072
2072
+
resolution: {integrity: sha512-xO6iEW2bC6ZHyOTPmPWrg/nM6xgzyRPaS84rATy6F8d79wz69LdRdJ3l/PXlkqhi7XoxhvX4ExysA0Nf10ZZEQ==}
2073
2073
+
cpu: [x64]
2074
2074
+
os: [linux]
2075
2075
+
2076
2076
+
'@oxfmt/linux-x64-musl@0.26.0':
2077
2077
+
resolution: {integrity: sha512-Z3KuZFC+MIuAyFCXBHY71kCsdRq1ulbsbzTe71v+hrEv7zVBn6yzql+/AZcgfIaKzWO9OXNuz5WWLWDmVALwow==}
2078
2078
+
cpu: [x64]
2079
2079
+
os: [linux]
2080
2080
+
2081
2081
+
'@oxfmt/win32-arm64@0.26.0':
2082
2082
+
resolution: {integrity: sha512-3zRbqwVWK1mDhRhTknlQFpRFL9GhEB5GfU6U7wawnuEwpvi39q91kJ+SRJvJnhyPCARkjZBd1V8XnweN5IFd1g==}
2083
2083
+
cpu: [arm64]
2084
2084
+
os: [win32]
2085
2085
+
2086
2086
+
'@oxfmt/win32-x64@0.26.0':
2087
2087
+
resolution: {integrity: sha512-m8TfIljU22i9UEIkD+slGPifTFeaCwIUfxszN3E6ABWP1KQbtwSw9Ak0TdoikibvukF/dtbeyG3WW63jv9DnEg==}
2088
2088
+
cpu: [x64]
2089
2089
+
os: [win32]
2090
2090
+
2091
2091
+
'@oxlint/darwin-arm64@1.41.0':
2092
2092
+
resolution: {integrity: sha512-K0Bs0cNW11oWdSrKmrollKF44HMM2HKr4QidZQHMlhJcSX8pozxv0V5FLdqB4sddzCY0J9Wuuw+oRAfR8sdRwA==}
2093
2093
+
cpu: [arm64]
2094
2094
+
os: [darwin]
2095
2095
+
2096
2096
+
'@oxlint/darwin-x64@1.41.0':
2097
2097
+
resolution: {integrity: sha512-1LCCXCe9nN8LbrJ1QOGari2HqnxrZrveYKysWDIg8gFsQglIg00XF/8lRbA0kWHMdLgt4X0wfNYhhFz+c3XXLQ==}
2098
2098
+
cpu: [x64]
2099
2099
+
os: [darwin]
2100
2100
+
2101
2101
+
'@oxlint/linux-arm64-gnu@1.41.0':
2102
2102
+
resolution: {integrity: sha512-Fow7H84Bs8XxuaK1yfSEWBC8HI7rfEQB9eR2A0J61un1WgCas7jNrt1HbT6+p6KmUH2bhR+r/RDu/6JFAvvj4g==}
2103
2103
+
cpu: [arm64]
2104
2104
+
os: [linux]
2105
2105
+
2106
2106
+
'@oxlint/linux-arm64-musl@1.41.0':
2107
2107
+
resolution: {integrity: sha512-WoRRDNwgP5W3rjRh42Zdx8ferYnqpKoYCv2QQLenmdrLjRGYwAd52uywfkcS45mKEWHeY1RPwPkYCSROXiGb2w==}
2108
2108
+
cpu: [arm64]
2109
2109
+
os: [linux]
2110
2110
+
2111
2111
+
'@oxlint/linux-x64-gnu@1.41.0':
2112
2112
+
resolution: {integrity: sha512-75k3CKj3fOc/a/2aSgO81s3HsTZOFROthPJ+UI2Oatic1LhvH6eKjKfx3jDDyVpzeDS2qekPlc/y3N33iZz5Og==}
2113
2113
+
cpu: [x64]
2114
2114
+
os: [linux]
2115
2115
+
2116
2116
+
'@oxlint/linux-x64-musl@1.41.0':
2117
2117
+
resolution: {integrity: sha512-8r82eBwGPoAPn67ZvdxTlX/Z3gVb+ZtN6nbkyFzwwHWAh8yGutX+VBcVkyrePSl6XgBP4QAaddPnHmkvJjqY0g==}
2118
2118
+
cpu: [x64]
2119
2119
+
os: [linux]
2120
2120
+
2121
2121
+
'@oxlint/win32-arm64@1.41.0':
2122
2122
+
resolution: {integrity: sha512-aK+DAcckQsNCOXKruatyYuY/ROjNiRejQB1PeJtkZwM21+8rV9ODYbvKNvt0pW+YCws7svftBSFMCpl3ke2unw==}
2123
2123
+
cpu: [arm64]
2124
2124
+
os: [win32]
2125
2125
+
2126
2126
+
'@oxlint/win32-x64@1.41.0':
2127
2127
+
resolution: {integrity: sha512-dVBXkZ6MGLd3owV7jvuqJsZwiF3qw7kEkDVsYVpS/O96eEvlHcxVbaPjJjrTBgikXqyC22vg3dxBU7MW0utGfw==}
2128
2128
+
cpu: [x64]
2129
2129
+
os: [win32]
2130
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
2671
-
'@sindresorhus/base62@1.0.0':
2672
2672
-
resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==}
2673
2673
-
engines: {node: '>=18'}
2674
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
2688
-
2689
2689
-
'@stylistic/eslint-plugin@5.7.0':
2690
2690
-
resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==}
2691
2691
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2692
2692
-
peerDependencies:
2693
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
2765
-
'@typescript-eslint/eslint-plugin@8.53.1':
2766
2766
-
resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==}
2767
2767
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2768
2768
-
peerDependencies:
2769
2769
-
'@typescript-eslint/parser': ^8.53.1
2770
2770
-
eslint: ^8.57.0 || ^9.0.0
2771
2771
-
typescript: '>=4.8.4 <6.0.0'
2772
2772
-
2773
2773
-
'@typescript-eslint/parser@8.53.1':
2774
2774
-
resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==}
2775
2775
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2776
2776
-
peerDependencies:
2777
2777
-
eslint: ^8.57.0 || ^9.0.0
2778
2778
-
typescript: '>=4.8.4 <6.0.0'
2779
2779
-
2780
2780
-
'@typescript-eslint/project-service@8.53.1':
2781
2781
-
resolution: {integrity: sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==}
2782
2782
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2783
2783
-
peerDependencies:
2784
2784
-
typescript: '>=4.8.4 <6.0.0'
2785
2785
-
2786
2786
-
'@typescript-eslint/scope-manager@8.53.1':
2787
2787
-
resolution: {integrity: sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==}
2788
2788
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2789
2789
-
2790
2790
-
'@typescript-eslint/tsconfig-utils@8.53.1':
2791
2791
-
resolution: {integrity: sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==}
2792
2792
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2793
2793
-
peerDependencies:
2794
2794
-
typescript: '>=4.8.4 <6.0.0'
2795
2795
-
2796
2796
-
'@typescript-eslint/type-utils@8.53.1':
2797
2797
-
resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==}
2798
2798
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2799
2799
-
peerDependencies:
2800
2800
-
eslint: ^8.57.0 || ^9.0.0
2801
2801
-
typescript: '>=4.8.4 <6.0.0'
2802
2802
-
2803
2803
-
'@typescript-eslint/types@8.53.1':
2804
2804
-
resolution: {integrity: sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==}
2805
2805
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2806
2806
-
2807
2807
-
'@typescript-eslint/typescript-estree@8.53.1':
2808
2808
-
resolution: {integrity: sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==}
2809
2809
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2810
2810
-
peerDependencies:
2811
2811
-
typescript: '>=4.8.4 <6.0.0'
2812
2812
-
2813
2813
-
'@typescript-eslint/utils@8.53.1':
2814
2814
-
resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==}
2815
2815
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2816
2816
-
peerDependencies:
2817
2817
-
eslint: ^8.57.0 || ^9.0.0
2818
2818
-
typescript: '>=4.8.4 <6.0.0'
2819
2819
-
2820
2820
-
'@typescript-eslint/visitor-keys@8.53.1':
2821
2821
-
resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==}
2822
2822
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
2823
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
2926
-
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
2927
2927
-
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
2928
2928
-
cpu: [arm]
2929
2929
-
os: [android]
2930
2930
-
2931
2931
-
'@unrs/resolver-binding-android-arm64@1.11.1':
2932
2932
-
resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
2933
2933
-
cpu: [arm64]
2934
2934
-
os: [android]
2935
2935
-
2936
2936
-
'@unrs/resolver-binding-darwin-arm64@1.11.1':
2937
2937
-
resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
2938
2938
-
cpu: [arm64]
2939
2939
-
os: [darwin]
2940
2940
-
2941
2941
-
'@unrs/resolver-binding-darwin-x64@1.11.1':
2942
2942
-
resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
2943
2943
-
cpu: [x64]
2944
2944
-
os: [darwin]
2945
2945
-
2946
2946
-
'@unrs/resolver-binding-freebsd-x64@1.11.1':
2947
2947
-
resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
2948
2948
-
cpu: [x64]
2949
2949
-
os: [freebsd]
2950
2950
-
2951
2951
-
'@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
2952
2952
-
resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
2953
2953
-
cpu: [arm]
2954
2954
-
os: [linux]
2955
2955
-
2956
2956
-
'@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
2957
2957
-
resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
2958
2958
-
cpu: [arm]
2959
2959
-
os: [linux]
2960
2960
-
2961
2961
-
'@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
2962
2962
-
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
2963
2963
-
cpu: [arm64]
2964
2964
-
os: [linux]
2965
2965
-
2966
2966
-
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
2967
2967
-
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
2968
2968
-
cpu: [arm64]
2969
2969
-
os: [linux]
2970
2970
-
2971
2971
-
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
2972
2972
-
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
2973
2973
-
cpu: [ppc64]
2974
2974
-
os: [linux]
2975
2975
-
2976
2976
-
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
2977
2977
-
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
2978
2978
-
cpu: [riscv64]
2979
2979
-
os: [linux]
2980
2980
-
2981
2981
-
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
2982
2982
-
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
2983
2983
-
cpu: [riscv64]
2984
2984
-
os: [linux]
2985
2985
-
2986
2986
-
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
2987
2987
-
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
2988
2988
-
cpu: [s390x]
2989
2989
-
os: [linux]
2990
2990
-
2991
2991
-
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
2992
2992
-
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
2993
2993
-
cpu: [x64]
2994
2994
-
os: [linux]
2995
2995
-
2996
2996
-
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
2997
2997
-
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
2998
2998
-
cpu: [x64]
2999
2999
-
os: [linux]
3000
3000
-
3001
3001
-
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
3002
3002
-
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
3003
3003
-
engines: {node: '>=14.0.0'}
3004
3004
-
cpu: [wasm32]
3005
3005
-
3006
3006
-
'@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
3007
3007
-
resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
3008
3008
-
cpu: [arm64]
3009
3009
-
os: [win32]
3010
3010
-
3011
3011
-
'@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
3012
3012
-
resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
3013
3013
-
cpu: [ia32]
3014
3014
-
os: [win32]
3015
3015
-
3016
3016
-
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
3017
3017
-
resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
3018
3018
-
cpu: [x64]
3019
3019
-
os: [win32]
3020
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
3350
-
are-docs-informative@0.0.2:
3351
3351
-
resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==}
3352
3352
-
engines: {node: '>=14'}
3353
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
3490
-
builtin-modules@5.0.0:
3491
3491
-
resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==}
3492
3492
-
engines: {node: '>=18.20'}
3493
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
3498
-
bundle-require@5.1.0:
3499
3499
-
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
3500
3500
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
3501
3501
-
peerDependencies:
3502
3502
-
esbuild: '>=0.18'
3503
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
3552
-
change-case@5.4.4:
3553
3553
-
resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
3554
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
3582
-
ci-info@4.3.1:
3583
3583
-
resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}
3584
3584
-
engines: {node: '>=8'}
3585
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
3592
-
clean-regexp@1.0.0:
3593
3593
-
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
3594
3594
-
engines: {node: '>=4'}
3595
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
3657
-
3658
3658
-
comment-parser@1.4.1:
3659
3659
-
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
3660
3660
-
engines: {node: '>= 12.0.0'}
3661
3661
-
3662
3662
-
comment-parser@1.4.4:
3663
3663
-
resolution: {integrity: sha512-0D6qSQ5IkeRrGJFHRClzaMOenMeT0gErz3zIw3AprKMqhRN6LNU2jQOdkPG/FZ+8bCgXE1VidrgSzlBBDZRr8A==}
3664
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
4080
-
escape-string-regexp@1.0.5:
4081
4081
-
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
4082
4082
-
engines: {node: '>=0.8.0'}
4083
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
4092
-
eslint-config-flat-gitignore@2.1.0:
4093
4093
-
resolution: {integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==}
4094
4094
-
peerDependencies:
4095
4095
-
eslint: ^9.5.0
4096
4096
-
4097
4097
-
eslint-flat-config-utils@2.1.4:
4098
4098
-
resolution: {integrity: sha512-bEnmU5gqzS+4O+id9vrbP43vByjF+8KOs+QuuV4OlqAuXmnRW2zfI/Rza1fQvdihQ5h4DUo0NqFAiViD4mSrzQ==}
4099
4099
-
4100
4100
-
eslint-import-context@0.1.9:
4101
4101
-
resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==}
4102
4102
-
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
4103
4103
-
peerDependencies:
4104
4104
-
unrs-resolver: ^1.0.0
4105
4105
-
peerDependenciesMeta:
4106
4106
-
unrs-resolver:
4107
4107
-
optional: true
4108
4108
-
4109
4109
-
eslint-merge-processors@2.0.0:
4110
4110
-
resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==}
4111
4111
-
peerDependencies:
4112
4112
-
eslint: '*'
4113
4113
-
4114
4114
-
eslint-plugin-import-lite@0.3.1:
4115
4115
-
resolution: {integrity: sha512-9+EByHZatvWFn/lRsUja5pwah0U5lhOA6SXqTI/iIzoIJHMgmsHUHEaTlLzKU/ukyCRwKEU5E92aUURPgVWq0A==}
4116
4116
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
4117
4117
-
peerDependencies:
4118
4118
-
eslint: '>=9.0.0'
4119
4119
-
typescript: '>=4.5'
4120
4120
-
peerDependenciesMeta:
4121
4121
-
typescript:
4122
4122
-
optional: true
4123
4123
-
4124
4124
-
eslint-plugin-import-x@4.16.1:
4125
4125
-
resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==}
4126
4126
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
4127
4127
-
peerDependencies:
4128
4128
-
'@typescript-eslint/utils': ^8.0.0
4129
4129
-
eslint: ^8.57.0 || ^9.0.0
4130
4130
-
eslint-import-resolver-node: '*'
4131
4131
-
peerDependenciesMeta:
4132
4132
-
'@typescript-eslint/utils':
4133
4133
-
optional: true
4134
4134
-
eslint-import-resolver-node:
4135
4135
-
optional: true
4136
4136
-
4137
4137
-
eslint-plugin-jsdoc@61.7.1:
4138
4138
-
resolution: {integrity: sha512-36DpldF95MlTX//n3/naULFVt8d1cV4jmSkx7ZKrE9ikkKHAgMLesuWp1SmwpVwAs5ndIM6abKd6PeOYZUgdWg==}
4139
4139
-
engines: {node: '>=20.11.0'}
4140
4140
-
peerDependencies:
4141
4141
-
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
4142
4142
-
4143
4143
-
eslint-plugin-regexp@2.10.0:
4144
4144
-
resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==}
4145
4145
-
engines: {node: ^18 || >=20}
4146
4146
-
peerDependencies:
4147
4147
-
eslint: '>=8.44.0'
4148
4148
-
4149
4149
-
eslint-plugin-unicorn@62.0.0:
4150
4150
-
resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==}
4151
4151
-
engines: {node: ^20.10.0 || >=21.0.0}
4152
4152
-
peerDependencies:
4153
4153
-
eslint: '>=9.38.0'
4154
4154
-
4155
4155
-
eslint-plugin-vue@10.7.0:
4156
4156
-
resolution: {integrity: sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==}
4157
4157
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
4158
4158
-
peerDependencies:
4159
4159
-
'@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
4160
4160
-
'@typescript-eslint/parser': ^7.0.0 || ^8.0.0
4161
4161
-
eslint: ^8.57.0 || ^9.0.0
4162
4162
-
vue-eslint-parser: ^10.0.0
4163
4163
-
peerDependenciesMeta:
4164
4164
-
'@stylistic/eslint-plugin':
4165
4165
-
optional: true
4166
4166
-
'@typescript-eslint/parser':
4167
4167
-
optional: true
4168
4168
-
4169
4169
-
eslint-processor-vue-blocks@2.0.0:
4170
4170
-
resolution: {integrity: sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==}
4171
4171
-
peerDependencies:
4172
4172
-
'@vue/compiler-sfc': ^3.3.0
4173
4173
-
eslint: '>=9.0.0'
4174
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
4183
-
eslint-typegen@2.3.0:
4184
4184
-
resolution: {integrity: sha512-azYgAvhlz1AyTpeLfVSKcrNJInuIsRrcUrOcHmEl8T9oMKesePVUPrF8gRgE6azV8CAlFzxJDTyaXAAbA/BYiA==}
4185
4185
-
peerDependencies:
4186
4186
-
eslint: ^9.0.0
4187
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
4196
-
eslint-visitor-keys@5.0.0:
4197
4197
-
resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==}
4198
4198
-
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
4199
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
4214
-
espree@11.1.0:
4215
4215
-
resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
4216
4216
-
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
4217
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
4339
-
find-up-simple@1.0.1:
4340
4340
-
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
4341
4341
-
engines: {node: '>=18'}
4342
4342
-
4343
4058
find-up@5.0.0:
4344
4059
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
4345
4060
engines: {node: '>=10'}
4346
4346
-
4347
4347
-
find-up@8.0.0:
4348
4348
-
resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==}
4349
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
4519
-
globals@16.5.0:
4520
4520
-
resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
4521
4521
-
engines: {node: '>=18'}
4522
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
4608
-
4609
4609
-
html-entities@2.6.0:
4610
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
4707
-
indent-string@5.0.0:
4708
4708
-
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
4709
4709
-
engines: {node: '>=12'}
4710
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
4751
-
is-builtin-module@5.0.0:
4752
4752
-
resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==}
4753
4753
-
engines: {node: '>=18.20'}
4754
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
4998
-
jsdoc-type-pratt-parser@4.8.0:
4999
4999
-
resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==}
5000
5000
-
engines: {node: '>=12.0.0'}
5001
5001
-
5002
5002
-
jsdoc-type-pratt-parser@7.0.0:
5003
5003
-
resolution: {integrity: sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==}
5004
5004
-
engines: {node: '>=20.0.0'}
5005
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
5025
-
5026
5026
-
json-schema-to-typescript-lite@15.0.0:
5027
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
5179
-
load-tsconfig@0.2.5:
5180
5180
-
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
5181
5181
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
5182
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
5194
-
5195
5195
-
locate-path@8.0.0:
5196
5196
-
resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==}
5197
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
5420
-
napi-postinstall@0.3.4:
5421
5421
-
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
5422
5422
-
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
5423
5423
-
hasBin: true
5424
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
5526
-
object-deep-merge@2.0.0:
5527
5527
-
resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==}
5528
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
5274
+
oxfmt@0.26.0:
5275
5275
+
resolution: {integrity: sha512-UDD1wFNwfeorMm2ZY0xy1KRAAvJ5NjKBfbDmiMwGP7baEHTq65cYpC0aPP+BGHc8weXUbSZaK8MdGyvuRUvS4Q==}
5276
5276
+
engines: {node: ^20.19.0 || >=22.12.0}
5277
5277
+
hasBin: true
5278
5278
+
5279
5279
+
oxlint@1.41.0:
5280
5280
+
resolution: {integrity: sha512-Dyaoup82uhgAgp5xLNt4dPdvl5eSJTIzqzL7DcKbkooUE4PDViWURIPlSUF8hu5a+sCnNIp/LlQMDsKoyaLTBA==}
5281
5281
+
engines: {node: ^20.19.0 || >=22.12.0}
5282
5282
+
hasBin: true
5283
5283
+
peerDependencies:
5284
5284
+
oxlint-tsgolint: '>=0.11.1'
5285
5285
+
peerDependenciesMeta:
5286
5286
+
oxlint-tsgolint:
5287
5287
+
optional: true
5288
5288
+
5605
5289
p-limit@3.1.0:
5606
5290
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
5607
5291
engines: {node: '>=10'}
5608
5292
5609
5609
-
p-limit@4.0.0:
5610
5610
-
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
5611
5611
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
5612
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
5617
-
p-locate@6.0.0:
5618
5618
-
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
5619
5619
-
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
5620
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
5637
-
parse-imports-exports@0.2.4:
5638
5638
-
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
5639
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
5649
-
5650
5650
-
parse-statements@1.0.11:
5651
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
5734
-
5735
5735
-
pluralize@8.0.0:
5736
5736
-
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
5737
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
6023
-
refa@0.12.1:
6024
6024
-
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
6025
6025
-
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
6026
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
6047
-
regexp-ast-analysis@0.7.1:
6048
6048
-
resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
6049
6049
-
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
6050
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
6077
-
6078
6078
-
reserved-identifiers@1.2.0:
6079
6079
-
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
6080
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
6200
-
scslre@0.3.0:
6201
6201
-
resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
6202
6202
-
engines: {node: ^14.0.0 || >=16.0.0}
6203
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
6358
-
spdx-exceptions@2.5.0:
6359
6359
-
resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
6360
6360
-
6361
6361
-
spdx-expression-parse@4.0.0:
6362
6362
-
resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==}
6363
6363
-
6364
6364
-
spdx-license-ids@3.0.22:
6365
6365
-
resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==}
6366
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
6375
-
6376
6376
-
stable-hash-x@0.2.0:
6377
6377
-
resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==}
6378
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
6471
-
6472
6472
-
strip-indent@4.1.1:
6473
6473
-
resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==}
6474
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
6221
+
tinypool@2.0.0:
6222
6222
+
resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==}
6223
6223
+
engines: {node: ^20.0.0 || >=22.0.0}
6224
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
6605
-
6606
6606
-
to-valid-identifier@1.0.0:
6607
6607
-
resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==}
6608
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
6635
-
ts-api-utils@2.4.0:
6636
6636
-
resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
6637
6637
-
engines: {node: '>=18.12'}
6638
6638
-
peerDependencies:
6639
6639
-
typescript: '>=4.8.4'
6640
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
6817
-
6818
6818
-
unrs-resolver@1.11.1:
6819
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
7096
-
vue-eslint-parser@10.2.0:
7097
7097
-
resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==}
7098
7098
-
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
7099
7099
-
peerDependencies:
7100
7100
-
eslint: ^8.57.0 || ^9.0.0
7101
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
7293
-
xml-name-validator@4.0.0:
7294
7294
-
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
7295
7295
-
engines: {node: '>=12'}
7296
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
7332
-
yocto-queue@1.2.2:
7333
7333
-
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
7334
7334
-
engines: {node: '>=12.20'}
7335
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
7376
-
'@apidevtools/json-schema-ref-parser@14.2.1(@types/json-schema@7.0.15)':
7377
7377
-
dependencies:
7378
7378
-
'@types/json-schema': 7.0.15
7379
7379
-
js-yaml: 4.1.1
7380
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
8110
-
'@clack/core@0.5.0':
8111
8111
-
dependencies:
8112
8112
-
picocolors: 1.1.1
8113
8113
-
sisteransi: 1.0.5
8114
8114
-
8115
7715
'@clack/core@1.0.0-alpha.7':
8116
7716
dependencies:
8117
8117
-
picocolors: 1.1.1
8118
8118
-
sisteransi: 1.0.5
8119
8119
-
8120
8120
-
'@clack/prompts@0.11.0':
8121
8121
-
dependencies:
8122
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
8189
-
8190
8190
-
'@es-joy/jsdoccomment@0.78.0':
8191
8191
-
dependencies:
8192
8192
-
'@types/estree': 1.0.8
8193
8193
-
'@typescript-eslint/types': 8.53.1
8194
8194
-
comment-parser: 1.4.1
8195
8195
-
esquery: 1.7.0
8196
8196
-
jsdoc-type-pratt-parser: 7.0.0
8197
8197
-
8198
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
8360
-
8361
8361
-
'@eslint-community/regexpp@4.12.2': {}
7944
7944
+
optional: true
8362
7945
8363
8363
-
'@eslint/compat@1.4.1(eslint@9.39.2(jiti@2.6.1))':
8364
8364
-
dependencies:
8365
8365
-
'@eslint/core': 0.17.0
8366
8366
-
optionalDependencies:
8367
8367
-
eslint: 9.39.2(jiti@2.6.1)
7946
7946
+
'@eslint-community/regexpp@4.12.2':
7947
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
7956
+
optional: true
8376
7957
8377
7958
'@eslint/config-helpers@0.4.2':
8378
7959
dependencies:
8379
7960
'@eslint/core': 0.17.0
8380
8380
-
8381
8381
-
'@eslint/config-inspector@1.4.2(eslint@9.39.2(jiti@2.6.1))':
8382
8382
-
dependencies:
8383
8383
-
ansis: 4.2.0
8384
8384
-
bundle-require: 5.1.0(esbuild@0.27.2)
8385
8385
-
cac: 6.7.14
8386
8386
-
chokidar: 4.0.3
8387
8387
-
esbuild: 0.27.2
8388
8388
-
eslint: 9.39.2(jiti@2.6.1)
8389
8389
-
h3: 1.15.5
8390
8390
-
tinyglobby: 0.2.15
8391
8391
-
ws: 8.19.0
8392
8392
-
transitivePeerDependencies:
8393
8393
-
- bufferutil
8394
8394
-
- utf-8-validate
7961
7961
+
optional: true
8395
7962
8396
7963
'@eslint/core@0.17.0':
8397
7964
dependencies:
8398
7965
'@types/json-schema': 7.0.15
7966
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
7981
+
optional: true
8413
7982
8414
8414
-
'@eslint/js@9.39.2': {}
7983
7983
+
'@eslint/js@9.39.2':
7984
7984
+
optional: true
8415
7985
8416
8416
-
'@eslint/object-schema@2.1.7': {}
7986
7986
+
'@eslint/object-schema@2.1.7':
7987
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
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
8430
-
'@humanfs/core@0.19.1': {}
8002
8002
+
'@humanfs/core@0.19.1':
8003
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
8009
+
optional: true
8436
8010
8437
8437
-
'@humanwhocodes/module-importer@1.0.1': {}
8011
8011
+
'@humanwhocodes/module-importer@1.0.1':
8012
8012
+
optional: true
8438
8013
8439
8439
-
'@humanwhocodes/retry@0.4.3': {}
8014
8014
+
'@humanwhocodes/retry@0.4.3':
8015
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
8698
-
'@napi-rs/wasm-runtime@0.2.12':
8699
8699
-
dependencies:
8700
8700
-
'@emnapi/core': 1.8.1
8701
8701
-
'@emnapi/runtime': 1.8.1
8702
8702
-
'@tybys/wasm-util': 0.10.1
8703
8703
-
optional: true
8704
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
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
8824
-
dependencies:
8825
8825
-
'@antfu/install-pkg': 1.1.0
8826
8826
-
'@clack/prompts': 0.11.0
8827
8827
-
'@eslint/js': 9.39.2
8828
8828
-
'@nuxt/eslint-plugin': 1.12.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
8829
8829
-
'@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.6.1))
8830
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
8831
-
'@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
8832
8832
-
eslint: 9.39.2(jiti@2.6.1)
8833
8833
-
eslint-config-flat-gitignore: 2.1.0(eslint@9.39.2(jiti@2.6.1))
8834
8834
-
eslint-flat-config-utils: 2.1.4
8835
8835
-
eslint-merge-processors: 2.0.0(eslint@9.39.2(jiti@2.6.1))
8836
8836
-
eslint-plugin-import-lite: 0.3.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
8837
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
8838
-
eslint-plugin-jsdoc: 61.7.1(eslint@9.39.2(jiti@2.6.1))
8839
8839
-
eslint-plugin-regexp: 2.10.0(eslint@9.39.2(jiti@2.6.1))
8840
8840
-
eslint-plugin-unicorn: 62.0.0(eslint@9.39.2(jiti@2.6.1))
8841
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
8842
-
eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1))
8843
8843
-
globals: 16.5.0
8844
8844
-
local-pkg: 1.1.2
8845
8845
-
pathe: 2.0.3
8846
8846
-
vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1))
8847
8847
-
transitivePeerDependencies:
8848
8848
-
- '@typescript-eslint/utils'
8849
8849
-
- '@vue/compiler-sfc'
8850
8850
-
- eslint-import-resolver-node
8851
8851
-
- supports-color
8852
8852
-
- typescript
8853
8853
-
8854
8854
-
'@nuxt/eslint-plugin@1.12.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
8855
8855
-
dependencies:
8856
8856
-
'@typescript-eslint/types': 8.53.1
8857
8857
-
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
8858
8858
-
eslint: 9.39.2(jiti@2.6.1)
8859
8859
-
transitivePeerDependencies:
8860
8860
-
- supports-color
8861
8861
-
- typescript
8862
8862
-
8863
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
8864
-
dependencies:
8865
8865
-
'@eslint/config-inspector': 1.4.2(eslint@9.39.2(jiti@2.6.1))
8866
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
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
8868
-
'@nuxt/eslint-plugin': 1.12.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
8869
8869
-
'@nuxt/kit': 4.3.0(magicast@0.5.1)
8870
8870
-
chokidar: 5.0.0
8871
8871
-
eslint: 9.39.2(jiti@2.6.1)
8872
8872
-
eslint-flat-config-utils: 2.1.4
8873
8873
-
eslint-typegen: 2.3.0(eslint@9.39.2(jiti@2.6.1))
8874
8874
-
find-up: 8.0.0
8875
8875
-
get-port-please: 3.2.0
8876
8876
-
mlly: 1.8.0
8877
8877
-
pathe: 2.0.3
8878
8878
-
unimport: 5.6.0
8879
8879
-
transitivePeerDependencies:
8880
8880
-
- '@typescript-eslint/utils'
8881
8881
-
- '@vue/compiler-sfc'
8882
8882
-
- bufferutil
8883
8883
-
- eslint-import-resolver-node
8884
8884
-
- eslint-plugin-format
8885
8885
-
- magicast
8886
8886
-
- supports-color
8887
8887
-
- typescript
8888
8888
-
- utf-8-validate
8889
8889
-
- vite
8890
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
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
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
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
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
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
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
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
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
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
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
8974
+
'@oxfmt/darwin-arm64@0.26.0':
8975
8975
+
optional: true
8976
8976
+
8977
8977
+
'@oxfmt/darwin-x64@0.26.0':
8978
8978
+
optional: true
8979
8979
+
8980
8980
+
'@oxfmt/linux-arm64-gnu@0.26.0':
8981
8981
+
optional: true
8982
8982
+
8983
8983
+
'@oxfmt/linux-arm64-musl@0.26.0':
8984
8984
+
optional: true
8985
8985
+
8986
8986
+
'@oxfmt/linux-x64-gnu@0.26.0':
8987
8987
+
optional: true
8988
8988
+
8989
8989
+
'@oxfmt/linux-x64-musl@0.26.0':
8990
8990
+
optional: true
8991
8991
+
8992
8992
+
'@oxfmt/win32-arm64@0.26.0':
8993
8993
+
optional: true
8994
8994
+
8995
8995
+
'@oxfmt/win32-x64@0.26.0':
8996
8996
+
optional: true
8997
8997
+
8998
8998
+
'@oxlint/darwin-arm64@1.41.0':
8999
8999
+
optional: true
9000
9000
+
9001
9001
+
'@oxlint/darwin-x64@1.41.0':
9002
9002
+
optional: true
9003
9003
+
9004
9004
+
'@oxlint/linux-arm64-gnu@1.41.0':
9005
9005
+
optional: true
9006
9006
+
9007
9007
+
'@oxlint/linux-arm64-musl@1.41.0':
9008
9008
+
optional: true
9009
9009
+
9010
9010
+
'@oxlint/linux-x64-gnu@1.41.0':
9011
9011
+
optional: true
9012
9012
+
9013
9013
+
'@oxlint/linux-x64-musl@1.41.0':
9014
9014
+
optional: true
9015
9015
+
9016
9016
+
'@oxlint/win32-arm64@1.41.0':
9017
9017
+
optional: true
9018
9018
+
9019
9019
+
'@oxlint/win32-x64@1.41.0':
9020
9020
+
optional: true
9021
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
9908
-
'@sindresorhus/base62@1.0.0': {}
9909
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
9917
-
9918
9918
-
'@stylistic/eslint-plugin@5.7.0(eslint@9.39.2(jiti@2.6.1))':
9919
9919
-
dependencies:
9920
9920
-
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
9921
9921
-
'@typescript-eslint/types': 8.53.1
9922
9922
-
eslint: 9.39.2(jiti@2.6.1)
9923
9923
-
eslint-visitor-keys: 5.0.0
9924
9924
-
espree: 11.1.0
9925
9925
-
estraverse: 5.3.0
9926
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
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
10009
-
dependencies:
10010
10010
-
'@eslint-community/regexpp': 4.12.2
10011
10011
-
'@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
10012
10012
-
'@typescript-eslint/scope-manager': 8.53.1
10013
10013
-
'@typescript-eslint/type-utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
10014
10014
-
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
10015
10015
-
'@typescript-eslint/visitor-keys': 8.53.1
10016
10016
-
eslint: 9.39.2(jiti@2.6.1)
10017
10017
-
ignore: 7.0.5
10018
10018
-
natural-compare: 1.4.0
10019
10019
-
ts-api-utils: 2.4.0(typescript@5.9.3)
10020
10020
-
typescript: 5.9.3
10021
10021
-
transitivePeerDependencies:
10022
10022
-
- supports-color
10023
10023
-
10024
10024
-
'@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
10025
10025
-
dependencies:
10026
10026
-
'@typescript-eslint/scope-manager': 8.53.1
10027
10027
-
'@typescript-eslint/types': 8.53.1
10028
10028
-
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
10029
10029
-
'@typescript-eslint/visitor-keys': 8.53.1
10030
10030
-
debug: 4.4.3
10031
10031
-
eslint: 9.39.2(jiti@2.6.1)
10032
10032
-
typescript: 5.9.3
10033
10033
-
transitivePeerDependencies:
10034
10034
-
- supports-color
10035
10035
-
10036
10036
-
'@typescript-eslint/project-service@8.53.1(typescript@5.9.3)':
10037
10037
-
dependencies:
10038
10038
-
'@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3)
10039
10039
-
'@typescript-eslint/types': 8.53.1
10040
10040
-
debug: 4.4.3
10041
10041
-
typescript: 5.9.3
10042
10042
-
transitivePeerDependencies:
10043
10043
-
- supports-color
10044
10044
-
10045
10045
-
'@typescript-eslint/scope-manager@8.53.1':
10046
10046
-
dependencies:
10047
10047
-
'@typescript-eslint/types': 8.53.1
10048
10048
-
'@typescript-eslint/visitor-keys': 8.53.1
10049
10049
-
10050
10050
-
'@typescript-eslint/tsconfig-utils@8.53.1(typescript@5.9.3)':
10051
10051
-
dependencies:
10052
10052
-
typescript: 5.9.3
10053
10053
-
10054
10054
-
'@typescript-eslint/type-utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
10055
10055
-
dependencies:
10056
10056
-
'@typescript-eslint/types': 8.53.1
10057
10057
-
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
10058
10058
-
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
10059
10059
-
debug: 4.4.3
10060
10060
-
eslint: 9.39.2(jiti@2.6.1)
10061
10061
-
ts-api-utils: 2.4.0(typescript@5.9.3)
10062
10062
-
typescript: 5.9.3
10063
10063
-
transitivePeerDependencies:
10064
10064
-
- supports-color
10065
10065
-
10066
10066
-
'@typescript-eslint/types@8.53.1': {}
10067
10067
-
10068
10068
-
'@typescript-eslint/typescript-estree@8.53.1(typescript@5.9.3)':
10069
10069
-
dependencies:
10070
10070
-
'@typescript-eslint/project-service': 8.53.1(typescript@5.9.3)
10071
10071
-
'@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3)
10072
10072
-
'@typescript-eslint/types': 8.53.1
10073
10073
-
'@typescript-eslint/visitor-keys': 8.53.1
10074
10074
-
debug: 4.4.3
10075
10075
-
minimatch: 9.0.5
10076
10076
-
semver: 7.7.3
10077
10077
-
tinyglobby: 0.2.15
10078
10078
-
ts-api-utils: 2.4.0(typescript@5.9.3)
10079
10079
-
typescript: 5.9.3
10080
10080
-
transitivePeerDependencies:
10081
10081
-
- supports-color
10082
10082
-
10083
10083
-
'@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
10084
10084
-
dependencies:
10085
10085
-
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
10086
10086
-
'@typescript-eslint/scope-manager': 8.53.1
10087
10087
-
'@typescript-eslint/types': 8.53.1
10088
10088
-
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
10089
10089
-
eslint: 9.39.2(jiti@2.6.1)
10090
10090
-
typescript: 5.9.3
10091
10091
-
transitivePeerDependencies:
10092
10092
-
- supports-color
10093
10093
-
10094
10094
-
'@typescript-eslint/visitor-keys@8.53.1':
10095
10095
-
dependencies:
10096
10096
-
'@typescript-eslint/types': 8.53.1
10097
10097
-
eslint-visitor-keys: 4.2.1
10098
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
10293
-
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
10294
10294
-
optional: true
10295
10295
-
10296
10296
-
'@unrs/resolver-binding-android-arm64@1.11.1':
10297
10297
-
optional: true
10298
10298
-
10299
10299
-
'@unrs/resolver-binding-darwin-arm64@1.11.1':
10300
10300
-
optional: true
10301
10301
-
10302
10302
-
'@unrs/resolver-binding-darwin-x64@1.11.1':
10303
10303
-
optional: true
10304
10304
-
10305
10305
-
'@unrs/resolver-binding-freebsd-x64@1.11.1':
10306
10306
-
optional: true
10307
10307
-
10308
10308
-
'@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
10309
10309
-
optional: true
10310
10310
-
10311
10311
-
'@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
10312
10312
-
optional: true
10313
10313
-
10314
10314
-
'@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
10315
10315
-
optional: true
10316
10316
-
10317
10317
-
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
10318
10318
-
optional: true
10319
10319
-
10320
10320
-
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
10321
10321
-
optional: true
10322
10322
-
10323
10323
-
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
10324
10324
-
optional: true
10325
10325
-
10326
10326
-
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
10327
10327
-
optional: true
10328
10328
-
10329
10329
-
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
10330
10330
-
optional: true
10331
10331
-
10332
10332
-
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
10333
10333
-
optional: true
10334
10334
-
10335
10335
-
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
10336
10336
-
optional: true
10337
10337
-
10338
10338
-
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
10339
10339
-
dependencies:
10340
10340
-
'@napi-rs/wasm-runtime': 0.2.12
10341
10341
-
optional: true
10342
10342
-
10343
10343
-
'@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
10344
10344
-
optional: true
10345
10345
-
10346
10346
-
'@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
10347
10347
-
optional: true
10348
10348
-
10349
10349
-
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
10350
10350
-
optional: true
10351
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
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
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
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
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
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
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
10843
-
are-docs-informative@0.0.2: {}
10844
10844
-
10845
10845
-
argparse@2.0.1: {}
10232
10232
+
argparse@2.0.1:
10233
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
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
10984
-
builtin-modules@5.0.0: {}
10985
10985
-
10986
10373
bundle-name@4.1.0:
10987
10374
dependencies:
10988
10375
run-applescript: 7.1.0
10989
10376
10990
10990
-
bundle-require@5.1.0(esbuild@0.27.2):
10991
10991
-
dependencies:
10992
10992
-
esbuild: 0.27.2
10993
10993
-
load-tsconfig: 0.2.5
10994
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
11031
-
callsites@3.1.0: {}
10413
10413
+
callsites@3.1.0:
10414
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
11052
-
11053
11053
-
change-case@5.4.4: {}
10435
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
11080
-
ci-info@4.3.1: {}
11081
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
11087
-
11088
11088
-
clean-regexp@1.0.0:
11089
11089
-
dependencies:
11090
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
11147
-
comment-parser@1.4.1: {}
11148
11148
-
11149
11149
-
comment-parser@1.4.4: {}
11150
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
11165
-
concat-map@0.0.1: {}
10537
10537
+
concat-map@0.0.1:
10538
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
11361
-
deep-is@0.1.4: {}
10734
10734
+
deep-is@0.1.4:
10735
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
11633
-
escape-string-regexp@1.0.5: {}
11634
11634
-
11635
11007
escape-string-regexp@4.0.0: {}
11636
11008
11637
11009
escape-string-regexp@5.0.0: {}
11638
11010
11639
11639
-
eslint-config-flat-gitignore@2.1.0(eslint@9.39.2(jiti@2.6.1)):
11640
11640
-
dependencies:
11641
11641
-
'@eslint/compat': 1.4.1(eslint@9.39.2(jiti@2.6.1))
11642
11642
-
eslint: 9.39.2(jiti@2.6.1)
11643
11643
-
11644
11644
-
eslint-flat-config-utils@2.1.4:
11645
11645
-
dependencies:
11646
11646
-
pathe: 2.0.3
11647
11647
-
11648
11648
-
eslint-import-context@0.1.9(unrs-resolver@1.11.1):
11649
11649
-
dependencies:
11650
11650
-
get-tsconfig: 4.13.0
11651
11651
-
stable-hash-x: 0.2.0
11652
11652
-
optionalDependencies:
11653
11653
-
unrs-resolver: 1.11.1
11654
11654
-
11655
11655
-
eslint-merge-processors@2.0.0(eslint@9.39.2(jiti@2.6.1)):
11656
11656
-
dependencies:
11657
11657
-
eslint: 9.39.2(jiti@2.6.1)
11658
11658
-
11659
11659
-
eslint-plugin-import-lite@0.3.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
11660
11660
-
dependencies:
11661
11661
-
eslint: 9.39.2(jiti@2.6.1)
11662
11662
-
optionalDependencies:
11663
11663
-
typescript: 5.9.3
11664
11664
-
11665
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
11666
-
dependencies:
11667
11667
-
'@typescript-eslint/types': 8.53.1
11668
11668
-
comment-parser: 1.4.4
11669
11669
-
debug: 4.4.3
11670
11670
-
eslint: 9.39.2(jiti@2.6.1)
11671
11671
-
eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
11672
11672
-
is-glob: 4.0.3
11673
11673
-
minimatch: 10.1.1
11674
11674
-
semver: 7.7.3
11675
11675
-
stable-hash-x: 0.2.0
11676
11676
-
unrs-resolver: 1.11.1
11677
11677
-
optionalDependencies:
11678
11678
-
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
11679
11679
-
transitivePeerDependencies:
11680
11680
-
- supports-color
11681
11681
-
11682
11682
-
eslint-plugin-jsdoc@61.7.1(eslint@9.39.2(jiti@2.6.1)):
11683
11683
-
dependencies:
11684
11684
-
'@es-joy/jsdoccomment': 0.78.0
11685
11685
-
'@es-joy/resolve.exports': 1.2.0
11686
11686
-
are-docs-informative: 0.0.2
11687
11687
-
comment-parser: 1.4.1
11688
11688
-
debug: 4.4.3
11689
11689
-
escape-string-regexp: 4.0.0
11690
11690
-
eslint: 9.39.2(jiti@2.6.1)
11691
11691
-
espree: 11.1.0
11692
11692
-
esquery: 1.7.0
11693
11693
-
html-entities: 2.6.0
11694
11694
-
object-deep-merge: 2.0.0
11695
11695
-
parse-imports-exports: 0.2.4
11696
11696
-
semver: 7.7.3
11697
11697
-
spdx-expression-parse: 4.0.0
11698
11698
-
to-valid-identifier: 1.0.0
11699
11699
-
transitivePeerDependencies:
11700
11700
-
- supports-color
11701
11701
-
11702
11702
-
eslint-plugin-regexp@2.10.0(eslint@9.39.2(jiti@2.6.1)):
11703
11703
-
dependencies:
11704
11704
-
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
11705
11705
-
'@eslint-community/regexpp': 4.12.2
11706
11706
-
comment-parser: 1.4.4
11707
11707
-
eslint: 9.39.2(jiti@2.6.1)
11708
11708
-
jsdoc-type-pratt-parser: 4.8.0
11709
11709
-
refa: 0.12.1
11710
11710
-
regexp-ast-analysis: 0.7.1
11711
11711
-
scslre: 0.3.0
11712
11712
-
11713
11713
-
eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@2.6.1)):
11714
11714
-
dependencies:
11715
11715
-
'@babel/helper-validator-identifier': 7.28.5
11716
11716
-
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
11717
11717
-
'@eslint/plugin-kit': 0.4.1
11718
11718
-
change-case: 5.4.4
11719
11719
-
ci-info: 4.3.1
11720
11720
-
clean-regexp: 1.0.0
11721
11721
-
core-js-compat: 3.48.0
11722
11722
-
eslint: 9.39.2(jiti@2.6.1)
11723
11723
-
esquery: 1.7.0
11724
11724
-
find-up-simple: 1.0.1
11725
11725
-
globals: 16.5.0
11726
11726
-
indent-string: 5.0.0
11727
11727
-
is-builtin-module: 5.0.0
11728
11728
-
jsesc: 3.1.0
11729
11729
-
pluralize: 8.0.0
11730
11730
-
regexp-tree: 0.1.27
11731
11731
-
regjsparser: 0.13.0
11732
11732
-
semver: 7.7.3
11733
11733
-
strip-indent: 4.1.1
11734
11734
-
11735
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
11736
-
dependencies:
11737
11737
-
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
11738
11738
-
eslint: 9.39.2(jiti@2.6.1)
11739
11739
-
natural-compare: 1.4.0
11740
11740
-
nth-check: 2.1.1
11741
11741
-
postcss-selector-parser: 7.1.1
11742
11742
-
semver: 7.7.3
11743
11743
-
vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1))
11744
11744
-
xml-name-validator: 4.0.0
11745
11745
-
optionalDependencies:
11746
11746
-
'@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.6.1))
11747
11747
-
'@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
11748
11748
-
11749
11749
-
eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.27)(eslint@9.39.2(jiti@2.6.1)):
11750
11750
-
dependencies:
11751
11751
-
'@vue/compiler-sfc': 3.5.27
11752
11752
-
eslint: 9.39.2(jiti@2.6.1)
11753
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
11020
+
optional: true
11763
11021
11764
11764
-
eslint-typegen@2.3.0(eslint@9.39.2(jiti@2.6.1)):
11765
11765
-
dependencies:
11766
11766
-
eslint: 9.39.2(jiti@2.6.1)
11767
11767
-
json-schema-to-typescript-lite: 15.0.0
11768
11768
-
ohash: 2.0.11
11022
11022
+
eslint-visitor-keys@3.4.3:
11023
11023
+
optional: true
11769
11024
11770
11770
-
eslint-visitor-keys@3.4.3: {}
11771
11771
-
11772
11772
-
eslint-visitor-keys@4.2.1: {}
11773
11773
-
11774
11774
-
eslint-visitor-keys@5.0.0: {}
11025
11025
+
eslint-visitor-keys@4.2.1:
11026
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
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
11822
-
11823
11823
-
espree@11.1.0:
11824
11824
-
dependencies:
11825
11825
-
acorn: 8.15.0
11826
11826
-
acorn-jsx: 5.3.2(acorn@8.15.0)
11827
11827
-
eslint-visitor-keys: 5.0.0
11075
11075
+
optional: true
11828
11076
11829
11077
esquery@1.7.0:
11830
11078
dependencies:
11831
11079
estraverse: 5.3.0
11080
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
11912
-
fast-levenshtein@2.0.6: {}
11161
11161
+
fast-levenshtein@2.0.6:
11162
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
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
11946
-
find-up-simple@1.0.1: {}
11947
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
11952
-
11953
11953
-
find-up@8.0.0:
11954
11954
-
dependencies:
11955
11955
-
locate-path: 8.0.0
11956
11956
-
unicorn-magic: 0.3.0
11201
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
11213
+
optional: true
11968
11214
11969
11969
-
flatted@3.3.3: {}
11215
11215
+
flatted@3.3.3:
11216
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
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
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
12191
-
globals@14.0.0: {}
12192
12192
-
12193
12193
-
globals@16.5.0: {}
11440
11440
+
globals@14.0.0:
11441
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
12303
-
html-entities@2.6.0: {}
12304
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
12370
-
ignore@5.3.2: {}
11616
11616
+
ignore@5.3.2:
11617
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
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
12391
-
imurmurhash@0.1.4: {}
12392
12392
-
12393
12393
-
indent-string@5.0.0: {}
11639
11639
+
imurmurhash@0.1.4:
11640
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
12447
-
12448
12448
-
is-builtin-module@5.0.0:
12449
12449
-
dependencies:
12450
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
12665
-
12666
12666
-
jsdoc-type-pratt-parser@4.8.0: {}
12667
12667
-
12668
12668
-
jsdoc-type-pratt-parser@7.0.0: {}
11908
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
12701
-
json-buffer@3.0.1: {}
11941
11941
+
json-buffer@3.0.1:
11942
11942
+
optional: true
12702
11943
12703
11944
json-parse-even-better-errors@2.3.1: {}
12704
11945
12705
12705
-
json-schema-to-typescript-lite@15.0.0:
12706
12706
-
dependencies:
12707
12707
-
'@apidevtools/json-schema-ref-parser': 14.2.1(@types/json-schema@7.0.15)
12708
12708
-
'@types/json-schema': 7.0.15
12709
12709
-
12710
12710
-
json-schema-traverse@0.4.1: {}
11946
11946
+
json-schema-traverse@0.4.1:
11947
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
12716
-
json-stable-stringify-without-jsonify@1.0.1: {}
11953
11953
+
json-stable-stringify-without-jsonify@1.0.1:
11954
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
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
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
12858
-
12859
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
12872
-
12873
12873
-
locate-path@8.0.0:
12874
12874
-
dependencies:
12875
12875
-
p-locate: 6.0.0
12110
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
12885
-
lodash.merge@4.6.2: {}
12120
12120
+
lodash.merge@4.6.2:
12121
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
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
13080
-
napi-postinstall@0.3.4: {}
13081
13081
-
13082
13082
-
natural-compare@1.4.0: {}
12317
12317
+
natural-compare@1.4.0:
12318
12318
+
optional: true
13083
12319
13084
12320
neo-async@2.6.2: {}
13085
12321
···
13289
12525
- magicast
13290
12526
- vue
13291
12527
13292
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
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
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
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
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
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
13419
-
13420
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
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
12806
+
oxfmt@0.26.0:
12807
12807
+
dependencies:
12808
12808
+
tinypool: 2.0.0
12809
12809
+
optionalDependencies:
12810
12810
+
'@oxfmt/darwin-arm64': 0.26.0
12811
12811
+
'@oxfmt/darwin-x64': 0.26.0
12812
12812
+
'@oxfmt/linux-arm64-gnu': 0.26.0
12813
12813
+
'@oxfmt/linux-arm64-musl': 0.26.0
12814
12814
+
'@oxfmt/linux-x64-gnu': 0.26.0
12815
12815
+
'@oxfmt/linux-x64-musl': 0.26.0
12816
12816
+
'@oxfmt/win32-arm64': 0.26.0
12817
12817
+
'@oxfmt/win32-x64': 0.26.0
12818
12818
+
12819
12819
+
oxlint@1.41.0:
12820
12820
+
optionalDependencies:
12821
12821
+
'@oxlint/darwin-arm64': 1.41.0
12822
12822
+
'@oxlint/darwin-x64': 1.41.0
12823
12823
+
'@oxlint/linux-arm64-gnu': 1.41.0
12824
12824
+
'@oxlint/linux-arm64-musl': 1.41.0
12825
12825
+
'@oxlint/linux-x64-gnu': 1.41.0
12826
12826
+
'@oxlint/linux-x64-musl': 1.41.0
12827
12827
+
'@oxlint/win32-arm64': 1.41.0
12828
12828
+
'@oxlint/win32-x64': 1.41.0
12829
12829
+
13571
12830
p-limit@3.1.0:
13572
12831
dependencies:
13573
12832
yocto-queue: 0.1.0
13574
13574
-
13575
13575
-
p-limit@4.0.0:
13576
13576
-
dependencies:
13577
13577
-
yocto-queue: 1.2.2
12833
12833
+
optional: true
13578
12834
13579
12835
p-locate@5.0.0:
13580
12836
dependencies:
13581
12837
p-limit: 3.1.0
13582
13582
-
13583
13583
-
p-locate@6.0.0:
13584
13584
-
dependencies:
13585
13585
-
p-limit: 4.0.0
12838
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
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
13602
-
parse-imports-exports@0.2.4:
13603
13603
-
dependencies:
13604
13604
-
parse-statements: 1.0.11
13605
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
13613
-
13614
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
13630
-
path-exists@4.0.0: {}
12878
12878
+
path-exists@4.0.0:
12879
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
13685
-
13686
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
13859
-
prelude-ls@1.2.1: {}
13106
13106
+
prelude-ls@1.2.1:
13107
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
13941
-
refa@0.12.1:
13942
13942
-
dependencies:
13943
13943
-
'@eslint-community/regexpp': 4.12.2
13944
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
13972
-
regexp-ast-analysis@0.7.1:
13973
13973
-
dependencies:
13974
13974
-
'@eslint-community/regexpp': 4.12.2
13975
13975
-
refa: 0.12.1
13976
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
14007
-
reserved-identifiers@1.2.0: {}
14008
14008
-
14009
14009
-
resolve-from@4.0.0: {}
13246
13246
+
resolve-from@4.0.0:
13247
13247
+
optional: true
14010
13248
14011
13249
resolve-from@5.0.0: {}
14012
13250
14013
14013
-
resolve-pkg-maps@1.0.0: {}
13251
13251
+
resolve-pkg-maps@1.0.0:
13252
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
14177
-
14178
14178
-
scslre@0.3.0:
14179
14179
-
dependencies:
14180
14180
-
'@eslint-community/regexpp': 4.12.2
14181
14181
-
refa: 0.12.1
14182
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
14418
-
spdx-exceptions@2.5.0: {}
14419
14419
-
14420
14420
-
spdx-expression-parse@4.0.0:
14421
14421
-
dependencies:
14422
14422
-
spdx-exceptions: 2.5.0
14423
14423
-
spdx-license-ids: 3.0.22
14424
14424
-
14425
14425
-
spdx-license-ids@3.0.22: {}
14426
14426
-
14427
13651
speakingurl@14.0.1: {}
14428
13652
14429
13653
srvx@0.10.1: {}
14430
14430
-
14431
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
14554
-
strip-indent@4.1.1: {}
14555
14555
-
14556
14556
-
strip-json-comments@3.1.1: {}
13776
13776
+
strip-json-comments@3.1.1:
13777
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
13889
+
tinypool@2.0.0: {}
13890
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
14684
-
to-valid-identifier@1.0.0:
14685
14685
-
dependencies:
14686
14686
-
'@sindresorhus/base62': 1.0.0
14687
14687
-
reserved-identifiers: 1.2.0
14688
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
14711
-
ts-api-utils@2.4.0(typescript@5.9.3):
14712
14712
-
dependencies:
14713
14713
-
typescript: 5.9.3
14714
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
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
14996
-
unrs-resolver@1.11.1:
14997
14997
-
dependencies:
14998
14998
-
napi-postinstall: 0.3.4
14999
14999
-
optionalDependencies:
15000
15000
-
'@unrs/resolver-binding-android-arm-eabi': 1.11.1
15001
15001
-
'@unrs/resolver-binding-android-arm64': 1.11.1
15002
15002
-
'@unrs/resolver-binding-darwin-arm64': 1.11.1
15003
15003
-
'@unrs/resolver-binding-darwin-x64': 1.11.1
15004
15004
-
'@unrs/resolver-binding-freebsd-x64': 1.11.1
15005
15005
-
'@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1
15006
15006
-
'@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1
15007
15007
-
'@unrs/resolver-binding-linux-arm64-gnu': 1.11.1
15008
15008
-
'@unrs/resolver-binding-linux-arm64-musl': 1.11.1
15009
15009
-
'@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1
15010
15010
-
'@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1
15011
15011
-
'@unrs/resolver-binding-linux-riscv64-musl': 1.11.1
15012
15012
-
'@unrs/resolver-binding-linux-s390x-gnu': 1.11.1
15013
15013
-
'@unrs/resolver-binding-linux-x64-gnu': 1.11.1
15014
15014
-
'@unrs/resolver-binding-linux-x64-musl': 1.11.1
15015
15015
-
'@unrs/resolver-binding-wasm32-wasi': 1.11.1
15016
15016
-
'@unrs/resolver-binding-win32-arm64-msvc': 1.11.1
15017
15017
-
'@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
15018
15018
-
'@unrs/resolver-binding-win32-x64-msvc': 1.11.1
15019
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
14261
+
optional: true
15070
14262
15071
14263
util-deprecate@1.0.2: {}
15072
14264
···
15114
14306
- tsx
15115
14307
- yaml
15116
14308
15117
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
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
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
15260
-
vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)):
15261
15261
-
dependencies:
15262
15262
-
debug: 4.4.3
15263
15263
-
eslint: 9.39.2(jiti@2.6.1)
15264
15264
-
eslint-scope: 8.4.0
15265
15265
-
eslint-visitor-keys: 4.2.1
15266
15266
-
espree: 10.4.0
15267
15267
-
esquery: 1.7.0
15268
15268
-
semver: 7.7.3
15269
15269
-
transitivePeerDependencies:
15270
15270
-
- supports-color
15271
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
15427
-
word-wrap@1.2.5: {}
14608
14608
+
word-wrap@1.2.5:
14609
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
15566
-
xml-name-validator@4.0.0: {}
15567
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
15594
-
yocto-queue@0.1.0: {}
15595
15595
-
15596
15596
-
yocto-queue@1.2.2: {}
14774
14774
+
yocto-queue@0.1.0:
14775
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
4
-
"extends": [
5
5
-
"github>danielroe/renovate"
6
6
-
]
4
4
+
"extends": ["github>danielroe/renovate"]
7
5
}
+2
-3
server/api/registry/[...pkg].get.ts
···
1
1
export default defineCachedEventHandler(
2
2
-
async (event) => {
2
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
12
-
}
13
13
-
catch (error) {
12
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
2
-
async (event) => {
2
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
20
-
}
21
21
-
catch {
20
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
27
-
getKey: (event) => {
26
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
8
-
'javascript', 'typescript', 'jsx', 'tsx',
9
9
-
'vue', 'svelte', 'astro',
8
8
+
'javascript',
9
9
+
'typescript',
10
10
+
'jsx',
11
11
+
'tsx',
12
12
+
'vue',
13
13
+
'svelte',
14
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
27
-
return await response.json() as PackageJson
28
28
-
}
29
29
-
catch {
32
32
+
return (await response.json()) as PackageJson
33
33
+
} catch {
30
34
return null
31
35
}
32
36
}
···
34
38
/**
35
39
* Fetch file content from jsDelivr CDN.
36
40
*/
37
37
-
async function fetchFileContent(packageName: string, version: string, filePath: string): Promise<string> {
41
41
+
async function fetchFileContent(
42
42
+
packageName: string,
43
43
+
version: string,
44
44
+
filePath: string,
45
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
78
-
async (event) => {
86
86
+
async event => {
79
87
const segments = getRouterParam(event, 'pkg')?.split('/') ?? []
80
88
if (segments.length === 0) {
81
81
-
throw createError({ statusCode: 400, message: 'Package name, version, and file path are required' })
89
89
+
throw createError({
90
90
+
statusCode: 400,
91
91
+
message: 'Package name, version, and file path are required',
92
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
100
-
throw createError({ statusCode: 400, message: 'Package name, version, and file path are required' })
111
111
+
throw createError({
112
112
+
statusCode: 400,
113
113
+
message: 'Package name, version, and file path are required',
114
114
+
})
101
115
}
102
116
103
117
try {
···
152
166
html,
153
167
lines: content.split('\n').length,
154
168
}
155
155
-
}
156
156
-
catch (error) {
169
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
165
-
getKey: (event) => {
178
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
11
-
async (event) => {
11
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
41
-
}
42
42
-
catch (error) {
41
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
51
-
getKey: (event) => {
50
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
5
-
async function fetchReadmeFromJsdelivr(packageName: string, version?: string): Promise<string | null> {
5
5
+
async function fetchReadmeFromJsdelivr(
6
6
+
packageName: string,
7
7
+
version?: string,
8
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
16
-
}
17
17
-
catch {
19
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
35
-
async (event) => {
37
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
50
-
}
51
51
-
else {
52
52
+
} else {
52
53
packageName = segments.join('/')
53
54
}
54
55
···
67
68
if (versionData) {
68
69
readmeContent = versionData.readme
69
70
}
70
70
-
}
71
71
-
else {
71
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
78
-
readmeContent = await fetchReadmeFromJsdelivr(packageName, version) ?? undefined
78
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
87
-
}
88
88
-
catch (error) {
87
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
97
-
getKey: (event) => {
96
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
2
-
async (event) => {
2
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
14
-
}
15
15
-
catch {
14
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
21
-
getKey: (event) => {
20
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
234
-
'javascript', 'typescript', 'jsx', 'tsx',
235
235
-
'vue', 'svelte', 'astro',
234
234
+
'javascript',
235
235
+
'typescript',
236
236
+
'jsx',
237
237
+
'tsx',
238
238
+
'vue',
239
239
+
'svelte',
240
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
249
-
export async function highlightCode(code: string, language: string, options?: HighlightOptions): Promise<string> {
254
254
+
export async function highlightCode(
255
255
+
code: string,
256
256
+
language: string,
257
257
+
options?: HighlightOptions,
258
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
282
-
const wrappedLines = lines.map((line: string, i: number) => {
283
283
-
if (i === lines.length - 1 && line === '') return null
284
284
-
return `<span class="line">${line}</span>`
285
285
-
}).filter((line: string | null): line is string => line !== null).join('')
291
291
+
const wrappedLines = lines
292
292
+
.map((line: string, i: number) => {
293
293
+
if (i === lines.length - 1 && line === '') return null
294
294
+
return `<span class="line">${line}</span>`
295
295
+
})
296
296
+
.filter((line: string | null): line is string => line !== null)
297
297
+
.join('')
286
298
287
299
return html.replace(codeMatch[1], wrappedLines)
288
300
}
289
301
290
302
return html
291
291
-
}
292
292
-
catch {
303
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
299
-
const wrappedLines = lines.map((line) => {
300
300
-
const escaped = line
301
301
-
.replace(/&/g, '&')
302
302
-
.replace(/</g, '<')
303
303
-
.replace(/>/g, '>')
304
304
-
return `<span class="line">${escaped}</span>`
305
305
-
}).join('') // No newlines - display:block handles it
310
310
+
const wrappedLines = lines
311
311
+
.map(line => {
312
312
+
const escaped = line.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
313
313
+
return `<span class="line">${escaped}</span>`
314
314
+
})
315
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
1
-
import type { JsDelivrPackageResponse, JsDelivrFileNode, PackageFileTree, PackageFileTreeResponse } from '#shared/types'
1
1
+
import type {
2
2
+
JsDelivrPackageResponse,
3
3
+
JsDelivrFileNode,
4
4
+
PackageFileTree,
5
5
+
PackageFileTreeResponse,
6
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
7
-
export async function fetchFileTree(packageName: string, version: string): Promise<JsDelivrPackageResponse> {
12
12
+
export async function fetchFileTree(
13
13
+
packageName: string,
14
14
+
version: string,
15
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
24
-
export function convertToFileTree(nodes: JsDelivrFileNode[], parentPath: string = ''): PackageFileTree[] {
32
32
+
export function convertToFileTree(
33
33
+
nodes: JsDelivrFileNode[],
34
34
+
parentPath: string = '',
35
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
37
-
}
38
38
-
else {
48
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
63
-
export async function getPackageFileTree(packageName: string, version: string): Promise<PackageFileTreeResponse> {
73
73
+
export async function getPackageFileTree(
74
74
+
packageName: string,
75
75
+
version: string,
76
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
19
-
}
20
20
-
else if (node.children) {
19
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
43
-
}
44
44
-
else {
42
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
80
-
return [
81
81
-
[],
82
82
-
['.ts', '.tsx'],
83
83
-
['.d.ts'],
84
84
-
['.js', '.jsx'],
85
85
-
['.json'],
86
86
-
]
78
78
+
return [[], ['.ts', '.tsx'], ['.d.ts'], ['.js', '.jsx'], ['.json']]
87
79
}
88
80
89
81
if (ext === 'mts') {
90
90
-
return [
91
91
-
[],
92
92
-
['.mts'],
93
93
-
['.d.mts', '.d.ts'],
94
94
-
['.mjs', '.js'],
95
95
-
['.json'],
96
96
-
]
82
82
+
return [[], ['.mts'], ['.d.mts', '.d.ts'], ['.mjs', '.js'], ['.json']]
97
83
}
98
84
99
85
if (ext === 'cts') {
100
100
-
return [
101
101
-
[],
102
102
-
['.cts'],
103
103
-
['.d.cts', '.d.ts'],
104
104
-
['.cjs', '.js'],
105
105
-
['.json'],
106
106
-
]
86
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
111
-
return [
112
112
-
[],
113
113
-
['.js', '.jsx'],
114
114
-
['.ts', '.tsx'],
115
115
-
['.json'],
116
116
-
]
91
91
+
return [[], ['.js', '.jsx'], ['.ts', '.tsx'], ['.json']]
117
92
}
118
93
119
94
if (ext === 'mjs') {
120
120
-
return [
121
121
-
[],
122
122
-
['.mjs'],
123
123
-
['.js'],
124
124
-
['.mts', '.ts'],
125
125
-
['.json'],
126
126
-
]
95
95
+
return [[], ['.mjs'], ['.js'], ['.mts', '.ts'], ['.json']]
127
96
}
128
97
129
98
if (ext === 'cjs') {
130
130
-
return [
131
131
-
[],
132
132
-
['.cjs'],
133
133
-
['.js'],
134
134
-
['.cts', '.ts'],
135
135
-
['.json'],
136
136
-
]
99
99
+
return [[], ['.cjs'], ['.js'], ['.cts', '.ts'], ['.json']]
137
100
}
138
101
139
102
// Default for other files (vue, svelte, etc.)
140
140
-
return [
141
141
-
[],
142
142
-
['.ts', '.js'],
143
143
-
['.d.ts'],
144
144
-
['.json'],
145
145
-
]
103
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
223
-
}
224
224
-
else {
181
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
60
-
return /-(alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint)
61
61
-
|| /-\d/.test(constraint) // e.g., -0, -1
60
60
+
return (
61
61
+
/-(alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) ||
62
62
+
/-\d/.test(constraint)
63
63
+
) // e.g., -0, -1
62
64
}
63
65
64
66
/**
···
82
84
}
83
85
84
86
return maxSatisfying(versions, constraint)
85
85
-
}
86
86
-
catch {
87
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
8
-
'h3', 'h4', 'h5', 'h6',
9
9
-
'p', 'br', 'hr',
10
10
-
'ul', 'ol', 'li',
11
11
-
'blockquote', 'pre', 'code',
12
12
-
'a', 'strong', 'em', 'del', 's',
13
13
-
'table', 'thead', 'tbody', 'tr', 'th', 'td',
14
14
-
'img', 'picture', 'source',
15
15
-
'details', 'summary',
16
16
-
'div', 'span',
17
17
-
'sup', 'sub',
18
18
-
'kbd', 'mark',
8
8
+
'h3',
9
9
+
'h4',
10
10
+
'h5',
11
11
+
'h6',
12
12
+
'p',
13
13
+
'br',
14
14
+
'hr',
15
15
+
'ul',
16
16
+
'ol',
17
17
+
'li',
18
18
+
'blockquote',
19
19
+
'pre',
20
20
+
'code',
21
21
+
'a',
22
22
+
'strong',
23
23
+
'em',
24
24
+
'del',
25
25
+
's',
26
26
+
'table',
27
27
+
'thead',
28
28
+
'tbody',
29
29
+
'tr',
30
30
+
'th',
31
31
+
'td',
32
32
+
'img',
33
33
+
'picture',
34
34
+
'source',
35
35
+
'details',
36
36
+
'summary',
37
37
+
'div',
38
38
+
'span',
39
39
+
'sup',
40
40
+
'sub',
41
41
+
'kbd',
42
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
94
-
}
95
95
-
catch {
118
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
101
-
const escaped = text
102
102
-
.replace(/&/g, '&')
103
103
-
.replace(/</g, '<')
104
104
-
.replace(/>/g, '>')
124
124
+
const escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
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
9
-
themes: [
10
10
-
import('@shikijs/themes/github-dark'),
11
11
-
],
9
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
64
-
}
65
65
-
catch {
62
62
+
} catch {
66
63
// Fall back to plain
67
64
}
68
65
}
69
66
70
67
// Plain code block for unknown languages
71
71
-
const escaped = code
72
72
-
.replace(/&/g, '&')
73
73
-
.replace(/</g, '<')
74
74
-
.replace(/>/g, '>')
68
68
+
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
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
10
-
export type { Packument, PackumentVersion, Manifest, ManifestVersion, PackageJSON } from '@npm/types'
10
10
+
export type {
11
11
+
Packument,
12
12
+
PackumentVersion,
13
13
+
Manifest,
14
14
+
ManifestVersion,
15
15
+
PackageJSON,
16
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
26
-
'time': { modified?: string, created?: string } & Record<string, string>
32
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
32
-
'repository'?: { type?: string, url?: string, directory?: string }
33
33
-
'bugs'?: { url?: string, email?: string }
38
38
+
'repository'?: { type?: string; url?: string; directory?: string }
39
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
4
-
it('should ', () => {
4
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
13
-
const localUrl = baseURL?.endsWith('/') ? `${baseURL}${ogImagePath.slice(1)}` : `${baseURL}${ogImagePath}`
13
13
+
const localUrl = baseURL?.endsWith('/')
14
14
+
? `${baseURL}${ogImagePath.slice(1)}`
15
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
1
-
import { defineConfig, presetIcons, presetWind4, transformerDirectives, transformerVariantGroup } from 'unocss'
1
1
+
import {
2
2
+
defineConfig,
3
3
+
presetIcons,
4
4
+
presetWind4,
5
5
+
transformerDirectives,
6
6
+
transformerVariantGroup,
7
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
11
-
transformers: [
12
12
-
transformerDirectives(),
13
13
-
transformerVariantGroup(),
14
14
-
],
17
17
+
transformers: [transformerDirectives(), transformerVariantGroup()],
15
18
theme: {
16
19
font: {
17
17
-
mono: '\'Geist Mono\', monospace',
18
18
-
sans: '\'Geist\', system-ui, -apple-system, sans-serif',
20
20
+
mono: "'Geist Mono', monospace",
21
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
54
-
'slide-up': '{from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: translateY(0) }}',
55
55
-
'scale-in': '{from { opacity: 0; transform: scale(0.95) } to { opacity: 1; transform: scale(1) }}',
57
57
+
'slide-up':
58
58
+
'{from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: translateY(0) }}',
59
59
+
'scale-in':
60
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
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
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
87
+
[
88
88
+
'btn',
89
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
90
+
],
91
91
+
[
92
92
+
'btn-ghost',
93
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
94
+
],
84
95
85
96
// Links
86
86
-
['link', 'text-fg underline-offset-4 decoration-border hover:(decoration-fg underline) transition-colors duration-200 focus-ring'],
97
97
+
[
98
98
+
'link',
99
99
+
'text-fg underline-offset-4 decoration-border hover:(decoration-fg underline) transition-colors duration-200 focus-ring',
100
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
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
108
+
[
109
109
+
'input-base',
110
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
111
+
],
95
112
96
113
// Tags/badges
97
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
114
+
[
115
115
+
'tag',
116
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
117
+
],
98
118
99
119
// Code blocks
100
100
-
['code-block', 'bg-bg-muted border border-border rounded-md p-4 font-mono text-sm overflow-x-auto'],
120
120
+
[
121
121
+
'code-block',
122
122
+
'bg-bg-muted border border-border rounded-md p-4 font-mono text-sm overflow-x-auto',
123
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
116
-
['text-gradient', {
117
117
-
'background': 'linear-gradient(to right, #fafafa, #a1a1a1)',
118
118
-
'-webkit-background-clip': 'text',
119
119
-
'-webkit-text-fill-color': 'transparent',
120
120
-
'background-clip': 'text',
121
121
-
}],
139
139
+
[
140
140
+
'text-gradient',
141
141
+
{
142
142
+
'background': 'linear-gradient(to right, #fafafa, #a1a1a1)',
143
143
+
'-webkit-background-clip': 'text',
144
144
+
'-webkit-text-fill-color': 'transparent',
145
145
+
'background-clip': 'text',
146
146
+
},
147
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
35
-
instances: [
36
36
-
{ browser: 'chromium' },
37
37
-
],
35
35
+
instances: [{ browser: 'chromium' }],
38
36
},
39
37
},
40
38
}),