fix(docs): refresh release data from R2
This commit is contained in:
parent
f2426fac5e
commit
19f7cbd2ba
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"CORSRules": [
|
||||
{
|
||||
"AllowedHeaders": ["*"],
|
||||
"AllowedMethods": ["GET", "HEAD"],
|
||||
"AllowedOrigins": ["*"],
|
||||
"ExposeHeaders": ["ETag"],
|
||||
"MaxAgeSeconds": 3600
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,10 +3,14 @@ name: Sync Changelog to R2
|
|||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_run:
|
||||
workflows: ['Release']
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -24,6 +28,10 @@ jobs:
|
|||
- name: Upload to R2
|
||||
run: |
|
||||
pip install awscli --quiet
|
||||
aws s3api put-bucket-cors \
|
||||
--bucket "${R2_BUCKET_NAME}" \
|
||||
--cors-configuration file://.github/r2-cors.json \
|
||||
--endpoint-url "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
|
||||
aws s3 cp releases-cn.json "s3://${R2_BUCKET_NAME}/changelog/releases-cn.json" \
|
||||
--endpoint-url "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com" \
|
||||
--content-type application/json
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { fetchChangelog } from '@/lib/changelog';
|
||||
import { LandingNav } from '@/components/landing/LandingNav';
|
||||
import { ChangelogList } from '@/components/landing/ChangelogList';
|
||||
import { ChangelogRuntime } from '@/components/landing/ChangelogRuntime';
|
||||
import { fetchChangelog } from '@/lib/changelog';
|
||||
|
||||
const i18n = {
|
||||
en: {
|
||||
|
|
@ -17,7 +17,7 @@ export default async function ChangelogPage({ params }: { params: Promise<{ lang
|
|||
const { lang } = await params;
|
||||
const l = lang === 'cn' ? 'cn' : 'en';
|
||||
const t = i18n[l];
|
||||
const data = await fetchChangelog(l);
|
||||
const initialData = await fetchChangelog(l);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0b1120] text-landing-ink">
|
||||
|
|
@ -29,11 +29,7 @@ export default async function ChangelogPage({ params }: { params: Promise<{ lang
|
|||
</div>
|
||||
|
||||
<div className="max-w-[860px] mx-auto px-6 pb-24">
|
||||
{data.releases.length === 0 ? (
|
||||
<p className="text-landing-muted py-12">No releases found.</p>
|
||||
) : (
|
||||
<ChangelogList releases={data.releases} lang={l} />
|
||||
)}
|
||||
<ChangelogRuntime lang={l} initialReleases={initialData.releases} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,14 @@ import { InfiniteMovingCards } from '@/components/aceternity/InfiniteMovingCards
|
|||
import { Spotlight } from '@/components/aceternity/Spotlight';
|
||||
import { LandingNav } from '@/components/landing/LandingNav';
|
||||
import { InstallTabs } from '@/components/landing/InstallTabs';
|
||||
import { LandingLatestUpdates } from '@/components/landing/LandingLatestUpdates';
|
||||
import { RevealSection } from '@/components/landing/RevealSection';
|
||||
import { getAppVersion } from '@/lib/appVersion';
|
||||
import { fetchChangelog } from '@/lib/changelog';
|
||||
import { fetchLatestReleaseInfo } from '@/lib/latestRelease';
|
||||
import {
|
||||
ArrowRight,
|
||||
Bot,
|
||||
CheckCircle2,
|
||||
Database,
|
||||
FileCode,
|
||||
GitCompare,
|
||||
|
|
@ -166,33 +169,6 @@ const capabilities = {
|
|||
],
|
||||
};
|
||||
|
||||
const latestUpdates = {
|
||||
en: {
|
||||
version: 'v0.5.4',
|
||||
title: 'Latest updates',
|
||||
desc: 'Mirrored from the latest GitHub release notes.',
|
||||
link: 'Read the changelog',
|
||||
items: [
|
||||
'JDBC SSH tunnels and proxy support',
|
||||
'Grouped object browser with context menus',
|
||||
'Redis batch operations and command runner',
|
||||
'LIKE / NOT LIKE filters in the data grid',
|
||||
],
|
||||
},
|
||||
cn: {
|
||||
version: 'v0.5.4',
|
||||
title: '最近更新',
|
||||
desc: '同步 GitHub 最新 Release Notes。',
|
||||
link: '查看更新日志',
|
||||
items: [
|
||||
'JDBC SSH 隧道和代理支持',
|
||||
'对象浏览器分组与右键菜单',
|
||||
'Redis 批量操作和命令行',
|
||||
'数据表格 LIKE / NOT LIKE 过滤',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const testimonials = {
|
||||
en: [
|
||||
{
|
||||
|
|
@ -402,7 +378,9 @@ export default async function LandingPage({
|
|||
const capabilityItems = capabilities[l];
|
||||
const starLabel = await getGitHubStarLabel();
|
||||
const metricItems = metrics(starLabel)[l];
|
||||
const latest = latestUpdates[l];
|
||||
const appVersion = getAppVersion();
|
||||
const [initialChangelog, initialLatestRelease] = await Promise.all([fetchChangelog(l), fetchLatestReleaseInfo()]);
|
||||
const initialDownloadVersion = initialLatestRelease?.version ?? appVersion;
|
||||
const testimonialItems = testimonials[l];
|
||||
|
||||
return (
|
||||
|
|
@ -422,7 +400,7 @@ export default async function LandingPage({
|
|||
{t.heroSubtitle}
|
||||
</p>
|
||||
<div className="w-full max-w-[520px] mt-10">
|
||||
<InstallTabs lang={l} />
|
||||
<InstallTabs lang={l} version={initialDownloadVersion} />
|
||||
</div>
|
||||
</div>
|
||||
<HeroProductStage />
|
||||
|
|
@ -524,25 +502,7 @@ export default async function LandingPage({
|
|||
</RevealSection>
|
||||
|
||||
{/* Updates */}
|
||||
<RevealSection className="grid grid-cols-[minmax(86px,0.16fr)_minmax(210px,0.32fr)_minmax(0,0.38fr)_max-content] gap-[22px] items-center max-w-[1180px] mx-auto px-7 border-t border-b border-landing-line mt-[62px] py-6 max-[1040px]:grid-cols-[minmax(0,1fr)_max-content] max-[760px]:block max-[760px]:px-[18px]">
|
||||
<div className="landing-update-version w-max rounded-[7px] px-2.5 py-[7px] text-[13px] font-[720]">{latest.version}</div>
|
||||
<div className="max-[1040px]:col-span-full max-[760px]:mt-4">
|
||||
<h2 className="m-0 text-[21px] font-[720] text-landing-ink">{latest.title}</h2>
|
||||
<p className="mt-1.5 text-landing-muted text-[13px] leading-[1.55]">{latest.desc}</p>
|
||||
</div>
|
||||
<ul className="grid gap-2 m-0 p-0 list-none max-[1040px]:col-span-full max-[760px]:mt-4">
|
||||
{latest.items.map((item) => (
|
||||
<li key={item} className="flex gap-2 items-center text-[13px] font-[560] leading-[1.45] text-[color-mix(in_srgb,var(--color-landing-ink)_88%,var(--color-landing-muted))]" data-stagger>
|
||||
<CheckCircle2 size={14} className="shrink-0 text-landing-green" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link href={`/${l}/changelog`} className="landing-inline-link flex shrink-0 items-center gap-[7px] text-sm font-[650] max-[760px]:mt-4">
|
||||
{latest.link}
|
||||
<ArrowRight size={15} />
|
||||
</Link>
|
||||
</RevealSection>
|
||||
<LandingLatestUpdates lang={l} fallbackVersion={appVersion} initialRelease={initialChangelog.releases[0]} initialLatestRelease={initialLatestRelease} />
|
||||
|
||||
{/* Final CTA */}
|
||||
<RevealSection className="flex items-center justify-between gap-6 max-w-[1180px] mx-auto px-7 border border-landing-line rounded-[10px] bg-landing-panel mt-[72px] mb-14 py-[30px] max-[760px]:block max-[760px]:px-[18px]">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ChangelogList } from '@/components/landing/ChangelogList';
|
||||
import { fetchChangelog, type ChangelogRelease } from '@/lib/changelog';
|
||||
|
||||
type ChangelogRuntimeProps = {
|
||||
lang: 'en' | 'cn';
|
||||
initialReleases?: ChangelogRelease[];
|
||||
};
|
||||
|
||||
const text = {
|
||||
en: {
|
||||
loading: 'Loading releases...',
|
||||
empty: 'No releases found.',
|
||||
},
|
||||
cn: {
|
||||
loading: '正在加载版本记录...',
|
||||
empty: '暂无版本记录。',
|
||||
},
|
||||
};
|
||||
|
||||
export function ChangelogRuntime({ lang, initialReleases = [] }: ChangelogRuntimeProps) {
|
||||
const [releases, setReleases] = useState<ChangelogRelease[] | null>(initialReleases);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
fetchChangelog(lang).then((data) => {
|
||||
if (active && data.releases.length > 0) {
|
||||
setReleases(data.releases);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [lang]);
|
||||
|
||||
if (releases === null) {
|
||||
return <p className="text-landing-muted py-12">{text[lang].loading}</p>;
|
||||
}
|
||||
|
||||
if (releases.length === 0) {
|
||||
return <p className="text-landing-muted py-12">{text[lang].empty}</p>;
|
||||
}
|
||||
|
||||
return <ChangelogList releases={releases} lang={lang} />;
|
||||
}
|
||||
|
|
@ -2,32 +2,12 @@
|
|||
|
||||
import { ChevronDown, Download, Server } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { createInstallOptions, type InstallOption } from '@/lib/downloadLinks';
|
||||
import { fetchLatestReleaseInfo } from '@/lib/latestRelease';
|
||||
|
||||
type InstallTabsProps = {
|
||||
lang: 'en' | 'cn';
|
||||
};
|
||||
|
||||
type InstallOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
const allOptions: Record<string, InstallOption[]> = {
|
||||
en: [
|
||||
{ id: 'macos-arm', label: 'For macOS (Apple Silicon)', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_aarch64.dmg' },
|
||||
{ id: 'macos-intel', label: 'For macOS (Intel)', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_x64.dmg' },
|
||||
{ id: 'windows', label: 'For Windows', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_x64-setup.exe' },
|
||||
{ id: 'linux', label: 'For Linux x64', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_amd64.AppImage' },
|
||||
{ id: 'linux-arm', label: 'For Linux ARM64', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_aarch64.AppImage' },
|
||||
],
|
||||
cn: [
|
||||
{ id: 'macos-arm', label: '适用于 macOS (Apple Silicon)', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_aarch64.dmg' },
|
||||
{ id: 'macos-intel', label: '适用于 macOS (Intel)', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_x64.dmg' },
|
||||
{ id: 'windows', label: '适用于 Windows', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_x64-setup.exe' },
|
||||
{ id: 'linux', label: '适用于 Linux x64', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_amd64.AppImage' },
|
||||
{ id: 'linux-arm', label: '适用于 Linux ARM64', href: 'https://dl.dbxio.com/releases/latest/DBX_0.5.9_aarch64.AppImage' },
|
||||
],
|
||||
version: string;
|
||||
};
|
||||
|
||||
const downloadLabel = { en: 'Download DBX', cn: '下载 DBX' };
|
||||
|
|
@ -63,8 +43,9 @@ function PlatformIcon({ id, size, variant }: { id: string; size: number; variant
|
|||
return <img alt="" aria-hidden="true" height={size} src={src} width={size} />;
|
||||
}
|
||||
|
||||
export function InstallTabs({ lang }: InstallTabsProps) {
|
||||
const options = allOptions[lang];
|
||||
export function InstallTabs({ lang, version }: InstallTabsProps) {
|
||||
const [downloadVersion, setDownloadVersion] = useState(version);
|
||||
const options = useMemo(() => createInstallOptions(lang, downloadVersion), [lang, downloadVersion]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [platformId, setPlatformId] = useState('macos-arm');
|
||||
|
||||
|
|
@ -72,6 +53,21 @@ export function InstallTabs({ lang }: InstallTabsProps) {
|
|||
setPlatformId(detectPlatformId());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
setDownloadVersion(version);
|
||||
fetchLatestReleaseInfo().then((release) => {
|
||||
if (active && release?.version) {
|
||||
setDownloadVersion(release.version);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [version]);
|
||||
|
||||
const primary = useMemo(() => options.find((o) => o.id === platformId) ?? options[0], [options, platformId]);
|
||||
const menuOptions = useMemo(() => options.filter((o) => o.id !== platformId), [options, platformId]);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { ArrowRight, CheckCircle2 } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { RevealSection } from '@/components/landing/RevealSection';
|
||||
import { fetchChangelog, type ChangelogRelease } from '@/lib/changelog';
|
||||
import { buildLandingLatestUpdates } from '@/lib/landingLatest';
|
||||
import { fetchLatestReleaseInfo, type LatestReleaseInfo } from '@/lib/latestRelease';
|
||||
|
||||
type LandingLatestUpdatesProps = {
|
||||
lang: 'en' | 'cn';
|
||||
fallbackVersion: string;
|
||||
initialRelease?: ChangelogRelease;
|
||||
initialLatestRelease?: LatestReleaseInfo | null;
|
||||
};
|
||||
|
||||
export function LandingLatestUpdates({ lang, fallbackVersion, initialRelease, initialLatestRelease }: LandingLatestUpdatesProps) {
|
||||
const [latest, setLatest] = useState(() => buildLandingLatestUpdates(lang, initialRelease, fallbackVersion, initialLatestRelease));
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
setLatest(buildLandingLatestUpdates(lang, initialRelease, fallbackVersion, initialLatestRelease));
|
||||
Promise.all([fetchLatestReleaseInfo(), fetchChangelog(lang)]).then(([releaseInfo, data]) => {
|
||||
if (!active) return;
|
||||
|
||||
setLatest(buildLandingLatestUpdates(lang, data.releases[0] ?? initialRelease, fallbackVersion, releaseInfo ?? initialLatestRelease));
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [lang, fallbackVersion, initialRelease, initialLatestRelease]);
|
||||
|
||||
return (
|
||||
<RevealSection className="grid grid-cols-[minmax(86px,0.16fr)_minmax(210px,0.32fr)_minmax(0,0.38fr)_max-content] gap-[22px] items-center max-w-[1180px] mx-auto px-7 border-t border-b border-landing-line mt-[62px] py-6 max-[1040px]:grid-cols-[minmax(0,1fr)_max-content] max-[760px]:block max-[760px]:px-[18px]">
|
||||
<div className="landing-update-version w-max rounded-[7px] px-2.5 py-[7px] text-[13px] font-[720]">{latest.version}</div>
|
||||
<div className="max-[1040px]:col-span-full max-[760px]:mt-4">
|
||||
<h2 className="m-0 text-[21px] font-[720] text-landing-ink">{latest.title}</h2>
|
||||
<p className="mt-1.5 text-landing-muted text-[13px] leading-[1.55]">{latest.desc}</p>
|
||||
</div>
|
||||
<ul className="grid gap-2 m-0 p-0 list-none max-[1040px]:col-span-full max-[760px]:mt-4">
|
||||
{latest.items.map((item) => (
|
||||
<li key={item} className="flex gap-2 items-center text-[13px] font-[560] leading-[1.45] text-[color-mix(in_srgb,var(--color-landing-ink)_88%,var(--color-landing-muted))]" data-stagger>
|
||||
<CheckCircle2 size={14} className="shrink-0 text-landing-green" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link href={`/${lang}/changelog`} className="landing-inline-link flex shrink-0 items-center gap-[7px] text-sm font-[650] max-[760px]:mt-4">
|
||||
{latest.link}
|
||||
<ArrowRight size={15} />
|
||||
</Link>
|
||||
</RevealSection>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,195 +3,6 @@ title: 更新日志
|
|||
description: 按版本查看 DBX 的新增功能、体验改进和问题修复,内容同步自 GitHub Releases。
|
||||
---
|
||||
|
||||
这里整理 DBX 的版本变化,按版本号从新到旧排列。内容以 GitHub Releases 为准,保留功能、改进和修复条目,下载安装说明请查看对应 GitHub Release 页面。
|
||||
更新日志会直接读取 R2 上的最新 Release 数据,因此发布后无需重新构建文档站也能刷新。
|
||||
|
||||
## v0.5.4
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.4)
|
||||
|
||||
### 新功能
|
||||
|
||||
- **JDBC SSH 隧道和代理支持** — JDBC 连接新增 SSH 隧道和代理支持,自动解析和改写 JDBC URL 中的 host:port,通过隧道安全访问远程数据库
|
||||
- **对象浏览器分组与右键菜单** — 对象浏览器按类型分组显示数据库对象,新增右键上下文菜单操作
|
||||
- **Redis 批量操作和命令行** — Redis 新增批量键操作和命令执行器
|
||||
- **数据库代理支持** — 新增数据库连接和 AI 的代理 (Proxy) 支持
|
||||
- **数据表格 LIKE/NOT LIKE 过滤** — 右键菜单新增 LIKE、NOT LIKE、大于、小于等过滤条件
|
||||
- **数据库对象源码编辑** — 支持查看和编辑数据库对象(存储过程、函数、视图等)的源码
|
||||
- **Connection URL 动态占位符** — 连接对话框根据数据库类型动态显示对应的 URL 格式示例 (contributed by @Abeautifulsnow)
|
||||
- **AI Thinking 开关** — AI 设置新增 thinking 开关,可关闭本地模型(如 Ollama/vLLM)的思考输出以节省 token (contributed by @rarnu)
|
||||
- **PostgreSQL 新类型支持** — 新增 PostgreSQL 整数数组和 float4 类型支持 (contributed by @xKrah)
|
||||
- **西班牙语支持** — 新增西班牙语本地化,重构语言切换为下拉菜单 (contributed by @Max29xD)
|
||||
|
||||
### 改进
|
||||
|
||||
- **设置页面优化** — AI 模型设置移至全局设置页面,优化设置页面布局和选中项样式 (contributed by @rarnu)
|
||||
- **单元格值不再截断** — 修复详情面板和复制/导出操作中单元格值被截断为 256 字符的问题,现在显示完整内容 (contributed by @Abeautifulsnow)
|
||||
- **虚拟滚动优化** — 优化数据表格虚拟滚动,减少快速滚动时的白屏闪烁
|
||||
- **数据表格工具栏** — 优化数据表格工具栏布局
|
||||
- **Redis 键浏览器国际化** — Redis 键浏览器新增国际化支持 (contributed by @xKrah)
|
||||
- **设置页面链接** — 改进设置对话框中的链接显示
|
||||
|
||||
### 修复
|
||||
|
||||
- **Oracle RAC/SCAN 连接** — 修复 Oracle RAC/SCAN 连接重定向问题 (closes #209)
|
||||
- **Oracle 表数据加载与编辑** — 修复 Oracle 表数据加载和编辑功能异常
|
||||
- **Oracle Schema 切换** — 简化编辑器选择器并支持 schema 切换 (closes #205)
|
||||
- **Oracle 非英文 Windows 连接错误** — 修复 Oracle 在非英文 Windows 系统上的连接错误检测 (closes #208)
|
||||
- **Oracle Schema Diff** — 加速 Oracle schema diff 并增加注释同步 (closes #198)
|
||||
- **侧边栏性能** — 修复 100+ 数据库时侧边栏 UI 卡顿 (closes #201)
|
||||
- **侧边栏搜索** — 修复侧边栏搜索无法找到被对象浏览器隐藏的表
|
||||
- **侧边栏刷新** — 修复展开节点刷新时子节点缓存未清除的问题
|
||||
- **WHERE 过滤持久化** — 修复 WHERE 过滤条件在 tab 切换时丢失的问题 (closes #210)
|
||||
- **数据表格分页** — 修复保存数据后分页大小被重置的问题
|
||||
- **Enter 键补全** — 修复 Enter 键优先选中自动补全建议项 (closes #202)
|
||||
- **SQL Server 对象源码** — 修复 SQL Server 对象源码定义保存问题
|
||||
- **构建修复** — 修复单引号导致构建失败的问题 (contributed by @xKrah)
|
||||
- **侧边栏双击** — 修复侧边栏需要双击才能打开对象浏览器的问题
|
||||
- **虚拟树优化** — 修复非空树的虚拟化判断函数
|
||||
|
||||
## v0.5.3
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.3)
|
||||
|
||||
### 新功能
|
||||
|
||||
- **AI SQL 智能执行** — AI 助手支持自动执行 SQL 语句,内置安全防护策略,根据语句类型自动判断是否允许执行
|
||||
- **MongoDB 表视图** — 新增 MongoDB 集合的表格视图,支持内联编辑数据
|
||||
- **导入 DBeaver 连接** — 支持从 DBeaver 导入数据库连接配置
|
||||
- **导入 Navicat 连接** — 支持从 Navicat NCX 文件导入数据库连接配置
|
||||
- **存储过程/函数浏览** — 对象浏览器支持显示存储过程和函数
|
||||
- **虚拟对象浏览器** — 新增虚拟对象浏览器,统一展示数据库对象
|
||||
- **数据库活动历史** — 支持追踪和查看数据库操作历史记录
|
||||
- **外部表格数据源基础** — 新增外部表格数据源的通用底座架构,为后续接入 CSV/XLSX/在线表格等数据源奠定基础 (contributed by @BlueSkyXN)
|
||||
- **数据表格加载指示器** — 数据表格新增加载遮罩层和耗时计时器,查询执行状态一目了然
|
||||
|
||||
### 改进
|
||||
|
||||
- **编辑器增强** — 编辑器功能增强、DataGrid 重构和侧边栏交互改进
|
||||
- **XLSX 导出** — 表格右键菜单新增 XLSX 导出选项
|
||||
- **全栈性能优化** — 全栈性能优化和安全加固
|
||||
- **Oracle 加载提速** — 优化 Oracle 数据库初始表加载速度
|
||||
- **DuckDB 编译优化** — DuckDB 捆绑编译改为可选,减小构建体积
|
||||
|
||||
### 修复
|
||||
|
||||
- **侧边栏树状态** — 修复侧边栏树状态显示问题
|
||||
- **GitHub 链接** — 修复设置页面 About 中 GitHub 链接不可点击的问题
|
||||
- **Web 连接池** — 修复带前缀的 Web 连接池被意外断开的问题
|
||||
- **连接配置缓存** — 修复 Web 连接配置缓存同步问题
|
||||
- **Oracle Schema 缓存** — 修复 Oracle Schema 缓存刷新和表列表不完整的问题
|
||||
- **Oracle NCHAR 解码** — 修复 Oracle NCHAR 值解码不正确的问题
|
||||
- **侧边栏搜索折叠** — 修复侧边栏搜索结果无法折叠的问题
|
||||
|
||||
## v0.5.2
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.2)
|
||||
|
||||
### 新功能
|
||||
|
||||
- **JDBC 插件支持** — 新增可选的 JDBC 插件模块,支持通过 JDBC 驱动连接更多数据库类型
|
||||
- **SQL 代码库** — 支持保存和管理常用 SQL 语句,存储在应用数据库中,随时复用
|
||||
- **DataGrip 风格列过滤器** — 数据表格支持类似 DataGrip 的列过滤功能,快速筛选数据
|
||||
- **数据对比与结构同步** — 新增数据对比和表结构同步功能
|
||||
- **连接 URL 和 XLSX 导出** — 支持通过连接 URL 快速创建连接,支持导出数据为 XLSX 格式
|
||||
- **连接颜色标识** — 连接支持自定义颜色标识,方便区分不同环境
|
||||
- **表格剪贴板快捷键** — 数据表格支持复制粘贴等剪贴板快捷键操作
|
||||
- **布局偏好设置** — 支持自定义界面布局偏好
|
||||
- **SSH 超时配置** — SSH 隧道连接支持配置超时时间
|
||||
|
||||
### 改进
|
||||
|
||||
- **界面外观优化** — 优化面板、标签页和应用整体外观布局
|
||||
|
||||
### 修复
|
||||
|
||||
- **Redis 键表列对齐** — 修复 Redis 键表格列宽未对齐的问题
|
||||
- **连接状态同步** — 修复展开缓存树时连接状态未同步的问题
|
||||
- **连接对话框错误** — 修复连接对话框的错误提示问题
|
||||
- **SQL 注释后查询检测** — 修复 SQL 注释后的结果查询未被正确检测的问题
|
||||
- **列过滤弹窗优化** — 优化列过滤弹窗的交互体验
|
||||
- **查询标签页持久化** — 修复未保存的查询标签页丢失的问题
|
||||
- **隐藏系统数据库对象** — 修复侧栏显示系统数据库对象的问题
|
||||
- **文件选择器行为** — 修复 Web 版文件选择器的行为不一致问题
|
||||
|
||||
## v0.5.1
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.1)
|
||||
|
||||
### 新功能
|
||||
|
||||
- **设置页关于标签** — 设置对话框新增"关于"标签页,方便查看版本信息
|
||||
- **数据库对象列表缓存** — 侧栏数据库对象列表支持缓存,减少重复查询,加速切换
|
||||
- **从分组菜单创建连接** — 侧栏分组右键菜单支持直接创建新连接
|
||||
- **编辑器 Tab 缩进** — SQL 编辑器支持 Tab 键缩进
|
||||
- **Windows 绿色版压缩包** — 新增 Windows 便携版(ZIP),无需安装即可使用
|
||||
|
||||
### 改进
|
||||
|
||||
- **查询结果流式加载** — 查询结果使用流式传输并限制行数,提升大数据量查询的响应速度
|
||||
- **侧栏搜索性能优化** — 侧栏搜索匹配算法优化,过滤速度更快
|
||||
- **表格右键菜单增强** — 数据表格右键菜单功能增强
|
||||
- **数据表工具栏条件刷新** — 修复表数据编辑的工具栏,条件过滤和刷新不再丢失状态 (contributed by @rarnu)
|
||||
- **AI 对话历史可切换** — AI 助手对话历史支持开关控制
|
||||
|
||||
### 修复
|
||||
|
||||
- **SQL Server 编辑支持** — 修复 SQL Server 数据编辑相关问题
|
||||
- **更新后重启** — 修复应用更新后重启失败的问题
|
||||
- **表格翻页滚动位置** — 修复翻页后表格滚动位置未重置的问题
|
||||
- **MySQL 事务编辑** — 修复 MySQL 事务编辑使用原始 SQL 执行的问题
|
||||
- **侧栏刷新保持展开** — 修复侧栏刷新后树节点展开状态丢失的问题
|
||||
- **标题栏双击切换** — 修复标题栏双击时窗口状态重复切换的问题 (#179)
|
||||
- **默认库过滤数据库树** — 修复默认数据库配置过滤数据库树显示的问题 (#175)
|
||||
- **标签文字被箭头遮挡** — 修复标签页滚动箭头与文字重叠的问题
|
||||
- **表格与弹窗布局** — 优化表格与 SQL 文件弹窗的布局显示
|
||||
- **事务状态位置** — 事务状态指示移至操作按钮旁边,更直观
|
||||
- **WHERE/ORDER BY 条件** — 修复 WHERE 和 ORDER BY 输入框条件重复和状态丢失的问题
|
||||
- **默认数据库处理** — 统一默认数据库的处理逻辑
|
||||
|
||||
## v0.5.0
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.0)
|
||||
|
||||
### 新功能
|
||||
|
||||
- **Redis 浏览器全面重构** — 键浏览器升级为 Navicat 风格表格视图,支持键值编辑、分页浏览和全库键扫描 (contributed by @Bacon2994)
|
||||
- **自定义标题栏** — 窗口标题栏改为自定义样式,集成窗口控制按钮
|
||||
- **数据表格 WHERE/ORDER BY 过滤** — 数据表格新增独立的 WHERE 和 ORDER BY 输入框,支持自定义条件过滤和排序 (#165)
|
||||
- **结果集数据过滤** — 查询结果集内支持对已加载数据进行实时过滤 (contributed by @rarnu)
|
||||
- **单元格值编辑器** — 单元格详情面板支持直接编辑值和设置 NULL
|
||||
- **列宽自适应** — 数据表格自动根据表头文字和数据内容调整最佳列宽
|
||||
- **复制为 INSERT** — 数据表格右键菜单新增"复制为 INSERT 语句"功能 (#166)
|
||||
- **创建/删除数据库和 Schema** — 侧栏右键菜单支持创建和删除数据库及 Schema
|
||||
- **SQL Server CROSS APPLY 补全** — SQL 编辑器支持 CROSS APPLY / OUTER APPLY 语法补全 (#168)
|
||||
- **OceanBase Oracle 模式** — 自动检测 OceanBase Oracle 模式,使用 Oracle 风格的 Schema 查询 (#155)
|
||||
- **鼠标中键关闭标签** — 支持鼠标中键点击关闭标签页 (#162)
|
||||
- **排序提示图标** — 鼠标悬停列标题时显示可排序提示图标
|
||||
- **窗口状态记忆** — 窗口位置和大小在关闭后自动保存,重新打开时恢复
|
||||
- **MCP 连接管理** — MCP 工具新增 `dbx_add_connection` 和 `dbx_remove_connection`,操作后自动通知桌面端刷新
|
||||
|
||||
### 改进
|
||||
|
||||
- **DataGrid 工具栏增强** — 数据表格工具栏和数据集交互优化 (contributed by @rarnu)
|
||||
- **JSON/JSONB 渲染性能** — 预序列化 JSON/JSONB 值并截断单元格显示,大幅提升大数据量渲染性能
|
||||
- **信息栏合并** — 数据表底部信息栏和事务工具栏合并为一行,节省屏幕空间
|
||||
- **标签页样式优化** — 标签模式切换改用图标,标签标题改为 Navicat 风格
|
||||
|
||||
### 修复
|
||||
|
||||
- **标签页右键菜单** — 修复关闭所有/关闭其他/关闭右侧等标签页右键菜单不可用的问题
|
||||
- **查询结果表头和排序** — 修复查询结果表头提示和全量排序问题 (contributed by @Bacon2994)
|
||||
- **MCP 连接配置** — 修复 MCP 写入的 config_json 与 Rust ConnectionConfig 结构不匹配的问题,改为从 SQLite 读取连接
|
||||
- **Tailwind 样式丢失** — 修复 tmpfs 环境下 Tailwind 样式缺失的问题 (#167)
|
||||
- **数据表格滚动抖动** — 禁用数据表格过度滚动回弹,防止表头和数据错位 (#156)
|
||||
- **GaussDB Schema 列表** — 移除 GaussDB list_schemas 的 EXISTS 过滤以保持一致性 (#154)
|
||||
- **侧栏数据库过滤** — 连接配置指定数据库时,侧栏只显示该数据库 (#160)
|
||||
- **Oracle 11g 兼容性** — 修复 Oracle 11g 连接兼容性问题和侧栏改进
|
||||
- **MySQL 全 NULL 显示** — 增加 MySQL 类型回退,防止缺少类型映射时所有列值显示为 NULL (#40)
|
||||
- **AI 执行 SQL** — 修复 AI 执行时传递过期 SQL 状态的问题 (#153)
|
||||
- **JSONB 编辑显示** — 修复 JSONB 值在编辑模式和详情面板中未正确显示为 JSON 字符串的问题 (#152)
|
||||
- **MongoDB 副本集 SSH** — 修复 MongoDB 副本集通过 SSH 隧道连接失败的问题 (#151)
|
||||
- **Redis 二进制误判** — 修复反斜杠字符串被误判为二进制数据的问题 (contributed by @Bacon2994)
|
||||
- **Toast 通知层级** — 修复 Toast 通知被对话框遮挡的问题
|
||||
- **DDL 面板文本选择** — 修复 DDL 面板无法选中文本的问题
|
||||
- **原生右键菜单** — 禁用浏览器原生右键菜单,新增侧栏刷新按钮 (#159)
|
||||
[打开实时更新日志](/cn/changelog)
|
||||
|
|
|
|||
|
|
@ -3,195 +3,6 @@ title: Changelog
|
|||
description: Track DBX release notes by version, mirrored from GitHub Releases.
|
||||
---
|
||||
|
||||
This page mirrors the user-facing release notes from GitHub Releases. It keeps feature, improvement, and fix entries in the docs site; download tables and installer details remain on each GitHub Release page.
|
||||
The changelog is loaded from the latest release data on R2, so it can refresh after a release without rebuilding the docs site.
|
||||
|
||||
## v0.5.4
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.4)
|
||||
|
||||
### Added
|
||||
|
||||
- **JDBC SSH tunnels and proxy support** — JDBC connections can use SSH tunnels and proxies, with host:port values parsed and rewritten from JDBC URLs.
|
||||
- **Grouped object browser and context menus** — Database objects are grouped by type and expose context-menu actions.
|
||||
- **Redis batch operations and command runner** — Redis adds batch key operations and a command executor.
|
||||
- **Database proxy support** — Database connections and AI settings now support proxy configuration.
|
||||
- **Data grid LIKE / NOT LIKE filters** — Grid context menus add LIKE, NOT LIKE, greater-than, and less-than filters.
|
||||
- **Database object source editing** — View and edit source for database objects such as procedures, functions, and views.
|
||||
- **Dynamic Connection URL placeholders** — The connection dialog shows URL examples that match the selected database type (contributed by @Abeautifulsnow).
|
||||
- **AI Thinking toggle** — AI settings can hide thinking output from local models such as Ollama or vLLM to save tokens (contributed by @rarnu).
|
||||
- **PostgreSQL type support** — Adds support for integer arrays and float4 (contributed by @xKrah).
|
||||
- **Spanish localization** — Adds Spanish UI text and refactors language switching into a dropdown (contributed by @Max29xD).
|
||||
|
||||
### Improved
|
||||
|
||||
- **Settings page** — Moves AI model settings into global settings and improves the settings layout (contributed by @rarnu).
|
||||
- **Full cell values** — Details, copy, and export flows no longer truncate cell values to 256 characters (contributed by @Abeautifulsnow).
|
||||
- **Virtual scrolling** — Reduces blank flashing during fast grid scrolling.
|
||||
- **Data grid toolbar** — Refines the grid toolbar layout.
|
||||
- **Redis browser i18n** — Adds localization support to the Redis key browser (contributed by @xKrah).
|
||||
- **Settings links** — Improves link presentation in the settings dialog.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixes Oracle RAC/SCAN redirects (closes #209).
|
||||
- Fixes Oracle table loading and editing.
|
||||
- Simplifies Oracle selector behavior and supports schema switching (closes #205).
|
||||
- Fixes Oracle connection error detection on non-English Windows systems (closes #208).
|
||||
- Speeds up Oracle schema diff and adds comment synchronization (closes #198).
|
||||
- Fixes sidebar lag with 100+ databases (closes #201).
|
||||
- Fixes sidebar search missing tables hidden by the object browser.
|
||||
- Fixes stale child caches when refreshing expanded sidebar nodes.
|
||||
- Preserves WHERE filters across tab switches (closes #210).
|
||||
- Keeps data grid page size after saving data.
|
||||
- Makes Enter prefer autocomplete suggestions when available (closes #202).
|
||||
- Fixes SQL Server object source saving.
|
||||
- Fixes a build failure caused by single quotes (contributed by @xKrah).
|
||||
- Fixes object browser items requiring double-click to open.
|
||||
- Fixes virtual tree detection for non-empty trees.
|
||||
|
||||
## v0.5.3
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.3)
|
||||
|
||||
### Added
|
||||
|
||||
- **AI SQL smart execution** — The AI assistant can execute SQL with built-in statement safety checks.
|
||||
- **MongoDB table view** — MongoDB collections can be viewed and edited in a grid.
|
||||
- **DBeaver import** — Import database connection configs from DBeaver.
|
||||
- **Navicat import** — Import database connections from Navicat NCX files.
|
||||
- **Procedure and function browsing** — The object browser can show procedures and functions.
|
||||
- **Virtual object browser** — A unified browser for database objects.
|
||||
- **Database activity history** — Track and review database operation history.
|
||||
- **External table data source foundation** — Adds the shared architecture for future CSV, XLSX, and online sheet sources (contributed by @BlueSkyXN).
|
||||
- **Data grid loading indicator** — Adds a loading mask and elapsed-time display for query execution.
|
||||
|
||||
### Improved
|
||||
|
||||
- Editor features, DataGrid internals, and sidebar interactions.
|
||||
- XLSX export from the table context menu.
|
||||
- Full-stack performance and safety hardening.
|
||||
- Faster initial Oracle table loading.
|
||||
- Optional DuckDB bundling to reduce build size.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Sidebar tree state display.
|
||||
- GitHub link clickability in Settings > About.
|
||||
- Unexpected disconnection of prefixed web connection pools.
|
||||
- Web connection config cache synchronization.
|
||||
- Oracle schema cache refresh and incomplete table lists.
|
||||
- Oracle NCHAR decoding.
|
||||
- Sidebar search result collapsing.
|
||||
|
||||
## v0.5.2
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.2)
|
||||
|
||||
### Added
|
||||
|
||||
- Optional JDBC plugin support for connecting to more database types through JDBC drivers.
|
||||
- Saved SQL library for reusable SQL snippets.
|
||||
- DataGrip-style column filters in the data grid.
|
||||
- Data comparison and table structure synchronization.
|
||||
- Connection URL creation and XLSX export.
|
||||
- Custom connection colors for distinguishing environments.
|
||||
- Clipboard shortcuts in the data grid.
|
||||
- Layout preference settings.
|
||||
- SSH tunnel timeout configuration.
|
||||
|
||||
### Improved
|
||||
|
||||
- Panels, tabs, and general application layout.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Redis key table column alignment.
|
||||
- Connection state synchronization when expanding cached trees.
|
||||
- Connection dialog error messages.
|
||||
- Result-query detection after SQL comments.
|
||||
- Column filter popup interactions.
|
||||
- Unsaved query tab persistence.
|
||||
- Hidden system database objects in the sidebar.
|
||||
- Web file picker inconsistencies.
|
||||
|
||||
## v0.5.1
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.1)
|
||||
|
||||
### Added
|
||||
|
||||
- About tab in Settings.
|
||||
- Cached database object lists in the sidebar.
|
||||
- Create new connections from group context menus.
|
||||
- Tab indentation in the SQL editor.
|
||||
- Windows portable ZIP builds.
|
||||
|
||||
### Improved
|
||||
|
||||
- Streaming query results with row limits for large result sets.
|
||||
- Faster sidebar search filtering.
|
||||
- More capable grid context menus.
|
||||
- Better toolbar state retention for data editing filters and refresh (contributed by @rarnu).
|
||||
- Toggleable AI conversation history.
|
||||
|
||||
### Fixed
|
||||
|
||||
- SQL Server data editing issues.
|
||||
- Restart after app updates.
|
||||
- Grid scroll position after pagination.
|
||||
- MySQL transaction editing through raw SQL.
|
||||
- Sidebar expanded state after refresh.
|
||||
- Repeated title-bar double-click state toggles (#179).
|
||||
- Default database filtering in the database tree (#175).
|
||||
- Tab text overlapping scroll arrows.
|
||||
- Table and SQL file dialog layouts.
|
||||
- Transaction status placement.
|
||||
- Duplicate or lost WHERE / ORDER BY state.
|
||||
- Default database handling consistency.
|
||||
|
||||
## v0.5.0
|
||||
|
||||
[GitHub Release](https://github.com/t8y2/dbx/releases/tag/v0.5.0)
|
||||
|
||||
### Added
|
||||
|
||||
- Fully rebuilt Redis browser with a Navicat-style table view, value editing, pagination, and full keyspace scanning (contributed by @Bacon2994).
|
||||
- Custom window title bar with integrated window controls.
|
||||
- Dedicated WHERE and ORDER BY inputs for data-grid filtering and sorting (#165).
|
||||
- In-result filtering for loaded query data (contributed by @rarnu).
|
||||
- Editable cell details panel with explicit NULL support.
|
||||
- Auto-fit column widths based on headers and data.
|
||||
- Copy selected rows as INSERT statements (#166).
|
||||
- Create and drop databases and schemas from the sidebar.
|
||||
- SQL Server CROSS APPLY / OUTER APPLY completion (#168).
|
||||
- OceanBase Oracle mode detection (#155).
|
||||
- Middle-click tab close (#162).
|
||||
- Sort hint icons on column hover.
|
||||
- Window position and size persistence.
|
||||
- MCP connection management with `dbx_add_connection` and `dbx_remove_connection`.
|
||||
|
||||
### Improved
|
||||
|
||||
- DataGrid toolbar and dataset interactions (contributed by @rarnu).
|
||||
- JSON / JSONB rendering performance for large data sets.
|
||||
- Combined data-grid information and transaction toolbar row.
|
||||
- Navicat-style tab presentation.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Tab context-menu actions such as close all, close others, and close right.
|
||||
- Query result headers and full sorting (contributed by @Bacon2994).
|
||||
- MCP config_json compatibility with Rust `ConnectionConfig`.
|
||||
- Tailwind styles missing under tmpfs (#167).
|
||||
- Data-grid overscroll causing header and data misalignment (#156).
|
||||
- GaussDB schema-list filtering (#154).
|
||||
- Sidebar database filtering when a connection specifies a database (#160).
|
||||
- Oracle 11g compatibility and sidebar behavior.
|
||||
- MySQL fallback type mapping to avoid all-NULL columns (#40).
|
||||
- AI execution receiving stale SQL state (#153).
|
||||
- JSONB display in edit mode and the details panel (#152).
|
||||
- MongoDB replica-set connections through SSH tunnels (#151).
|
||||
- Redis backslash strings being misclassified as binary data (contributed by @Bacon2994).
|
||||
- Toast notifications hidden behind dialogs.
|
||||
- Text selection in the DDL panel.
|
||||
- Browser-native context menu conflicts and adds a sidebar refresh button (#159).
|
||||
[Open the live changelog](/en/changelog)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import appPackage from '../../package.json';
|
||||
|
||||
export function getAppVersion() {
|
||||
return appPackage.version;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { requestJson } from '@/lib/httpJson';
|
||||
|
||||
export type ChangelogItem = {
|
||||
title: string;
|
||||
desc: string;
|
||||
|
|
@ -21,17 +23,111 @@ export type ChangelogData = {
|
|||
releases: ChangelogRelease[];
|
||||
};
|
||||
|
||||
const BASE_URL = process.env.CHANGELOG_BASE_URL || 'https://dl.dbxio.com/changelog';
|
||||
type GitHubRelease = {
|
||||
tag_name: string;
|
||||
name: string | null;
|
||||
published_at: string | null;
|
||||
body: string | null;
|
||||
draft: boolean;
|
||||
prerelease: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_BASE_URL = 'https://dl.dbxio.com/changelog';
|
||||
const GITHUB_RELEASES_URL = 'https://api.github.com/repos/t8y2/dbx/releases?per_page=30';
|
||||
|
||||
const SECTION_MAP: Record<string, string> = {
|
||||
新功能: 'added',
|
||||
Added: 'added',
|
||||
改进: 'improved',
|
||||
Improved: 'improved',
|
||||
修复: 'fixed',
|
||||
Fixed: 'fixed',
|
||||
变更: 'changed',
|
||||
Changed: 'changed',
|
||||
移除: 'removed',
|
||||
Removed: 'removed',
|
||||
};
|
||||
|
||||
export function changelogUrl(lang: 'en' | 'cn') {
|
||||
const baseUrl =
|
||||
(typeof process !== 'undefined' &&
|
||||
(process.env.NEXT_PUBLIC_CHANGELOG_BASE_URL || process.env.CHANGELOG_BASE_URL)) ||
|
||||
DEFAULT_BASE_URL;
|
||||
|
||||
return `${baseUrl}/releases-${lang}.json`;
|
||||
}
|
||||
|
||||
export async function fetchChangelog(lang: 'en' | 'cn'): Promise<ChangelogData> {
|
||||
const url = `${BASE_URL}/releases-${lang}.json`;
|
||||
const url = changelogUrl(lang);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { next: { revalidate: 3600 } });
|
||||
if (!res.ok) {
|
||||
return { updatedAt: '', releases: [] };
|
||||
return await requestJson<ChangelogData>(url);
|
||||
} catch {
|
||||
return fetchGitHubChangelog();
|
||||
}
|
||||
}
|
||||
|
||||
function stripDownloadSection(body: string) {
|
||||
const markers = ['### 下载安装', '### Download', '### 系统要求', '### System Requirements'];
|
||||
let idx = body.length;
|
||||
|
||||
for (const marker of markers) {
|
||||
const markerIndex = body.indexOf(marker);
|
||||
if (markerIndex !== -1 && markerIndex < idx) {
|
||||
idx = markerIndex;
|
||||
}
|
||||
return res.json() as Promise<ChangelogData>;
|
||||
}
|
||||
|
||||
return body.slice(0, idx).trim();
|
||||
}
|
||||
|
||||
function parseReleaseBody(body: string) {
|
||||
const sections: ChangelogSection[] = [];
|
||||
let current: ChangelogSection | null = null;
|
||||
|
||||
for (const line of stripDownloadSection(body).split('\n')) {
|
||||
const headerMatch = line.match(/^###\s+(.+)/);
|
||||
if (headerMatch) {
|
||||
const title = headerMatch[1].trim();
|
||||
current = { type: SECTION_MAP[title] || 'other', title, items: [] };
|
||||
sections.push(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!current) continue;
|
||||
|
||||
const itemMatch = line.match(/^-\s+\*\*(.+?)\*\*\s*[—–-]\s*(.+)/);
|
||||
if (itemMatch) {
|
||||
current.items.push({ title: itemMatch[1].trim(), desc: itemMatch[2].trim() });
|
||||
continue;
|
||||
}
|
||||
|
||||
const plainMatch = line.match(/^-\s+(.+)/);
|
||||
if (plainMatch) {
|
||||
current.items.push({ title: plainMatch[1].trim(), desc: '' });
|
||||
}
|
||||
}
|
||||
|
||||
return sections.filter((section) => section.items.length > 0);
|
||||
}
|
||||
|
||||
export async function fetchGitHubChangelog(): Promise<ChangelogData> {
|
||||
try {
|
||||
const releases = await requestJson<GitHubRelease[]>(GITHUB_RELEASES_URL, {
|
||||
headers: { Accept: 'application/vnd.github+json' },
|
||||
});
|
||||
|
||||
return {
|
||||
updatedAt: new Date().toISOString(),
|
||||
releases: releases
|
||||
.filter((release) => !release.draft && !release.prerelease)
|
||||
.map((release) => ({
|
||||
tag: release.tag_name,
|
||||
name: release.name || release.tag_name,
|
||||
date: (release.published_at || '').slice(0, 10),
|
||||
sections: parseReleaseBody(release.body || ''),
|
||||
})),
|
||||
};
|
||||
} catch {
|
||||
return { updatedAt: '', releases: [] };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
export type InstallLang = 'en' | 'cn';
|
||||
|
||||
export type InstallOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
type DownloadArtifact = {
|
||||
id: string;
|
||||
labels: Record<InstallLang, string>;
|
||||
suffix: string;
|
||||
};
|
||||
|
||||
const DOWNLOAD_BASE_URL = 'https://dl.dbxio.com/releases/latest';
|
||||
|
||||
const downloadArtifacts: DownloadArtifact[] = [
|
||||
{
|
||||
id: 'macos-arm',
|
||||
labels: { en: 'For macOS (Apple Silicon)', cn: '适用于 macOS (Apple Silicon)' },
|
||||
suffix: 'aarch64.dmg',
|
||||
},
|
||||
{
|
||||
id: 'macos-intel',
|
||||
labels: { en: 'For macOS (Intel)', cn: '适用于 macOS (Intel)' },
|
||||
suffix: 'x64.dmg',
|
||||
},
|
||||
{
|
||||
id: 'windows',
|
||||
labels: { en: 'For Windows', cn: '适用于 Windows' },
|
||||
suffix: 'x64-setup.exe',
|
||||
},
|
||||
{
|
||||
id: 'linux',
|
||||
labels: { en: 'For Linux x64', cn: '适用于 Linux x64' },
|
||||
suffix: 'amd64.AppImage',
|
||||
},
|
||||
{
|
||||
id: 'linux-arm',
|
||||
labels: { en: 'For Linux ARM64', cn: '适用于 Linux ARM64' },
|
||||
suffix: 'aarch64.AppImage',
|
||||
},
|
||||
];
|
||||
|
||||
export function createInstallOptions(lang: InstallLang, version: string): InstallOption[] {
|
||||
return downloadArtifacts.map((artifact) => ({
|
||||
id: artifact.id,
|
||||
label: artifact.labels[lang],
|
||||
href: `${DOWNLOAD_BASE_URL}/DBX_${version}_${artifact.suffix}`,
|
||||
}));
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
function requestJsonWithXhr<T>(url: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'json';
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.onload = () => {
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
reject(new Error(`Request failed with status ${xhr.status}`));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve((xhr.response ?? JSON.parse(xhr.responseText)) as T);
|
||||
};
|
||||
xhr.onerror = () => reject(new Error('Request failed'));
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
export async function requestJson<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
if (typeof fetch === 'function') {
|
||||
const res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Request failed with status ${res.status}`);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
if (typeof XMLHttpRequest === 'function') {
|
||||
return requestJsonWithXhr<T>(url);
|
||||
}
|
||||
|
||||
throw new Error('No browser request API is available');
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import type { ChangelogRelease } from '@/lib/changelog';
|
||||
import type { LatestReleaseInfo } from '@/lib/latestRelease';
|
||||
|
||||
type LandingLatestUpdates = {
|
||||
version: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
link: string;
|
||||
items: string[];
|
||||
};
|
||||
|
||||
const text = {
|
||||
en: {
|
||||
title: 'Latest updates',
|
||||
desc: 'Mirrored from the latest GitHub release notes.',
|
||||
link: 'Read the changelog',
|
||||
fallbackItems: [
|
||||
'Desktop and Docker release assets',
|
||||
'Database workflow improvements',
|
||||
'Bug fixes and reliability updates',
|
||||
'Documentation and packaging updates',
|
||||
],
|
||||
},
|
||||
cn: {
|
||||
title: '最近更新',
|
||||
desc: '同步 GitHub 最新 Release Notes。',
|
||||
link: '查看更新日志',
|
||||
fallbackItems: [
|
||||
'桌面版与 Docker 发布资产',
|
||||
'数据库工作流改进',
|
||||
'问题修复与稳定性更新',
|
||||
'文档与打包流程更新',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function releaseItems(release: ChangelogRelease, lang: 'en' | 'cn') {
|
||||
const separator = lang === 'cn' ? ',' : ': ';
|
||||
|
||||
return release.sections
|
||||
.flatMap((section) => section.items)
|
||||
.map((item) => (item.desc ? `${item.title}${separator}${item.desc}` : item.title))
|
||||
.slice(0, 4);
|
||||
}
|
||||
|
||||
export function buildLandingLatestUpdates(
|
||||
lang: 'en' | 'cn',
|
||||
release: ChangelogRelease | undefined,
|
||||
appVersion: string,
|
||||
latestRelease?: LatestReleaseInfo | null,
|
||||
): LandingLatestUpdates {
|
||||
const t = text[lang];
|
||||
const items = release ? releaseItems(release, lang) : [];
|
||||
const version = latestRelease?.version ? `v${latestRelease.version}` : release?.tag ?? `v${appVersion}`;
|
||||
|
||||
return {
|
||||
version,
|
||||
title: t.title,
|
||||
desc: t.desc,
|
||||
link: t.link,
|
||||
items: items.length > 0 ? items : t.fallbackItems,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { requestJson } from '@/lib/httpJson';
|
||||
|
||||
export type LatestReleaseInfo = {
|
||||
version: string;
|
||||
notes?: string;
|
||||
pub_date?: string;
|
||||
};
|
||||
|
||||
type GitHubLatestRelease = {
|
||||
tag_name: string;
|
||||
body: string | null;
|
||||
published_at: string | null;
|
||||
};
|
||||
|
||||
const LATEST_RELEASE_URL = 'https://dl.dbxio.com/releases/latest/latest.json';
|
||||
const GITHUB_LATEST_RELEASE_URL = 'https://api.github.com/repos/t8y2/dbx/releases/latest';
|
||||
|
||||
function normalizeVersion(version: string) {
|
||||
return version.replace(/^v/, '');
|
||||
}
|
||||
|
||||
export async function fetchLatestReleaseInfo(): Promise<LatestReleaseInfo | null> {
|
||||
try {
|
||||
const release = await requestJson<LatestReleaseInfo>(LATEST_RELEASE_URL);
|
||||
return release.version ? { ...release, version: normalizeVersion(release.version) } : null;
|
||||
} catch {
|
||||
return fetchGitHubLatestReleaseInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchGitHubLatestReleaseInfo(): Promise<LatestReleaseInfo | null> {
|
||||
try {
|
||||
const release = await requestJson<GitHubLatestRelease>(GITHUB_LATEST_RELEASE_URL, {
|
||||
headers: { Accept: 'application/vnd.github+json' },
|
||||
});
|
||||
|
||||
return {
|
||||
version: normalizeVersion(release.tag_name),
|
||||
notes: release.body || undefined,
|
||||
pub_date: release.published_at || undefined,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { strict as assert } from "node:assert";
|
||||
import test from "node:test";
|
||||
import appPackage from "../package.json" with { type: "json" };
|
||||
import { createInstallOptions } from "../docs/lib/downloadLinks.ts";
|
||||
|
||||
test("docs install links are generated from the app package version", () => {
|
||||
const options = createInstallOptions("en", appPackage.version);
|
||||
const hrefs = options.map((option) => option.href);
|
||||
|
||||
assert.equal(hrefs.length, 5);
|
||||
assert.ok(hrefs.every((href) => href.includes(`/DBX_${appPackage.version}_`)));
|
||||
assert.equal(hrefs.some((href) => href.includes("0.5.9")), false);
|
||||
});
|
||||
|
||||
test("docs landing page reads latest release data instead of hard-coding the old latest update", () => {
|
||||
const source = readFileSync("docs/app/[lang]/page.tsx", "utf8");
|
||||
|
||||
assert.equal(source.includes("version: 'v0.5.4'"), false);
|
||||
assert.match(source, /LandingLatestUpdates/);
|
||||
assert.match(source, /fetchLatestReleaseInfo/);
|
||||
assert.match(source, /initialLatestRelease/);
|
||||
});
|
||||
|
||||
test("docs install widget reads the latest release version from latest.json at runtime", () => {
|
||||
const source = readFileSync("docs/components/landing/InstallTabs.tsx", "utf8");
|
||||
const latestRelease = readFileSync("docs/lib/latestRelease.ts", "utf8");
|
||||
|
||||
assert.equal(source.includes("fetchChangelog"), false);
|
||||
assert.match(source, /fetchLatestReleaseInfo/);
|
||||
assert.match(latestRelease, /latest\.json/);
|
||||
});
|
||||
|
||||
test("homepage update badge keeps latest.json version ahead of changelog tags", () => {
|
||||
const source = readFileSync("docs/components/landing/LandingLatestUpdates.tsx", "utf8");
|
||||
|
||||
assert.match(source, /Promise\.all\(\[fetchLatestReleaseInfo\(\), fetchChangelog\(lang\)\]\)/);
|
||||
assert.match(source, /releaseInfo \?\? initialLatestRelease/);
|
||||
});
|
||||
|
||||
test("docs changelog page loads releases in the browser from R2", () => {
|
||||
const source = readFileSync("docs/app/[lang]/changelog/page.tsx", "utf8");
|
||||
|
||||
assert.match(source, /initialData/);
|
||||
assert.match(source, /ChangelogRuntime/);
|
||||
});
|
||||
|
||||
test("legacy docs changelog pages do not keep stale release entries", () => {
|
||||
const en = readFileSync("docs/content/docs/changelog.mdx", "utf8");
|
||||
const cn = readFileSync("docs/content/docs/changelog.cn.mdx", "utf8");
|
||||
|
||||
assert.equal(en.includes("## v0.5.4"), false);
|
||||
assert.equal(cn.includes("## v0.5.4"), false);
|
||||
assert.match(en, /\/en\/changelog/);
|
||||
assert.match(cn, /\/cn\/changelog/);
|
||||
});
|
||||
|
||||
test("docs deploy does not rerun just to refresh R2 changelog data", () => {
|
||||
const source = readFileSync(".github/workflows/docs.yml", "utf8");
|
||||
|
||||
assert.equal(source.includes("workflow_run:"), false);
|
||||
assert.equal(source.includes("Sync Changelog to R2"), false);
|
||||
});
|
||||
|
||||
test("changelog sync configures R2 CORS for browser runtime reads", () => {
|
||||
const workflow = readFileSync(".github/workflows/sync-changelog.yml", "utf8");
|
||||
const cors = JSON.parse(readFileSync(".github/r2-cors.json", "utf8")) as {
|
||||
CORSRules: Array<{
|
||||
AllowedMethods: string[];
|
||||
AllowedOrigins: string[];
|
||||
}>;
|
||||
};
|
||||
const rule = cors.CORSRules[0];
|
||||
|
||||
assert.match(workflow, /put-bucket-cors/);
|
||||
assert.deepEqual(rule.AllowedOrigins, ["*"]);
|
||||
assert.ok(rule.AllowedMethods.includes("GET"));
|
||||
assert.ok(rule.AllowedMethods.includes("HEAD"));
|
||||
});
|
||||
|
||||
test("changelog sync also runs after release workflow publishes with GITHUB_TOKEN", () => {
|
||||
const workflow = readFileSync(".github/workflows/sync-changelog.yml", "utf8");
|
||||
|
||||
assert.match(workflow, /workflow_run:/);
|
||||
assert.match(workflow, /workflows: \['Release'\]/);
|
||||
assert.match(workflow, /github\.event\.workflow_run\.conclusion == 'success'/);
|
||||
});
|
||||
Loading…
Reference in New Issue