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
129 changes: 52 additions & 77 deletions app/components/Package/Install/AdditionalCommands.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { JsrPackageInfo } from '#shared/types/jsr'
import type { DevDependencySuggestion } from '#shared/utils/dev-dependency'
import type { PackageManagerId } from '~/utils/install-command'
import { getPackageManagerConfig } from '~/utils/install-command'

const props = defineProps<{
packageName: string
Expand All @@ -16,6 +17,17 @@ const props = defineProps<{
selectedPm: PackageManagerId
}>()

const isMounted = ref(false)
onMounted(() => {
isMounted.value = true
})

const resolvedPm = computed(() => {
return isMounted.value ? props.selectedPm : 'npm'
})

const selectedPackageManagerConfig = computed(() => getPackageManagerConfig(resolvedPm.value))

// Matches the summary row's copy button style (Package/Install/Dropdown.vue) for visual consistency.
const copyButtonClass =
'text-fg-muted bg-bg-subtle/80 border-border transition-all duration-150 hover:scale-105 active:scale-90 select-none'
Expand All @@ -24,41 +36,41 @@ const devDependencySuggestion = computed(
() => props.devDependencySuggestion ?? { recommended: false as const },
)

function getDevInstallPartsForPM(pmId: PackageManagerId) {
function getDevInstallParts() {
return getInstallCommandParts({
packageName: props.packageName,
packageManager: pmId,
packageManager: selectedPackageManagerConfig.value.id,
version: props.installVersionOverride ?? props.requestedVersion,
jsrInfo: props.jsrInfo,
dev: true,
})
}

function getTypesInstallPartsForPM(pmId: PackageManagerId) {
function getTypesInstallParts() {
if (!props.typesPackageName) return []
return getInstallCommandParts({
packageName: props.typesPackageName,
packageManager: pmId,
packageManager: selectedPackageManagerConfig.value.id,
jsrInfo: null,
dev: true,
})
}

function getRunPartsForPM(pmId: PackageManagerId, command?: string) {
function getRunParts(command?: string) {
return getRunCommandParts({
packageName: props.packageName,
packageManager: pmId,
packageManager: selectedPackageManagerConfig.value.id,
jsrInfo: props.jsrInfo,
command,
isBinaryOnly: false,
})
}

function getCreatePartsForPM(pmId: PackageManagerId) {
function getCreateParts() {
if (!props.createPackageInfo) return []
return getExecuteCommandParts({
packageName: props.createPackageInfo.packageName,
packageManager: pmId,
packageManager: selectedPackageManagerConfig.value.id,
jsrInfo: null,
isCreatePackage: true,
})
Expand Down Expand Up @@ -148,21 +160,16 @@ function copyCreateCommand() {
<code
class="font-mono text-sm min-w-0 block truncate tracking-tight"
dir="ltr"
v-for="pm in packageManagers"
:key="pm.id"
:data-pm-additional-cmd="pm.id"
><span class="text-fg-subtle select-none">${{ ' ' }}</span
><span
v-for="(part, i) in getDevInstallPartsForPM(pm.id)"
:data-pm-additional-cmd="selectedPackageManagerConfig.id"
>
<span class="text-fg-subtle select-none">$ </span>
<span
v-for="(part, i) in getDevInstallParts()"
:key="i"
:class="
i === getDevInstallPartsForPM(pm.id).length - 1
? 'text-fg font-medium'
: 'text-fg-muted'
"
:class="i === getDevInstallParts().length - 1 ? 'text-fg font-medium' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
>
</code>
</div>
</div>

Expand All @@ -187,21 +194,18 @@ function copyCreateCommand() {
<code
class="font-mono text-sm min-w-0 truncate tracking-tight"
dir="ltr"
v-for="pm in packageManagers"
:key="pm.id"
:data-pm-additional-cmd="pm.id"
><span class="text-fg-subtle select-none">${{ ' ' }}</span
><span
v-for="(part, i) in getTypesInstallPartsForPM(pm.id)"
:data-pm-additional-cmd="selectedPackageManagerConfig.id"
>
<span class="text-fg-subtle select-none">$ </span>
<span
v-for="(part, i) in getTypesInstallParts()"
:key="i"
:class="
i === getTypesInstallPartsForPM(pm.id).length - 1
? 'text-fg font-medium'
: 'text-fg-muted'
i === getTypesInstallParts().length - 1 ? 'text-fg font-medium' : 'text-fg-muted'
"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
>
</code>
<NuxtLink
:to="packageRoute(typesPackageName!)"
class="shrink-0 text-fg-subtle hover:text-fg-muted text-xs transition-colors focus-visible:outline-accent/70 rounded select-none -m-1 p-1"
Expand Down Expand Up @@ -234,21 +238,20 @@ function copyCreateCommand() {
<code
class="font-mono text-sm min-w-0 block truncate tracking-tight"
dir="ltr"
v-for="pm in packageManagers"
:key="pm.id"
:data-pm-additional-cmd="pm.id"
><span class="text-fg-subtle select-none">${{ ' ' }}</span
><span
v-for="(part, i) in getRunPartsForPM(pm.id, executableInfo?.primaryCommand)"
:data-pm-additional-cmd="selectedPackageManagerConfig.id"
>
<span class="text-fg-subtle select-none">$ </span>
<span
v-for="(part, i) in getRunParts(executableInfo?.primaryCommand)"
:key="i"
:class="
i === getRunPartsForPM(pm.id, executableInfo?.primaryCommand).length - 1
i === getRunParts(executableInfo?.primaryCommand).length - 1
? 'text-fg font-medium'
: 'text-fg-muted'
"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
>
</code>
</div>
</div>

Expand Down Expand Up @@ -285,44 +288,16 @@ function copyCreateCommand() {
<code
class="font-mono text-sm min-w-0 block truncate tracking-tight"
dir="ltr"
v-for="pm in packageManagers"
:key="pm.id"
:data-pm-additional-cmd="pm.id"
><span class="text-fg-subtle select-none">${{ ' ' }}</span
><span
v-for="(part, i) in getCreatePartsForPM(pm.id)"
:data-pm-additional-cmd="selectedPackageManagerConfig.id"
>
<span class="text-fg-subtle select-none">$ </span>
<span
v-for="(part, i) in getCreateParts()"
:key="i"
:class="
i === getCreatePartsForPM(pm.id).length - 1 ? 'text-fg font-medium' : 'text-fg-muted'
"
:class="i === getCreateParts().length - 1 ? 'text-fg font-medium' : 'text-fg-muted'"
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
></code
>
>
</code>
</div>
</div>
</template>
<style>
/* Hide all variants by default when preference is set */
:root[data-pm] [data-pm-additional-cmd] {
display: none;
}

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

/* Fallback: when no data-pm is set (SSR initial), show npm as default */
:root:not([data-pm]) [data-pm-additional-cmd]:not([data-pm-additional-cmd='npm']) {
display: none;
}
</style>
44 changes: 14 additions & 30 deletions app/components/Package/Install/Dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { JsrPackageInfo } from '#shared/types/jsr'
import type { SlimPackumentVersion } from '#shared/types'
import type { DevDependencySuggestion } from '#shared/utils/dev-dependency'
import type { PublishSecurityDowngrade } from '~/utils/publish-security'
import { getPackageManagerConfig } from '~/utils/install-command'

const props = defineProps<{
packageName: string
Expand All @@ -19,6 +20,11 @@ const props = defineProps<{
headingId: string
}>()

const isMounted = ref(false)
onMounted(() => {
isMounted.value = true
})

const { selectedPM, showTypesInInstall, copied, copyInstallCommand } = useInstallCommand(
() => props.packageName,
() => props.requestedVersion ?? null,
Expand All @@ -27,6 +33,12 @@ const { selectedPM, showTypesInInstall, copied, copyInstallCommand } = useInstal
() => props.installVersionOverride ?? null,
)

const resolvedPM = computed(() => {
return isMounted.value ? selectedPM.value : 'npm'
})

const selectedPackageManagerConfig = computed(() => getPackageManagerConfig(resolvedPM.value))

const { announce } = useCommandPalette()
const { polite } = useAnnouncer()
async function copyInstallCommandWithAnnounce() {
Expand Down Expand Up @@ -137,14 +149,11 @@ onPrehydrate(el => {
<code
class="font-mono text-sm min-w-0 flex-1 truncate tracking-tight cursor-text select-text"
dir="ltr"
v-for="pm in packageManagers"
:key="pm.id"
:data-pm-cmd="pm.id"
>
<span
v-for="(part, i) in getInstallCommandParts({
packageName: props.packageName,
packageManager: pm.id,
packageManager: selectedPackageManagerConfig.id,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
version: props.installVersionOverride ?? props.requestedVersion,
jsrInfo: props.jsrInfo,
})"
Expand Down Expand Up @@ -178,36 +187,11 @@ onPrehydrate(el => {
:show-types="showTypesInInstall"
:executable-info="executableInfo"
:create-package-info="createPackageInfo"
:selected-pm="selectedPM"
:selected-pm="resolvedPM"
/>
</div>
</div>
</div>
</div>
</section>
</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'],
:root[data-pm='ni'] [data-pm-cmd='ni'],
:root[data-pm='upm'] [data-pm-cmd='upm'] {
display: inline;
}

/* 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>
9 changes: 3 additions & 6 deletions test/e2e/create-command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ test.describe('Create Command', () => {

const createCommandSection = page.locator('[data-testid="create-command"]').first()
await expect(createCommandSection).toBeVisible()
const activePm = await page.locator('[data-pm]').first().getAttribute('data-pm')
const commandRow = createCommandSection.locator(`[data-pm-additional-cmd="${activePm}"]`)
const commandRow = createCommandSection.locator('code[data-pm-additional-cmd]')
await expect(commandRow).toBeVisible()
await expect(commandRow).toContainText(/create vite/i)

Expand All @@ -42,8 +41,7 @@ test.describe('Create Command', () => {

const createCommandSection = page.locator('[data-testid="create-command"]').first()
await expect(createCommandSection).toBeVisible()
const activePm = await page.locator('[data-pm]').first().getAttribute('data-pm')
const commandRow = createCommandSection.locator(`[data-pm-additional-cmd="${activePm}"]`)
const commandRow = createCommandSection.locator('code[data-pm-additional-cmd]')
await expect(commandRow).toBeVisible()
await expect(commandRow).toContainText(/create next-app/i)

Expand All @@ -63,8 +61,7 @@ test.describe('Create Command', () => {
// nuxt has create-nuxt package, so command is "npm create nuxt"
const createCommandSection = page.locator('[data-testid="create-command"]').first()
await expect(createCommandSection).toBeVisible()
const activePm = await page.locator('[data-pm]').first().getAttribute('data-pm')
const commandRow = createCommandSection.locator(`[data-pm-additional-cmd="${activePm}"]`)
const commandRow = createCommandSection.locator('code[data-pm-additional-cmd]')
await expect(commandRow).toBeVisible()
await expect(commandRow).toContainText(/create nuxt/i)
})
Expand Down
Loading