-
-
Notifications
You must be signed in to change notification settings - Fork 425
feat: add deprecate command with custom reason #921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2231fd7
f9d0df5
36d03de
bb37228
fb860dc
58e112d
2159074
2530ba9
d80dd64
0c221f5
4264814
1b38e9e
8fa2405
ea683a6
fe0420c
ec631d9
62e8c99
a8d922b
cb12a0a
baf0822
5cc1b0a
75f9c00
19c82f3
5722aac
cd70766
4b9a900
6e44c34
7f4ec8e
caeaebd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ test-results | |
|
|
||
| # Test coverage | ||
| coverage/ | ||
| *.junit.xml | ||
|
|
||
| # Playwright | ||
| playwright-report/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,274 @@ | ||
| <script setup lang="ts"> | ||
| import type { NewOperation } from '~/composables/useConnector' | ||
| import * as v from 'valibot' | ||
| import { PackageDeprecateParamsSchema } from '#shared/schemas/package' | ||
|
|
||
| const DEPRECATE_MESSAGE_MAX_LENGTH = 500 | ||
|
|
||
| const props = withDefaults( | ||
| defineProps<{ | ||
| packageName: string | ||
| version?: string | ||
| /** When true, the package or version is already deprecated; form is hidden and state cannot be changed. */ | ||
| isAlreadyDeprecated?: boolean | ||
| /** Version strings that are already deprecated (computed by parent from pkg.versions). */ | ||
| deprecatedVersions?: string[] | ||
| }>(), | ||
| { version: '', isAlreadyDeprecated: false, deprecatedVersions: () => [] }, | ||
| ) | ||
|
|
||
| const { t } = useI18n() | ||
| const { isConnected, state, addOperation, approveOperation, executeOperations, refreshState } = | ||
| useConnector() | ||
|
|
||
| const deprecateMessage = ref('') | ||
| const deprecateVersion = ref(props.version) | ||
| const isDeprecating = shallowRef(false) | ||
| const deprecateSuccess = shallowRef(false) | ||
| const deprecateError = shallowRef<string | null>(null) | ||
|
|
||
| const connectorModal = useModal('connector-modal') | ||
|
|
||
| const modalTitle = computed(() => | ||
| deprecateVersion.value | ||
| ? `${t('package.deprecation.modal.title')} ${props.packageName}@${deprecateVersion.value}` | ||
| : `${t('package.deprecation.modal.title')} ${props.packageName}`, | ||
| ) | ||
|
|
||
| /** True when the user has entered a version in the form that is already deprecated. */ | ||
| const isSelectedVersionDeprecated = computed(() => { | ||
| const v = deprecateVersion.value.trim() | ||
|
Check warning on line 40 in app/components/Package/DeprecatePackageModal.vue
|
||
| if (!v || !props.deprecatedVersions.length) return false | ||
| return props.deprecatedVersions.includes(v) | ||
| }) | ||
|
|
||
| async function handleDeprecate() { | ||
| if (props.isAlreadyDeprecated || isSelectedVersionDeprecated.value) return | ||
| const message = deprecateMessage.value.trim() | ||
| if (!isConnected.value) return | ||
|
|
||
| const params: Record<string, string> = { | ||
| pkg: props.packageName, | ||
| message, | ||
| } | ||
| if (deprecateVersion.value.trim()) { | ||
| params.version = deprecateVersion.value.trim() | ||
| } | ||
|
|
||
| const parsed = v.safeParse(PackageDeprecateParamsSchema, params) | ||
| if (!parsed.success) { | ||
| const firstIssue = parsed.issues[0] | ||
| const path = firstIssue?.path?.map(p => p.key).join('.') || '' | ||
| const message = firstIssue?.message || 'Validation failed' | ||
|
Check warning on line 62 in app/components/Package/DeprecatePackageModal.vue
|
||
| deprecateError.value = path ? `${path}: ${message}` : message | ||
| return | ||
| } | ||
|
|
||
| isDeprecating.value = true | ||
| deprecateError.value = null | ||
|
|
||
| try { | ||
| const escapedMessage = parsed.output.message.replace(/"/g, '\\"') | ||
| const command = parsed.output.version | ||
| ? `npm deprecate ${parsed.output.pkg}@${parsed.output.version} "${escapedMessage}"` | ||
| : `npm deprecate ${parsed.output.pkg} "${escapedMessage}"` | ||
|
|
||
| const operation = await addOperation({ | ||
| type: 'package:deprecate', | ||
| params: { | ||
| pkg: parsed.output.pkg, | ||
| message: parsed.output.message, | ||
| ...(parsed.output.version && { version: parsed.output.version }), | ||
| }, | ||
| description: parsed.output.version | ||
| ? `Deprecate ${parsed.output.pkg}@${parsed.output.version}` | ||
| : `Deprecate ${parsed.output.pkg}`, | ||
| command, | ||
| } as NewOperation) | ||
|
|
||
| if (!operation) { | ||
| throw new Error('Failed to create operation') | ||
| } | ||
|
|
||
| await approveOperation(operation.id) | ||
| await executeOperations() | ||
| await refreshState() | ||
|
|
||
| const completedOp = state.value.operations.find(op => op.id === operation.id) | ||
| if (completedOp?.status === 'completed') { | ||
| deprecateSuccess.value = true | ||
| } else if (completedOp?.status === 'failed') { | ||
| if (completedOp.result?.requiresOtp) { | ||
| close() | ||
| connectorModal.open() | ||
| } else { | ||
| deprecateError.value = completedOp.result?.stderr || t('common.try_again') | ||
| } | ||
| } else { | ||
| close() | ||
| connectorModal.open() | ||
| } | ||
| } catch (err) { | ||
| deprecateError.value = err instanceof Error ? err.message : t('common.try_again') | ||
| } finally { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| isDeprecating.value = false | ||
| } | ||
| } | ||
|
|
||
| const dialogRef = useTemplateRef('dialogRef') | ||
|
|
||
| function open() { | ||
| deprecateError.value = null | ||
| deprecateSuccess.value = false | ||
| deprecateMessage.value = '' | ||
| deprecateVersion.value = props.version ?? '' | ||
| dialogRef.value?.showModal() | ||
| } | ||
|
|
||
| function close() { | ||
| dialogRef.value?.close() | ||
| } | ||
|
|
||
| defineExpose({ open, close }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <Modal ref="dialogRef" :modal-title="modalTitle" id="deprecate-package-modal" class="max-w-md"> | ||
| <!-- Already deprecated: entire module read-only, hint only, no form / no deprecate button --> | ||
| <div v-if="isAlreadyDeprecated" class="space-y-4" aria-readonly="true"> | ||
| <div | ||
| class="flex items-center gap-3 p-4 bg-amber-500/10 border border-amber-500/20 rounded-lg" | ||
| role="status" | ||
| > | ||
| <span class="i-carbon-warning-alt text-amber-500 w-6 h-6 shrink-0" aria-hidden="true" /> | ||
| <div> | ||
| <p class="font-mono text-sm text-fg"> | ||
| {{ | ||
| deprecateVersion | ||
| ? $t('package.deprecation.modal.already_deprecated_version') | ||
| : $t('package.deprecation.modal.already_deprecated') | ||
| }} | ||
| </p> | ||
| <p class="text-xs text-fg-muted mt-0.5"> | ||
| {{ $t('package.deprecation.modal.already_deprecated_detail') }} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <button | ||
| type="button" | ||
| class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-accent/70" | ||
| @click="close" | ||
| > | ||
| {{ $t('common.close') }} | ||
| </button> | ||
|
Comment on lines
+157
to
+163
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove inline focus-visible utilities on buttons. 💡 Suggested fix- class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
+ class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover"- class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
+ class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover"- class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
+ class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed"Also applies to: 199-205, 272-276 |
||
| </div> | ||
|
|
||
| <!-- Success state --> | ||
| <div v-else-if="deprecateSuccess" class="space-y-4"> | ||
| <div | ||
| class="flex items-center gap-3 p-4 bg-green-500/10 border border-green-500/20 rounded-lg" | ||
| > | ||
| <span class="i-carbon-checkmark-filled text-green-500 w-6 h-6" aria-hidden="true" /> | ||
| <div> | ||
| <p class="font-mono text-sm text-fg">{{ $t('package.deprecation.modal.success') }}</p> | ||
| <p class="text-xs text-fg-muted"> | ||
| {{ $t('package.deprecation.modal.success_detail') }} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <button | ||
| type="button" | ||
| class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-accent/70" | ||
| @click="close" | ||
| > | ||
| {{ $t('common.close') }} | ||
| </button> | ||
| </div> | ||
|
|
||
| <!-- Form --> | ||
| <div v-else class="space-y-4"> | ||
| <!-- Hint when user-entered version is already deprecated --> | ||
| <div | ||
| v-if="isSelectedVersionDeprecated" | ||
| class="flex items-center gap-3 p-4 bg-amber-500/10 border border-amber-500/20 rounded-lg" | ||
| role="status" | ||
| > | ||
| <span class="i-carbon-warning-alt text-amber-500 w-6 h-6 shrink-0" aria-hidden="true" /> | ||
| <div> | ||
| <p class="font-mono text-sm text-fg"> | ||
| {{ $t('package.deprecation.modal.already_deprecated_version') }} | ||
| </p> | ||
| <p class="text-xs text-fg-muted mt-0.5"> | ||
| {{ $t('package.deprecation.modal.already_deprecated_detail') }} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <label | ||
| for="deprecate-message" | ||
| class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5" | ||
| > | ||
| {{ $t('package.deprecation.modal.reason') }} | ||
| </label> | ||
| <textarea | ||
| id="deprecate-message" | ||
| v-model="deprecateMessage" | ||
| rows="3" | ||
| :maxlength="DEPRECATE_MESSAGE_MAX_LENGTH" | ||
| :disabled="isSelectedVersionDeprecated" | ||
| class="w-full appearance-none bg-bg-subtle border border-border font-mono text-sm leading-none px-3 py-2.5 rounded-lg text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)" | ||
| :placeholder="$t('package.deprecation.modal.reason_placeholder')" | ||
| :aria-describedby=" | ||
| deprecateMessage.length >= DEPRECATE_MESSAGE_MAX_LENGTH | ||
| ? 'deprecate-message-hint' | ||
| : undefined | ||
| " | ||
| /> | ||
| <p | ||
| v-if="deprecateMessage.length >= DEPRECATE_MESSAGE_MAX_LENGTH * 0.9" | ||
| id="deprecate-message-hint" | ||
| class="mt-1 text-xs text-fg-muted" | ||
| > | ||
| {{ deprecateMessage.length }} / {{ DEPRECATE_MESSAGE_MAX_LENGTH }} | ||
| </p> | ||
| </div> | ||
| <div> | ||
| <label | ||
| for="deprecate-version" | ||
| class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5" | ||
| > | ||
| {{ $t('package.deprecation.modal.version') }} | ||
| </label> | ||
| <InputBase | ||
| id="deprecate-version" | ||
| v-model="deprecateVersion" | ||
| type="text" | ||
| name="deprecate-version" | ||
| :disabled="isSelectedVersionDeprecated" | ||
| class="w-full" | ||
| size="md" | ||
| :placeholder="$t('package.deprecation.modal.version_placeholder')" | ||
| /> | ||
| </div> | ||
| <div | ||
| v-if="deprecateError" | ||
| role="alert" | ||
| class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md" | ||
| > | ||
| {{ deprecateError }} | ||
| </div> | ||
| <button | ||
| type="button" | ||
| :disabled="isDeprecating || !deprecateMessage.trim() || isSelectedVersionDeprecated" | ||
| class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" | ||
| @click="handleDeprecate" | ||
| > | ||
| {{ | ||
| isDeprecating | ||
| ? $t('package.deprecation.modal.deprecating') | ||
| : $t('package.deprecation.action') | ||
| }} | ||
| </button> | ||
| </div> | ||
| </Modal> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -320,6 +320,21 @@ const latestVersion = computed(() => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return pkg.value.versions[latestTag] ?? null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** True when the currently displayed version (or resolved version) is deprecated; used to hide deprecate button. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isCurrentVersionDeprecated = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (displayVersion.value?.deprecated) return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ver = resolvedVersion.value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return !!(ver && pkg.value?.versions?.[ver]?.deprecated) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Version strings that are already deprecated; passed to DeprecatePackageModal to avoid extra fetch. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const deprecatedVersions = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!pkg.value?.versions) return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Object.entries(pkg.value.versions) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter(([, metadata]) => !!metadata.deprecated) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(([version]) => version) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const deprecationNotice = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!displayVersion.value?.deprecated) return null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -341,6 +356,17 @@ const deprecationNoticeMessage = useMarkdown(() => ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text: deprecationNotice.value?.message ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { isConnected, npmUser } = useConnector() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const deprecateModal = useTemplateRef<{ open: () => void }>('deprecateModal') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isPackageOwner = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const maintainers = pkg.value?.maintainers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = npmUser.value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!maintainers?.length || !user) return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const userLower = user.toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return maintainers.some((m: { name?: string }) => (m.name ?? '').toLowerCase() === userLower) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const publishSecurityDowngrade = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentVersion = displayVersion.value?.version | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!currentVersion) return null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -989,6 +1015,21 @@ const showSkeleton = shallowRef(false) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <!-- Maintainers (with admin actions when connected) --> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PackageMaintainers :package-name="pkg.name" :maintainers="pkg.maintainers" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <!-- Deprecation (when connected as package owner; hidden when current version is already deprecated) --> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v-if="isConnected && resolvedVersion && isPackageOwner && !isCurrentVersionDeprecated" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class="space-y-1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class="flex items-center justify-center gap-1.5 w-full px-3 py-1.5 bg-bg-subtle rounded text-sm font-mono text-red-400 hover:text-red-500 transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @click="deprecateModal?.open()" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span class="i-carbon-warning-alt w-4 h-4 shrink-0" aria-hidden="true" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {{ $t('package.deprecation.action') }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1019
to
+1032
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the button needs to be visually aligned with the rest of the sidebar elements, and we should use ButtonBase with the Lucide icons as well.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </PackageSidebar> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1094,6 +1135,16 @@ const showSkeleton = shallowRef(false) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $t('common.go_back_home') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }}</LinkBase> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ClientOnly> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PackageDeprecatePackageModal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v-if="pkg" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref="deprecateModal" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :package-name="pkg.name" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :version="resolvedVersion ?? ''" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if we don't get the version? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :is-already-deprecated="isCurrentVersionDeprecated" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :deprecated-versions="deprecatedVersions" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ClientOnly> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </main> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </template> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.