diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 86da3aa..50a9cb4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,6 +23,18 @@ jobs:
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
+ - name: Setup Node
+ uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install npm 11
+ run: npm install -g npm@11.12.1
+
+ - name: Install dependencies
+ run: npm ci
+
- name: Extract release notes from CHANGELOG
id: notes
run: |
diff --git a/.gitignore b/.gitignore
index b0f45e3..56125a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,7 @@ data
# 本地 Git hooks 模板(仅安装在 .git/hooks/,不进仓库)
git-hooks/
+
+# 飞牛 fpk 工程与产物(仅本地维护,不进仓库)
+scripts/fpk/
+dist-fpk/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3decd20..3da5a1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
本仓库自 **v1.0.0** 起作为正式版维护;此前历史提交已归档重置。
+## [1.0.4] - 2026-08-25
+
+### 新增
+
+- 密码输入框支持显示/隐藏切换(`PasswordInput` 组件,含无障碍文案)
+
+### 改进
+
+- 遗留 `ADMIN_SECRET` 部署:`/setup`、`/register` 路由与 `needsMigration` 检测对齐
+- 合并重复的密码不一致 i18n 文案;移除未使用的 `jose` 依赖
+
+### 修复
+
+- 修复 `PasswordInput` 导致 CI typecheck 失败
+
## [1.0.3] - 2026-08-23
### 修复
diff --git a/app/components/AdminLoginGate.vue b/app/components/AdminLoginGate.vue
index 5f959c6..b0d2e03 100644
--- a/app/components/AdminLoginGate.vue
+++ b/app/components/AdminLoginGate.vue
@@ -2,10 +2,6 @@
import logoLight from '~/assets/image/logo-light.png'
import logoDark from '~/assets/image/logo-dark.png'
-const emit = defineEmits<{
- success: []
-}>()
-
const router = useRouter()
const { login, fetchStatus, authStatus } = useAuth()
const toast = useToast()
@@ -47,7 +43,6 @@ async function submit() {
username.value = ''
password.value = ''
secret.value = ''
- emit('success')
toast.add({ title: t('auth.loginSuccess'), color: 'success' })
} else {
toast.add({
@@ -108,15 +103,11 @@ async function submit() {
>
{{ t('auth.adminSecret') }}
-
@@ -151,15 +142,11 @@ async function submit() {
>
{{ t('auth.password') }}
-
diff --git a/app/components/PasswordInput.vue b/app/components/PasswordInput.vue
new file mode 100644
index 0000000..547ae7b
--- /dev/null
+++ b/app/components/PasswordInput.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+ { show = !show }"
+ />
+
+
+
+
+
diff --git a/app/pages/account/password.vue b/app/pages/account/password.vue
index fc80014..3e67333 100644
--- a/app/pages/account/password.vue
+++ b/app/pages/account/password.vue
@@ -38,7 +38,7 @@ async function submit() {
if (!canSubmit.value || loading.value) return
if (newPassword.value !== confirmPassword.value) {
- toast.add({ title: t('password.mismatch'), color: 'error' })
+ toast.add({ title: t('auth.passwordMismatch'), color: 'error' })
return
}
@@ -101,14 +101,10 @@ async function submit() {
>
{{ t('password.current') }}
-
@@ -119,15 +115,11 @@ async function submit() {
>
{{ t('password.new') }}
-
@@ -139,14 +131,10 @@ async function submit() {
>
{{ t('password.confirmNew') }}
-
diff --git a/app/pages/register.vue b/app/pages/register.vue
index 5510fd1..83670f0 100644
--- a/app/pages/register.vue
+++ b/app/pages/register.vue
@@ -22,7 +22,7 @@ const canSubmit = computed(() =>
onMounted(async () => {
const status = await fetchStatus()
- if (!status.initialized) {
+ if (!status.initialized && !status.legacyMode) {
await router.replace('/setup')
return
}
@@ -118,15 +118,11 @@ async function submit() {
>
{{ t('auth.password') }}
-
@@ -138,15 +134,11 @@ async function submit() {
>
{{ t('auth.confirmPassword') }}
-
diff --git a/app/pages/setup.vue b/app/pages/setup.vue
index 50a35c5..c5a40cf 100644
--- a/app/pages/setup.vue
+++ b/app/pages/setup.vue
@@ -9,7 +9,8 @@ const { setup, migrate, fetchStatus } = useAuth()
const toast = useToast()
const { t } = useI18n()
-const isMigrate = computed(() => route.query.migrate === '1')
+const needsMigration = ref(false)
+const isMigrate = computed(() => needsMigration.value || route.query.migrate === '1')
const username = ref('')
const password = ref('')
@@ -26,6 +27,11 @@ const canSubmit = computed(() =>
onMounted(async () => {
const status = await fetchStatus()
+ needsMigration.value = status.needsMigration
+ if (status.legacyMode && !status.needsMigration) {
+ await router.replace('/')
+ return
+ }
if (status.initialized && !status.needsMigration) {
await router.replace('/')
}
@@ -129,15 +135,11 @@ async function submit() {
>
{{ t('auth.password') }}
-
@@ -149,15 +151,11 @@ async function submit() {
>
{{ t('auth.confirmPassword') }}
-
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 7c5e5e6..4fb715b 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -64,6 +64,8 @@
"setupFailed": "Setup failed. Check your input",
"migrateFailed": "Migration failed. Check your input",
"passwordMismatch": "Passwords do not match",
+ "showPassword": "Show password",
+ "hidePassword": "Hide password",
"registerSuccess": "Registered successfully",
"errors": {
"invalidUsername": "Username must be 3–32 letters, numbers, or underscores",
@@ -93,7 +95,6 @@
"new": "New password",
"confirmNew": "Confirm new password",
"newPlaceholder": "Set new password",
- "mismatch": "New passwords do not match",
"changed": "Password updated",
"tooShort": "New password must be at least 8 characters",
"changeFailed": "Failed to change password",
diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json
index 47dbfa2..e42dbd0 100644
--- a/i18n/locales/zh-CN.json
+++ b/i18n/locales/zh-CN.json
@@ -64,6 +64,8 @@
"setupFailed": "初始化失败,请检查输入",
"migrateFailed": "迁移失败,请检查输入",
"passwordMismatch": "两次输入的密码不一致",
+ "showPassword": "显示密码",
+ "hidePassword": "隐藏密码",
"registerSuccess": "注册成功",
"errors": {
"invalidUsername": "用户名需为 3–32 位字母、数字或下划线",
@@ -93,7 +95,6 @@
"new": "新密码",
"confirmNew": "确认新密码",
"newPlaceholder": "设置新密码",
- "mismatch": "两次输入的新密码不一致",
"changed": "密码已修改",
"tooShort": "新密码至少 8 位",
"changeFailed": "修改密码失败",
diff --git a/package-lock.json b/package-lock.json
index acd07b4..93e71e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,18 @@
{
"name": "pichost",
- "version": "1.0.3",
+ "version": "1.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pichost",
- "version": "1.0.3",
+ "version": "1.0.4",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@iconify-json/lucide": "^1.2.114",
"@nuxt/ui": "^4.9.0",
"@nuxtjs/i18n": "^10.6.0",
- "jose": "^6.0.11",
"nuxt": "^4.4.8",
"sharp": "^0.35.3",
"tailwindcss": "^4.3.1"
@@ -12242,15 +12241,6 @@
"jiti": "lib/jiti-cli.mjs"
}
},
- "node_modules/jose": {
- "version": "6.2.3",
- "resolved": "https://registry.npmmirror.com/jose/-/jose-6.2.3.tgz",
- "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/panva"
- }
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
diff --git a/package.json b/package.json
index 9544c70..dc5b45a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "pichost",
- "version": "1.0.3",
+ "version": "1.0.4",
"description": "Self-hosted image hosting for your NAS / home server — Nuxt admin UI, local disk storage, server-side WebP compression, referer protection, Twikoo/EasyImage compatible.",
"homepage": "https://github.com/O96u/PicHost#readme",
"repository": {
@@ -39,7 +39,6 @@
"@iconify-json/lucide": "^1.2.114",
"@nuxt/ui": "^4.9.0",
"@nuxtjs/i18n": "^10.6.0",
- "jose": "^6.0.11",
"nuxt": "^4.4.8",
"sharp": "^0.35.3",
"tailwindcss": "^4.3.1"
diff --git a/vitest.config.ts b/vitest.config.ts
index 6a49674..6ff45b4 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -3,6 +3,6 @@ import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node',
- include: ['server/**/*.test.ts']
+ include: ['server/**/*.test.ts', 'scripts/**/*.test.ts']
}
})