From fd7743eb44328abe965fefc28a3ea1b1aa01c051 Mon Sep 17 00:00:00 2001
From: eryue0220
Date: Mon, 2 Feb 2026 21:09:35 +0800
Subject: [PATCH 1/2] add deprecate command
---
cli/src/npm-client.ts | 27 +++++++++++++++++++++++++++
cli/src/schemas.ts | 16 ++++++++++++++++
cli/src/server.ts | 9 +++++++++
cli/src/types.ts | 1 +
4 files changed, 53 insertions(+)
diff --git a/cli/src/npm-client.ts b/cli/src/npm-client.ts
index f21169eafd..8f668354d7 100644
--- a/cli/src/npm-client.ts
+++ b/cli/src/npm-client.ts
@@ -424,3 +424,30 @@ export async function packageInit(
})
}
}
+
+/**
+ * Deprecate a package or a specific version with a custom message.
+ * @param pkg Package name (e.g. "vue" or "@nuxt/kit")
+ * @param reason Deprecation message shown to users
+ * @param version Optional version to deprecate (e.g. "1.0.0"); if omitted, deprecates the whole package
+ * @param options.dryRun If true, passes --dry-run to npm (report what would be done without making changes)
+ * @param options.registry Registry URL (e.g. "https://registry.npmjs.org"); if set, passes --registry
+ */
+export async function packageDeprecate(
+ pkg: string,
+ reason: string,
+ version?: string,
+ otp?: string,
+ options?: { dryRun?: boolean; registry?: string },
+): Promise {
+ validatePackageName(pkg)
+ const target = version ? `${pkg}@${version}` : pkg
+ const args = ['deprecate', target, reason]
+ if (options?.dryRun) {
+ args.push('--dry-run')
+ }
+ if (options?.registry?.trim()) {
+ args.push('--registry', options.registry.trim())
+ }
+ return execNpm(args, { otp })
+}
diff --git a/cli/src/schemas.ts b/cli/src/schemas.ts
index eb3facba6d..ff3289e4eb 100644
--- a/cli/src/schemas.ts
+++ b/cli/src/schemas.ts
@@ -106,6 +106,7 @@ export const OperationTypeSchema = v.picklist([
'owner:add',
'owner:rm',
'package:init',
+ 'package:deprecate',
])
/**
@@ -231,6 +232,18 @@ export const PackageInitParamsSchema = v.object({
author: v.optional(UsernameSchema),
})
+const PackageDeprecateParamsSchema = v.object({
+ pkg: PackageNameSchema,
+ message: v.pipe(
+ v.string(),
+ v.nonEmpty('Deprecation message is required'),
+ v.maxLength(500, 'Message is too long'),
+ ),
+ version: v.optional(v.pipe(v.string(), v.nonEmpty())),
+ dryRun: v.optional(v.picklist(['true', 'false'], 'dryRun must be "true" or "false"')),
+ registry: v.optional(v.pipe(v.string(), v.minLength(1, 'Registry URL cannot be empty'))),
+})
+
// ============================================================================
// Helper Functions
// ============================================================================
@@ -280,6 +293,9 @@ export function validateOperationParams(
case 'package:init':
v.parse(PackageInitParamsSchema, params)
break
+ case 'package:deprecate':
+ v.parse(PackageDeprecateParamsSchema, params)
+ break
}
}
diff --git a/cli/src/server.ts b/cli/src/server.ts
index fc609e06f8..9e4d7c6207 100644
--- a/cli/src/server.ts
+++ b/cli/src/server.ts
@@ -23,6 +23,7 @@ import {
ownerAdd,
ownerRemove,
packageInit,
+ packageDeprecate,
listUserPackages,
type NpmExecResult,
} from './npm-client.ts'
@@ -734,6 +735,14 @@ async function executeOperation(op: PendingOperation, otp?: string): Promise
Date: Wed, 4 Feb 2026 17:28:37 +0800
Subject: [PATCH 2/2] feat: update ui
---
.../Package/DeprecatePackageModal.vue | 176 ++++++++++++++++++
app/pages/package/[...package].vue | 35 ++++
i18n/locales/en.json | 15 +-
i18n/locales/zh-CN.json | 15 +-
lunaria/files/en-US.json | 15 +-
lunaria/files/zh-CN.json | 15 +-
6 files changed, 267 insertions(+), 4 deletions(-)
create mode 100644 app/components/Package/DeprecatePackageModal.vue
diff --git a/app/components/Package/DeprecatePackageModal.vue b/app/components/Package/DeprecatePackageModal.vue
new file mode 100644
index 0000000000..c6164f3ff1
--- /dev/null
+++ b/app/components/Package/DeprecatePackageModal.vue
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
{{ $t('package.deprecation.modal.success') }}
+
+ {{ $t('package.deprecation.modal.success_detail') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ deprecateError }}
+
+
+
+
+
diff --git a/app/pages/package/[...package].vue b/app/pages/package/[...package].vue
index 734a7e060e..7196acbd8b 100644
--- a/app/pages/package/[...package].vue
+++ b/app/pages/package/[...package].vue
@@ -179,6 +179,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 sizeTooltip = computed(() => {
const chunks = [
displayVersion.value &&
@@ -1054,6 +1065,22 @@ defineOgImageComponent('Package', {
:peer-dependencies-meta="displayVersion.peerDependenciesMeta"
:optional-dependencies="displayVersion.optionalDependencies"
/>
+
+
+
+
+
@@ -1072,6 +1099,14 @@ defineOgImageComponent('Package', {
{{ $t('common.go_back_home') }}
+
+
+
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 9c6676b7d9..61e4244505 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -124,9 +124,22 @@
"navigation": "Package",
"copy_name": "Copy package name",
"deprecation": {
+ "action": "Deprecate",
+ "action_change": "Change deprecation",
"package": "This package has been deprecated.",
"version": "This version has been deprecated.",
- "no_reason": "No reason provided"
+ "no_reason": "No reason provided",
+ "modal": {
+ "deprecating": "Deprecating",
+ "title": "Deprecate",
+ "title_version": "Deprecate Version",
+ "reason": "Reason",
+ "reason_placeholder": "e.g. Use package-x instead. This package is no longer maintained.",
+ "success": "Package deprecated",
+ "success_detail": "The package has been deprecated.",
+ "version": "Version",
+ "version_placeholder": "Leave empty to deprecate the whole package"
+ }
},
"replacement": {
"title": "You might not need this dependency.",
diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json
index 6f2887aef3..54cc9f0db4 100644
--- a/i18n/locales/zh-CN.json
+++ b/i18n/locales/zh-CN.json
@@ -124,9 +124,22 @@
"navigation": "包导航",
"copy_name": "拷贝包名",
"deprecation": {
+ "action": "废弃",
+ "action_change": "修改弃用",
"package": "这个包已经被弃用。",
"version": "这个版本已经被弃用。",
- "no_reason": "没有提供原因"
+ "no_reason": "没有提供原因",
+ "modal": {
+ "deprecating": "废弃中",
+ "title": "废弃",
+ "title_version": "废弃版本",
+ "reason": "原因",
+ "reason_placeholder": "例如:使用 package-x 替代。这个包不再维护。",
+ "success": "包已废弃",
+ "success_detail": "这个包已废弃。",
+ "version": "版本",
+ "version_placeholder": "留空则废弃整个包"
+ }
},
"replacement": {
"title": "你可能不需要这个依赖。",
diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json
index 9c6676b7d9..61e4244505 100644
--- a/lunaria/files/en-US.json
+++ b/lunaria/files/en-US.json
@@ -124,9 +124,22 @@
"navigation": "Package",
"copy_name": "Copy package name",
"deprecation": {
+ "action": "Deprecate",
+ "action_change": "Change deprecation",
"package": "This package has been deprecated.",
"version": "This version has been deprecated.",
- "no_reason": "No reason provided"
+ "no_reason": "No reason provided",
+ "modal": {
+ "deprecating": "Deprecating",
+ "title": "Deprecate",
+ "title_version": "Deprecate Version",
+ "reason": "Reason",
+ "reason_placeholder": "e.g. Use package-x instead. This package is no longer maintained.",
+ "success": "Package deprecated",
+ "success_detail": "The package has been deprecated.",
+ "version": "Version",
+ "version_placeholder": "Leave empty to deprecate the whole package"
+ }
},
"replacement": {
"title": "You might not need this dependency.",
diff --git a/lunaria/files/zh-CN.json b/lunaria/files/zh-CN.json
index 6f2887aef3..54cc9f0db4 100644
--- a/lunaria/files/zh-CN.json
+++ b/lunaria/files/zh-CN.json
@@ -124,9 +124,22 @@
"navigation": "包导航",
"copy_name": "拷贝包名",
"deprecation": {
+ "action": "废弃",
+ "action_change": "修改弃用",
"package": "这个包已经被弃用。",
"version": "这个版本已经被弃用。",
- "no_reason": "没有提供原因"
+ "no_reason": "没有提供原因",
+ "modal": {
+ "deprecating": "废弃中",
+ "title": "废弃",
+ "title_version": "废弃版本",
+ "reason": "原因",
+ "reason_placeholder": "例如:使用 package-x 替代。这个包不再维护。",
+ "success": "包已废弃",
+ "success_detail": "这个包已废弃。",
+ "version": "版本",
+ "version_placeholder": "留空则废弃整个包"
+ }
},
"replacement": {
"title": "你可能不需要这个依赖。",