diff --git a/app/components/ConnectorModal.vue b/app/components/ConnectorModal.vue
index 5feb6620ae..de81b419f7 100644
--- a/app/components/ConnectorModal.vue
+++ b/app/components/ConnectorModal.vue
@@ -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
@@ -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) {
@@ -103,9 +125,24 @@ watch(open, isOpen => {
Run the connector on your machine to enable admin features:
-
+
$
- npx npmx-connector
+ {{ executeNpmxConnectorCommand }}
+
Then paste the token shown in your terminal:
diff --git a/app/composables/useSelectedPackageManager.ts b/app/composables/useSelectedPackageManager.ts
new file mode 100644
index 0000000000..f4062b201e
--- /dev/null
+++ b/app/composables/useSelectedPackageManager.ts
@@ -0,0 +1,5 @@
+import { useLocalStorage } from '@vueuse/core'
+
+export function useSelectedPackageManager() {
+ return useLocalStorage
('npmx-pm', 'npm')
+}
diff --git a/app/pages/[...package].vue b/app/pages/[...package].vue
index 85a0449622..b11e0abc41 100644
--- a/app/pages/[...package].vue
+++ b/app/pages/[...package].vue
@@ -190,19 +190,7 @@ function hasProvenance(version: PackumentVersion | null): boolean {
return !!dist.attestations
}
-// Persist package manager preference in localStorage
-const selectedPM = ref('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 []
diff --git a/app/utils/install-command.ts b/app/utils/install-command.ts
index 0d42feb0d8..a27d98f0ae 100644
--- a/app/utils/install-command.ts
+++ b/app/utils/install-command.ts
@@ -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']
@@ -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)]
+}