Skip to content
Open
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
34 changes: 4 additions & 30 deletions app/components/Terminal/Execute.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PackageManagerId } from '~/utils/install-command'

/**
* A terminal-style execute command display for binary-only packages.
* Renders all package manager variants with CSS-based visibility.
* Renders the currently selected package manager command.
*/

const props = defineProps<{
Expand All @@ -14,6 +14,7 @@ const props = defineProps<{
}>()

const selectedPM = useSelectedPackageManager()
const selectedPackageManager = computed(() => getPackageManagerConfig(selectedPM.value))

// Generate execute command parts for a specific package manager
function getExecutePartsForPM(pmId: PackageManagerId) {
Expand Down Expand Up @@ -52,17 +53,14 @@ const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
</div>
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1">
<!-- Execute command - render all PM variants, CSS controls visibility -->
<div
v-for="pm in packageManagers"
:key="`execute-${pm.id}`"
:data-pm-cmd="pm.id"
:data-pm-cmd="selectedPackageManager.id"
class="flex items-center gap-2 group/executecmd"
>
<span class="text-fg-subtle font-mono text-sm select-none">$</span>
<code class="font-mono text-sm"
><span
v-for="(part, i) in getExecutePartsForPM(pm.id)"
v-for="(part, i) in getExecutePartsForPM(selectedPackageManager.id)"
:key="i"
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
Expand All @@ -81,27 +79,3 @@ const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
</div>
</div>
</template>

<style>
/* Hide all variants by default when preference is set */
:root[data-pm] [data-pm-cmd] {
display: none;
}

/* Show only the matching package manager command */
:root[data-pm='npm'] [data-pm-cmd='npm'],
:root[data-pm='pnpm'] [data-pm-cmd='pnpm'],
:root[data-pm='yarn'] [data-pm-cmd='yarn'],
:root[data-pm='bun'] [data-pm-cmd='bun'],
:root[data-pm='deno'] [data-pm-cmd='deno'],
:root[data-pm='vlt'] [data-pm-cmd='vlt'],
:root[data-pm='vp'] [data-pm-cmd='vp'],
:root[data-pm='nub'] [data-pm-cmd='nub'] {
display: flex;
}

/* Fallback: when no data-pm is set (SSR initial), show npm as default */
:root:not([data-pm]) [data-pm-cmd]:not([data-pm-cmd='npm']) {
display: none;
}
</style>
67 changes: 14 additions & 53 deletions app/components/Terminal/Install.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const { copied: createCopied, copy: copyCreate } = useClipboard({ copiedDuring:
const copyCreateCommand = () => copyCreate(getFullCreateCommand())

const { copied: devInstallCopied, copy: copyDevInstall } = useClipboard({ copiedDuring: 2000 })
const selectedPackageManager = computed(() => getPackageManagerConfig(selectedPM.value))
const copyDevInstallCommand = () =>
copyDevInstall(
getInstallCommand({
Expand Down Expand Up @@ -219,17 +220,14 @@ useCommandPaletteContextCommands(
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
</div>
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1 overflow-x-auto" dir="ltr">
<!-- Install command - render all PM variants, CSS controls visibility -->
<div
v-for="pm in packageManagers"
:key="`install-${pm.id}`"
:data-pm-cmd="pm.id"
:data-pm-cmd="selectedPackageManager.id"
class="flex items-center gap-2 group/installcmd min-w-0"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm min-w-0"
><span
v-for="(part, i) in getInstallPartsForPM(pm.id)"
v-for="(part, i) in getInstallPartsForPM(selectedPackageManager.id)"
:key="i"
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
Expand All @@ -253,15 +251,13 @@ useCommandPaletteContextCommands(
>
</div>
<div
v-for="pm in packageManagers"
:key="`install-dev-${pm.id}`"
:data-pm-cmd="pm.id"
:data-pm-cmd="selectedPackageManager.id"
class="flex items-center gap-2 group/devinstallcmd min-w-0"
>
<span class="text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm min-w-0"
><span
v-for="(part, i) in getDevInstallPartsForPM(pm.id)"
v-for="(part, i) in getDevInstallPartsForPM(selectedPackageManager.id)"
:key="i"
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
Expand All @@ -283,16 +279,11 @@ useCommandPaletteContextCommands(

<!-- @types package install - render all PM variants when types package exists -->
<template v-if="typesPackageName && showTypesInInstall">
<div
v-for="pm in packageManagers"
:key="`types-${pm.id}`"
:data-pm-cmd="pm.id"
class="flex items-center gap-2 min-w-0"
>
<div :data-pm-cmd="selectedPackageManager.id" class="flex items-center gap-2 min-w-0">
<span class="self-start text-fg-subtle font-mono text-sm select-none shrink-0">$</span>
<code class="font-mono text-sm min-w-0"
><span
v-for="(part, i) in getTypesInstallPartsForPM(pm.id)"
v-for="(part, i) in getTypesInstallPartsForPM(selectedPackageManager.id)"
:key="i"
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
Expand All @@ -319,15 +310,16 @@ useCommandPaletteContextCommands(
</div>

<div
v-for="pm in packageManagers"
:key="`run-${pm.id}`"
:data-pm-cmd="pm.id"
:data-pm-cmd="selectedPackageManager.id"
class="flex items-center gap-2 group/runcmd"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none">$</span>
<code class="font-mono text-sm"
><span
v-for="(part, i) in getRunPartsForPM(pm.id, executableInfo?.primaryCommand)"
v-for="(part, i) in getRunPartsForPM(
selectedPackageManager.id,
executableInfo?.primaryCommand,
)"
:key="i"
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
Expand Down Expand Up @@ -364,15 +356,13 @@ useCommandPaletteContextCommands(
</div>

<div
v-for="pm in packageManagers"
:key="`create-${pm.id}`"
:data-pm-cmd="pm.id"
:data-pm-cmd="selectedPackageManager.id"
class="flex items-center gap-2 group/createcmd"
>
<span class="self-start text-fg-subtle font-mono text-sm select-none">$</span>
<code class="font-mono text-sm"
><span
v-for="(part, i) in getCreatePartsForPM(pm.id)"
v-for="(part, i) in getCreatePartsForPM(selectedPackageManager.id)"
:key="i"
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
Expand All @@ -394,32 +384,3 @@ useCommandPaletteContextCommands(
</div>
</div>
</template>

<style>
/*
* Package manager command visibility based on data-pm attribute on <html>.
* All variants are rendered; CSS shows only the selected one.
*/

/* Hide all variants by default when preference is set */
:root[data-pm] [data-pm-cmd] {
display: none;
}

/* Show only the matching package manager command */
:root[data-pm='npm'] [data-pm-cmd='npm'],
:root[data-pm='pnpm'] [data-pm-cmd='pnpm'],
:root[data-pm='yarn'] [data-pm-cmd='yarn'],
:root[data-pm='bun'] [data-pm-cmd='bun'],
:root[data-pm='deno'] [data-pm-cmd='deno'],
:root[data-pm='vlt'] [data-pm-cmd='vlt'],
:root[data-pm='vp'] [data-pm-cmd='vp'],
:root[data-pm='nub'] [data-pm-cmd='nub'] {
display: flex;
}

/* Fallback: when no data-pm is set (SSR initial), show npm as default */
:root:not([data-pm]) [data-pm-cmd]:not([data-pm-cmd='npm']) {
display: none;
}
</style>
6 changes: 2 additions & 4 deletions app/composables/useInstallCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ export function useInstallCommand(
const typesInstallCommandParts = computed(() => {
const types = toValue(typesPackageName)
if (!types) return []
const pm = packageManagers.find(p => p.id === selectedPM.value)
if (!pm) return []
const pm = getPackageManagerConfig(selectedPM.value)

const pkgSpec = selectedPM.value === 'deno' ? `npm:${types}` : types

Expand All @@ -69,8 +68,7 @@ export function useInstallCommand(
return installCommand.value
}

const pm = packageManagers.find(p => p.id === selectedPM.value)
if (!pm) return installCommand.value
const pm = getPackageManagerConfig(selectedPM.value)

const pkgSpec = selectedPM.value === 'deno' ? `npm:${types}` : types

Expand Down
14 changes: 14 additions & 0 deletions app/utils/install-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ export const packageManagers = [

export type PackageManagerId = (typeof packageManagers)[number]['id']

export function getPackageManagerConfig(packageManager: PackageManagerId) {
const selectedPackageManager = packageManagers.find(pm => pm.id === packageManager)
if (selectedPackageManager) {
return selectedPackageManager
}

const defaultPackageManager = packageManagers.find(pm => pm.id === 'npm')
if (!defaultPackageManager) {
throw new Error('Default package manager configuration is missing.')
}

return defaultPackageManager
}

export interface InstallCommandOptions {
packageName: string
packageManager: PackageManagerId
Expand Down
53 changes: 53 additions & 0 deletions test/nuxt/components/Terminal.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import TerminalExecute from '~/components/Terminal/Execute.vue'
import TerminalInstall from '~/components/Terminal/Install.vue'

describe('Terminal components', () => {
beforeEach(() => {
localStorage.clear()
const selectedPackageManager = useSelectedPackageManager()
selectedPackageManager.value = 'npm'
})

it('renders only the selected package manager in TerminalExecute', async () => {
const selectedPackageManager = useSelectedPackageManager()
selectedPackageManager.value = 'nub'

const component = await mountSuspended(TerminalExecute, {
props: { packageName: 'create-vite' },
})

const renderedCommands = component.findAll('[data-pm-cmd]')

expect(renderedCommands).toHaveLength(1)
expect(renderedCommands[0]?.attributes('data-pm-cmd')).toBe('nub')
expect(component.text()).toContain('nubx create-vite')
})

it('renders only the selected package manager across all TerminalInstall sections', async () => {
const selectedPackageManager = useSelectedPackageManager()
selectedPackageManager.value = 'nub'

const component = await mountSuspended(TerminalInstall, {
props: {
packageName: 'vue',
typesPackageName: '@types/vue',
devDependencySuggestion: { recommended: true },
executableInfo: { hasExecutable: true, primaryCommand: 'vue' },
createPackageInfo: { packageName: 'create-vue' },
},
})

const renderedCommands = component.findAll('[data-pm-cmd]')

expect(renderedCommands).toHaveLength(5)
expect(renderedCommands.every(command => command.attributes('data-pm-cmd') === 'nub')).toBe(
true,
)
expect(component.text()).toContain('nub add vue')
expect(component.text()).toContain('nub add -D @types/vue')
expect(component.text()).toContain('nubx vue')
expect(component.text()).toContain('nub create vue')
})
})
Loading