Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions app/components/ConnectorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disco

const tokenInput = ref('')
const portInput = ref('31415')
const copied = ref(false)

async function handleConnect() {
const port = Number.parseInt(portInput.value, 10) || 31415
Expand All @@ -20,6 +21,27 @@ function handleDisconnect() {
disconnect()
}

function copyCommand() {
let command = executeNpmxConnectorCommand.value
if (portInput.value !== '31415') {
command += ` --port ${portInput.value}`
}
navigator.clipboard.writeText(command)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
}

const selectedPM = useSelectedPackageManager()

const executeNpmxConnectorCommand = computed(() => {
return getExecuteCommand({
packageName: 'npmx-connector',
packageManager: selectedPM.value,
})
})

// Reset form when modal opens
watch(open, isOpen => {
if (isOpen) {
Expand Down Expand Up @@ -103,9 +125,24 @@ watch(open, isOpen => {
Run the connector on your machine to enable admin features:
</p>

<div class="p-3 bg-[#0d0d0d] border border-border rounded-lg font-mono text-sm">
<div
class="flex items-center p-3 bg-[#0d0d0d] border border-border rounded-lg font-mono text-sm"
>
<span class="text-fg-subtle">$</span>
<span class="text-fg ml-2">npx&nbsp;npmx-connector</span>
<span class="text-fg ml-2">{{ executeNpmxConnectorCommand }}</span>
<button
type="button"
:aria-label="copied ? 'Copied' : 'Copy command'"
class="ml-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"
@click="copyCommand"
>
<span v-if="!copied" class="i-carbon-copy block w-5 h-5" aria-hidden="true" />
<span
v-else
class="i-carbon-checkmark block w-5 h-5 text-green-500"
aria-hidden="true"
/>
</button>
</div>

<p class="text-sm text-fg-muted">Then paste the token shown in your terminal:</p>
Expand Down
5 changes: 5 additions & 0 deletions app/composables/useSelectedPackageManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useLocalStorage } from '@vueuse/core'

export function useSelectedPackageManager() {
return useLocalStorage<PackageManagerId>('npmx-pm', 'npm')
}
14 changes: 1 addition & 13 deletions app/pages/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -190,19 +190,7 @@ function hasProvenance(version: PackumentVersion | null): boolean {
return !!dist.attestations
}

// Persist package manager preference in localStorage
const selectedPM = ref<PackageManagerId>('npm')

onMounted(() => {
const stored = localStorage.getItem('npmx-pm')
if (stored && packageManagers.some(pm => pm.id === stored)) {
selectedPM.value = stored as PackageManagerId
}
})

watch(selectedPM, value => {
localStorage.setItem('npmx-pm', value)
})
const selectedPM = useSelectedPackageManager()

const installCommandParts = computed(() => {
if (!pkg.value) return []
Expand Down
22 changes: 16 additions & 6 deletions app/utils/install-command.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { JsrPackageInfo } from '#shared/types/jsr'

export const packageManagers = [
{ id: 'npm', label: 'npm', action: 'install' },
{ id: 'pnpm', label: 'pnpm', action: 'add' },
{ id: 'yarn', label: 'yarn', action: 'add' },
{ id: 'bun', label: 'bun', action: 'add' },
{ id: 'deno', label: 'deno', action: 'add' },
{ id: 'vlt', label: 'vlt', action: 'install' },
{ id: 'npm', label: 'npm', action: 'install', execute: 'npx' },
{ id: 'pnpm', label: 'pnpm', action: 'add', execute: 'pnpm dlx' },
{ id: 'yarn', label: 'yarn', action: 'add', execute: 'yarn dlx' },
{ id: 'bun', label: 'bun', action: 'add', execute: 'bunx' },
{ id: 'deno', label: 'deno', action: 'add', execute: 'deno run' },
{ id: 'vlt', label: 'vlt', action: 'install', execute: 'vlt x' },
] as const

export type PackageManagerId = (typeof packageManagers)[number]['id']
Expand Down Expand Up @@ -59,3 +59,13 @@ export function getInstallCommandParts(options: InstallCommandOptions): string[]

return [pm.label, pm.action, `${spec}${version}`]
}

export function getExecuteCommand(options: InstallCommandOptions): string {
return getExecuteCommandParts(options).join(' ')
}

export function getExecuteCommandParts(options: InstallCommandOptions): string[] {
const pm = packageManagers.find(p => p.id === options.packageManager)
if (!pm) return []
return [pm.execute, getPackageSpecifier(options)]
}