Skip to content

Commit 5da366f

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents b2013d8 + 8825b0a commit 5da366f

16 files changed

Lines changed: 504 additions & 174 deletions

File tree

app/components/ConnectorModal.vue

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ const tokenInput = shallowRef('')
88
const portInput = shallowRef('31415')
99
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
1010
11+
const hasAttemptedConnect = shallowRef(false)
12+
1113
async function handleConnect() {
14+
hasAttemptedConnect.value = true
1215
const port = Number.parseInt(portInput.value, 10) || 31415
1316
const success = await connect(tokenInput.value.trim(), port)
1417
if (success) {
@@ -42,6 +45,7 @@ const executeNpmxConnectorCommand = computed(() => {
4245
watch(open, isOpen => {
4346
if (isOpen) {
4447
tokenInput.value = ''
48+
hasAttemptedConnect.value = false
4549
}
4650
})
4751
</script>
@@ -116,10 +120,58 @@ watch(open, isOpen => {
116120

117121
<!-- Disconnected state -->
118122
<form v-else class="space-y-4" @submit.prevent="handleConnect">
123+
<!-- Contributor-only notice -->
124+
<div class="p-3 bg-amber-500/10 border border-amber-500/30 rounded-lg">
125+
<div class="space-y-2">
126+
<span
127+
class="inline-block px-2 py-0.5 text-xs font-bold uppercase tracking-wider bg-amber-500/20 text-amber-400 rounded"
128+
>
129+
{{ $t('connector.modal.contributor_badge') }}
130+
</span>
131+
<p class="text-sm text-fg-muted">
132+
<i18n-t keypath="connector.modal.contributor_notice">
133+
<template #link>
134+
<a
135+
href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#local-connector-cli"
136+
target="_blank"
137+
rel="noopener noreferrer"
138+
class="text-amber-400 hover:underline"
139+
>
140+
{{ $t('connector.modal.contributor_link') }}
141+
</a>
142+
</template>
143+
</i18n-t>
144+
</p>
145+
</div>
146+
</div>
147+
119148
<p class="text-sm text-fg-muted">
120149
{{ $t('connector.modal.run_hint') }}
121150
</p>
122151

152+
<div
153+
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
154+
>
155+
<span class="text-fg-subtle">$</span>
156+
<span class="text-fg-subtle ms-2">pnpm npmx-connector</span>
157+
<button
158+
type="button"
159+
:aria-label="
160+
copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')
161+
"
162+
class="ms-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
163+
@click="copy('pnpm npmx-connector')"
164+
>
165+
<span v-if="!copied" class="i-carbon:copy block w-5 h-5" aria-hidden="true" />
166+
<span
167+
v-else
168+
class="i-carbon:checkmark block w-5 h-5 text-green-500"
169+
aria-hidden="true"
170+
/>
171+
</button>
172+
</div>
173+
174+
<!-- TODO: Uncomment when npmx-connector is published to npm
123175
<div
124176
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
125177
>
@@ -145,6 +197,7 @@ watch(open, isOpen => {
145197
</button>
146198
</div>
147199
</div>
200+
-->
148201

149202
<p class="text-sm text-fg-muted">{{ $t('connector.modal.paste_token') }}</p>
150203

@@ -193,9 +246,9 @@ watch(open, isOpen => {
193246
</details>
194247
</div>
195248

196-
<!-- Error message -->
249+
<!-- Error message (only show after user explicitly clicks Connect) -->
197250
<div
198-
v-if="error"
251+
v-if="error && hasAttemptedConnect"
199252
role="alert"
200253
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
201254
>

app/components/MobileMenu.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ watch(isOpen, open => (isLocked.value = open))
182182
rel="noopener noreferrer"
183183
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
184184
>
185-
<span class="i-carbon:logo-x w-5 h-5 text-fg-muted" aria-hidden="true" />
185+
<span class="i-simple-icons:bluesky w-5 h-5 text-fg-muted" aria-hidden="true" />
186186
{{ $t('footer.social') }}
187187
<span
188188
class="i-carbon:launch rtl-flip w-3 h-3 ms-auto text-fg-subtle"

app/components/ToggleSkeleton.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
label: string
4+
}>()
5+
</script>
6+
7+
<template>
8+
<div class="w-full flex items-center justify-between gap-4">
9+
<span class="text-sm text-fg font-medium text-start">
10+
{{ label }}
11+
</span>
12+
<span class="skeleton block h-6 w-11 shrink-0 rounded-full" />
13+
</div>
14+
</template>

app/composables/useCachedFetch.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,10 @@ export function useCachedFetch(): CachedFetchFunction {
3434
if (import.meta.client) {
3535
return async <T = unknown>(
3636
url: string,
37-
options: {
38-
method?: string
39-
body?: unknown
40-
headers?: Record<string, string>
41-
} = {},
37+
options: Parameters<typeof $fetch>[1] = {},
4238
_ttl?: number,
4339
): Promise<CachedFetchResult<T>> => {
44-
const data = (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
40+
const data = (await $fetch<T>(url, options)) as T
4541
return { data, isStale: false, cachedAt: null }
4642
}
4743
}
@@ -59,14 +55,10 @@ export function useCachedFetch(): CachedFetchFunction {
5955
// (shouldn't happen in normal operation)
6056
return async <T = unknown>(
6157
url: string,
62-
options: {
63-
method?: string
64-
body?: unknown
65-
headers?: Record<string, string>
66-
} = {},
58+
options: Parameters<typeof $fetch>[1] = {},
6759
_ttl?: number,
6860
): Promise<CachedFetchResult<T>> => {
69-
const data = (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
61+
const data = (await $fetch<T>(url, options)) as T
7062
return { data, isStale: false, cachedAt: null }
7163
}
7264
}

app/composables/useNpmRegistry.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,11 @@ export function usePackage(
185185

186186
const asyncData = useLazyAsyncData(
187187
() => `package:${toValue(name)}:${toValue(requestedVersion) ?? ''}`,
188-
async () => {
188+
async (_nuxtApp, { signal }) => {
189189
const encodedName = encodePackageName(toValue(name))
190-
const { data: r, isStale } = await cachedFetch<Packument>(`${NPM_REGISTRY}/${encodedName}`)
190+
const { data: r, isStale } = await cachedFetch<Packument>(`${NPM_REGISTRY}/${encodedName}`, {
191+
signal,
192+
})
191193
const reqVer = toValue(requestedVersion)
192194
const pkg = transformPackument(r, reqVer)
193195
const resolvedVersion = getResolvedVersion(pkg, reqVer)
@@ -233,10 +235,11 @@ export function usePackageDownloads(
233235

234236
const asyncData = useLazyAsyncData(
235237
() => `downloads:${toValue(name)}:${toValue(period)}`,
236-
async () => {
238+
async (_nuxtApp, { signal }) => {
237239
const encodedName = encodePackageName(toValue(name))
238240
const { data, isStale } = await cachedFetch<NpmDownloadCount>(
239241
`${NPM_API}/downloads/point/${toValue(period)}/${encodedName}`,
242+
{ signal },
240243
)
241244
return { ...data, isStale }
242245
},
@@ -306,7 +309,7 @@ export function useNpmSearch(
306309

307310
const asyncData = useLazyAsyncData(
308311
() => `search:incremental:${toValue(query)}`,
309-
async () => {
312+
async (_nuxtApp, { signal }) => {
310313
const q = toValue(query)
311314
if (!q.trim()) {
312315
return emptySearchResponse
@@ -325,7 +328,7 @@ export function useNpmSearch(
325328

326329
const { data: response, isStale } = await cachedFetch<NpmSearchResponse>(
327330
`${NPM_REGISTRY}/-/v1/search?${params.toString()}`,
328-
{},
331+
{ signal },
329332
60,
330333
)
331334

@@ -509,7 +512,7 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
509512

510513
const asyncData = useLazyAsyncData(
511514
() => `org-packages:${toValue(orgName)}`,
512-
async () => {
515+
async (_nuxtApp, { signal }) => {
513516
const org = toValue(orgName)
514517
if (!org) {
515518
return emptySearchResponse
@@ -520,6 +523,7 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
520523
try {
521524
const { data } = await cachedFetch<Record<string, string>>(
522525
`${NPM_REGISTRY}/-/org/${encodeURIComponent(org)}/package`,
526+
{ signal },
523527
)
524528
packageNames = Object.keys(data)
525529
} catch (err) {
@@ -553,6 +557,7 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
553557
const encoded = encodePackageName(name)
554558
const { data: pkg } = await cachedFetch<MinimalPackument>(
555559
`${NPM_REGISTRY}/${encoded}`,
560+
{ signal },
556561
)
557562
return pkg
558563
} catch {

app/composables/useRepoMeta.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type ProviderAdapter = {
9393
cachedFetch: CachedFetchFunction,
9494
ref: RepoRef,
9595
links: RepoMetaLinks,
96+
options?: Parameters<typeof $fetch>[1],
9697
): Promise<RepoMeta | null>
9798
}
9899

@@ -126,13 +127,13 @@ const githubAdapter: ProviderAdapter = {
126127
}
127128
},
128129

129-
async fetchMeta(cachedFetch, ref, links) {
130+
async fetchMeta(cachedFetch, ref, links, options = {}) {
130131
// Using UNGH to avoid API limitations of the Github API
131132
let res: UnghRepoResponse | null = null
132133
try {
133134
const { data } = await cachedFetch<UnghRepoResponse>(
134135
`https://ungh.cc/repos/${ref.owner}/${ref.repo}`,
135-
{ headers: { 'User-Agent': 'npmx' } },
136+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
136137
REPO_META_TTL,
137138
)
138139
res = data
@@ -191,14 +192,14 @@ const gitlabAdapter: ProviderAdapter = {
191192
}
192193
},
193194

194-
async fetchMeta(cachedFetch, ref, links) {
195+
async fetchMeta(cachedFetch, ref, links, options = {}) {
195196
const baseHost = ref.host ?? 'gitlab.com'
196197
const projectPath = encodeURIComponent(`${ref.owner}/${ref.repo}`)
197198
let res: GitLabProjectResponse | null = null
198199
try {
199200
const { data } = await cachedFetch<GitLabProjectResponse>(
200201
`https://${baseHost}/api/v4/projects/${projectPath}`,
201-
{ headers: { 'User-Agent': 'npmx' } },
202+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
202203
REPO_META_TTL,
203204
)
204205
res = data
@@ -249,12 +250,12 @@ const bitbucketAdapter: ProviderAdapter = {
249250
}
250251
},
251252

252-
async fetchMeta(cachedFetch, ref, links) {
253+
async fetchMeta(cachedFetch, ref, links, options = {}) {
253254
let res: BitbucketRepoResponse | null = null
254255
try {
255256
const { data } = await cachedFetch<BitbucketRepoResponse>(
256257
`https://api.bitbucket.org/2.0/repositories/${ref.owner}/${ref.repo}`,
257-
{ headers: { 'User-Agent': 'npmx' } },
258+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
258259
REPO_META_TTL,
259260
)
260261
res = data
@@ -307,12 +308,12 @@ const codebergAdapter: ProviderAdapter = {
307308
}
308309
},
309310

310-
async fetchMeta(cachedFetch, ref, links) {
311+
async fetchMeta(cachedFetch, ref, links, options = {}) {
311312
let res: GiteaRepoResponse | null = null
312313
try {
313314
const { data } = await cachedFetch<GiteaRepoResponse>(
314315
`https://codeberg.org/api/v1/repos/${ref.owner}/${ref.repo}`,
315-
{ headers: { 'User-Agent': 'npmx' } },
316+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
316317
REPO_META_TTL,
317318
)
318319
res = data
@@ -365,12 +366,12 @@ const giteeAdapter: ProviderAdapter = {
365366
}
366367
},
367368

368-
async fetchMeta(cachedFetch, ref, links) {
369+
async fetchMeta(cachedFetch, ref, links, options = {}) {
369370
let res: GiteeRepoResponse | null = null
370371
try {
371372
const { data } = await cachedFetch<GiteeRepoResponse>(
372373
`https://gitee.com/api/v5/repos/${ref.owner}/${ref.repo}`,
373-
{ headers: { 'User-Agent': 'npmx' } },
374+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
374375
REPO_META_TTL,
375376
)
376377
res = data
@@ -452,7 +453,7 @@ const giteaAdapter: ProviderAdapter = {
452453
}
453454
},
454455

455-
async fetchMeta(cachedFetch, ref, links) {
456+
async fetchMeta(cachedFetch, ref, links, options = {}) {
456457
if (!ref.host) return null
457458

458459
// Note: Generic Gitea instances may not be in the allowlist,
@@ -461,7 +462,7 @@ const giteaAdapter: ProviderAdapter = {
461462
try {
462463
const { data } = await cachedFetch<GiteaRepoResponse>(
463464
`https://${ref.host}/api/v1/repos/${ref.owner}/${ref.repo}`,
464-
{ headers: { 'User-Agent': 'npmx' } },
465+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
465466
REPO_META_TTL,
466467
)
467468
res = data
@@ -564,13 +565,16 @@ const tangledAdapter: ProviderAdapter = {
564565
}
565566
},
566567

567-
async fetchMeta(cachedFetch, ref, links) {
568+
async fetchMeta(cachedFetch, ref, links, options = {}) {
568569
// Tangled doesn't have a public JSON API, but we can scrape the star count
569570
// from the HTML page (it's in the hx-post URL as countHint=N)
570571
try {
571572
const { data: html } = await cachedFetch<string>(
572573
`https://tangled.org/${ref.owner}/${ref.repo}`,
573-
{ headers: { 'User-Agent': 'npmx', 'Accept': 'text/html' } },
574+
{
575+
headers: { 'User-Agent': 'npmx', 'Accept': 'text/html', ...options.headers },
576+
...options,
577+
},
574578
REPO_META_TTL,
575579
)
576580
// Extracts the at-uri used in atproto
@@ -640,12 +644,12 @@ const radicleAdapter: ProviderAdapter = {
640644
}
641645
},
642646

643-
async fetchMeta(cachedFetch, ref, links) {
647+
async fetchMeta(cachedFetch, ref, links, options = {}) {
644648
let res: RadicleProjectResponse | null = null
645649
try {
646650
const { data } = await cachedFetch<RadicleProjectResponse>(
647651
`https://seed.radicle.at/api/v1/projects/${ref.repo}`,
648-
{ headers: { 'User-Agent': 'npmx' } },
652+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
649653
REPO_META_TTL,
650654
)
651655
res = data
@@ -704,14 +708,14 @@ const forgejoAdapter: ProviderAdapter = {
704708
}
705709
},
706710

707-
async fetchMeta(cachedFetch, ref, links) {
711+
async fetchMeta(cachedFetch, ref, links, options = {}) {
708712
if (!ref.host) return null
709713

710714
let res: GiteaRepoResponse | null = null
711715
try {
712716
const { data } = await cachedFetch<GiteaRepoResponse>(
713717
`https://${ref.host}/api/v1/repos/${ref.owner}/${ref.repo}`,
714-
{ headers: { 'User-Agent': 'npmx' } },
718+
{ headers: { 'User-Agent': 'npmx', ...options.headers }, ...options },
715719
REPO_META_TTL,
716720
)
717721
res = data
@@ -766,15 +770,15 @@ export function useRepoMeta(repositoryUrl: MaybeRefOrGetter<string | null | unde
766770
repoRef.value
767771
? `repo-meta:${repoRef.value.provider}:${repoRef.value.owner}/${repoRef.value.repo}`
768772
: 'repo-meta:none',
769-
async () => {
773+
async (_nuxtApp, { signal }) => {
770774
const ref = repoRef.value
771775
if (!ref) return null
772776

773777
const adapter = providers.find(provider => provider.id === ref.provider)
774778
if (!adapter) return null
775779

776780
const links = adapter.links(ref)
777-
return await adapter.fetchMeta(cachedFetch, ref, links)
781+
return await adapter.fetchMeta(cachedFetch, ref, links, { signal })
778782
},
779783
)
780784

0 commit comments

Comments
 (0)