fix(docs): refresh release data from R2

This commit is contained in:
t8y2 2026-05-17 23:26:28 +08:00
parent f2426fac5e
commit 19f7cbd2ba
16 changed files with 550 additions and 470 deletions

11
.github/r2-cors.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}

View File

@ -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

View File

@ -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>
);

View File

@ -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]">

View File

@ -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} />;
}

View File

@ -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]);

View File

@ -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>
);
}

View File

@ -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)

View File

@ -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)

5
docs/lib/appVersion.ts Normal file
View File

@ -0,0 +1,5 @@
import appPackage from '../../package.json';
export function getAppVersion() {
return appPackage.version;
}

View File

@ -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: [] };
}

51
docs/lib/downloadLinks.ts Normal file
View File

@ -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}`,
}));
}

34
docs/lib/httpJson.ts Normal file
View File

@ -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');
}

63
docs/lib/landingLatest.ts Normal file
View File

@ -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,
};
}

45
docs/lib/latestRelease.ts Normal file
View File

@ -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;
}
}

View File

@ -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'/);
});