|
| 1 | +// Build-time release history, derived from the canonical package CHANGELOGs — |
| 2 | +// the same release notes the publish pipeline validates per `Release/<version>` |
| 3 | +// tag. Single source of truth: packages/<pkg>/CHANGELOG.md. Versions and per- |
| 4 | +// package notes are parsed here; publish dates come from pub.dev (baked below, |
| 5 | +// since the Pages build checks out shallow with no git tags). Chinese notes use |
| 6 | +// a translation overlay and fall back to English for any unmapped line. |
| 7 | + |
| 8 | +import { readFileSync } from "fs"; |
| 9 | +import { dirname, resolve, join } from "path"; |
| 10 | +import { fileURLToPath } from "url"; |
| 11 | + |
| 12 | +const __dirname = dirname(fileURLToPath(import.meta.url)); |
| 13 | +const PACKAGES_DIR = resolve(__dirname, "..", "..", "..", "packages"); |
| 14 | + |
| 15 | +// Publishable packages, in tier/display order. |
| 16 | +const PACKAGES = [ |
| 17 | + "dart_logging", |
| 18 | + "dart_node_core", |
| 19 | + "reflux", |
| 20 | + "dart_node_express", |
| 21 | + "dart_node_ws", |
| 22 | + "dart_node_better_sqlite3", |
| 23 | + "dart_node_sql_js", |
| 24 | + "dart_node_mcp", |
| 25 | + "dart_node_react", |
| 26 | + "dart_node_react_native", |
| 27 | +]; |
| 28 | + |
| 29 | +// Release dates (ISO) sourced from pub.dev — earliest publish across packages |
| 30 | +// for each version. Versions absent here render without a date. |
| 31 | +const RELEASE_DATES = { |
| 32 | + "0.13.0-beta": "2026-06-07", |
| 33 | + "0.12.0-beta": "2026-01-29", |
| 34 | + "0.11.0-beta": "2025-12-13", |
| 35 | + "0.9.0-beta": "2025-12-11", |
| 36 | + "0.8.0-beta": "2025-12-04", |
| 37 | + "0.2.0-beta": "2025-12-02", |
| 38 | + "0.1.0-beta": "2025-11-30", |
| 39 | +}; |
| 40 | + |
| 41 | +// Chinese overlay for changelog lines. Unmapped lines fall back to English. |
| 42 | +const ZH_NOTES = { |
| 43 | + "Maintenance release; no functional changes": "维护版本;无功能性更改", |
| 44 | + "Maintenance release": "维护版本", |
| 45 | + "Maintenance release; internal test improvements": "维护版本;内部测试改进", |
| 46 | + "Enhanced test coverage": "增强测试覆盖率", |
| 47 | + "Early release": "早期版本", |
| 48 | + "Initial beta release": "首个 Beta 版本", |
| 49 | + "Version bump for upcoming release": "为即将发布的版本提升版本号", |
| 50 | + "Updated docs": "更新文档", |
| 51 | + "Improved documentation for core extensions": "改进核心扩展的文档", |
| 52 | + "Pino-style structured logging framework": "Pino 风格的结构化日志框架", |
| 53 | + "Child loggers with inherited context": "支持继承上下文的子日志记录器", |
| 54 | + "Add filesystem (`fs`) bindings": "新增文件系统(`fs`)绑定", |
| 55 | + "Add child process interop": "新增子进程互操作", |
| 56 | + "Core JS interop utilities for Dart-to-JavaScript compilation": |
| 57 | + "用于 Dart 到 JavaScript 编译的核心 JS 互操作工具", |
| 58 | + "Foundation for dart_node package family": "dart_node 包系列的基础", |
| 59 | + "Add WebSocket support via dart_node_ws package": |
| 60 | + "通过 dart_node_ws 包新增 WebSocket 支持", |
| 61 | + "Redux-style predictable state container for Dart": |
| 62 | + "面向 Dart 的 Redux 风格可预测状态容器", |
| 63 | + "Sealed classes for exhaustive action pattern matching": |
| 64 | + "使用密封类实现详尽的 action 模式匹配", |
| 65 | + "Express.js bindings for Dart": "面向 Dart 的 Express.js 绑定", |
| 66 | + "Build Node.js HTTP servers entirely in Dart": |
| 67 | + "完全使用 Dart 构建 Node.js HTTP 服务器", |
| 68 | + "Add WebSocket support integration with dart_node_ws": |
| 69 | + "新增与 dart_node_ws 的 WebSocket 集成支持", |
| 70 | + "WebSocket bindings for Dart on Node.js": |
| 71 | + "面向 Node.js 上 Dart 的 WebSocket 绑定", |
| 72 | + "Build real-time WebSocket servers entirely in Dart": |
| 73 | + "完全使用 Dart 构建实时 WebSocket 服务器", |
| 74 | + "Typed Dart bindings for better-sqlite3 npm package": |
| 75 | + "为 better-sqlite3 npm 包提供类型化的 Dart 绑定", |
| 76 | + "Synchronous SQLite3 access with WAL mode": |
| 77 | + "支持 WAL 模式的同步 SQLite3 访问", |
| 78 | + "Typed Dart bindings for sql.js (SQLite compiled to WebAssembly)": |
| 79 | + "为 sql.js(编译为 WebAssembly 的 SQLite)提供类型化的 Dart 绑定", |
| 80 | + "Synchronous in-memory SQLite with file persistence on Node.js": |
| 81 | + "在 Node.js 上支持文件持久化的同步内存 SQLite", |
| 82 | + "`Result`-based API: open, prepare, exec, run, get, all, pragma": |
| 83 | + "基于 `Result` 的 API:open、prepare、exec、run、get、all、pragma", |
| 84 | + "Add streamable HTTP transport": "新增可流式传输的 HTTP 传输层", |
| 85 | + "Additional MCP server capabilities": "新增 MCP 服务器能力", |
| 86 | + "Typed Dart bindings for @modelcontextprotocol/sdk": |
| 87 | + "为 @modelcontextprotocol/sdk 提供类型化的 Dart 绑定", |
| 88 | + "Build MCP servers in Dart that run on Node.js": |
| 89 | + "使用 Dart 构建运行于 Node.js 的 MCP 服务器", |
| 90 | + "React bindings for Dart": "面向 Dart 的 React 绑定", |
| 91 | + "Build React web applications entirely in Dart": |
| 92 | + "完全使用 Dart 构建 React Web 应用", |
| 93 | + "React Native bindings for Dart": "面向 Dart 的 React Native 绑定", |
| 94 | + "Build mobile apps with Expo entirely in Dart": |
| 95 | + "完全使用 Dart 与 Expo 构建移动应用", |
| 96 | +}; |
| 97 | + |
| 98 | +const EN_MONTHS = [ |
| 99 | + "January", "February", "March", "April", "May", "June", |
| 100 | + "July", "August", "September", "October", "November", "December", |
| 101 | +]; |
| 102 | + |
| 103 | +const escapeHtml = (s) => |
| 104 | + s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
| 105 | + |
| 106 | +// Escape, then render inline `code` spans. Content is our own changelogs. |
| 107 | +const toHtml = (s) => |
| 108 | + escapeHtml(s).replace(/`([^`]+)`/g, "<code>$1</code>"); |
| 109 | + |
| 110 | +const formatDateEn = (iso) => { |
| 111 | + const [y, m, d] = iso.split("-").map((n) => parseInt(n, 10)); |
| 112 | + return `${EN_MONTHS[m - 1]} ${d}, ${y}`; |
| 113 | +}; |
| 114 | + |
| 115 | +const formatDateZh = (iso) => { |
| 116 | + const [y, m, d] = iso.split("-").map((n) => parseInt(n, 10)); |
| 117 | + return `${y}年${m}月${d}日`; |
| 118 | +}; |
| 119 | + |
| 120 | +const PRERELEASE_RANK = { alpha: 1, beta: 2 }; |
| 121 | + |
| 122 | +const parseVersion = (v) => { |
| 123 | + const [core, pre] = v.split("-"); |
| 124 | + const [major, minor, patch] = core.split(".").map((n) => parseInt(n, 10) || 0); |
| 125 | + const rank = pre ? (PRERELEASE_RANK[pre.split(".")[0]] ?? 3) : 4; |
| 126 | + return [major, minor, patch, rank]; |
| 127 | +}; |
| 128 | + |
| 129 | +const compareDesc = (a, b) => { |
| 130 | + const pa = parseVersion(a); |
| 131 | + const pb = parseVersion(b); |
| 132 | + for (let i = 0; i < pa.length; i += 1) { |
| 133 | + if (pa[i] !== pb[i]) return pb[i] - pa[i]; |
| 134 | + } |
| 135 | + return 0; |
| 136 | +}; |
| 137 | + |
| 138 | +const parseChangelog = (markdown) => { |
| 139 | + const byVersion = {}; |
| 140 | + let current = null; |
| 141 | + for (const line of markdown.split("\n")) { |
| 142 | + const header = line.match(/^##\s+(.+?)\s*$/); |
| 143 | + if (header) { |
| 144 | + current = header[1].trim(); |
| 145 | + byVersion[current] = []; |
| 146 | + continue; |
| 147 | + } |
| 148 | + if (!current) continue; |
| 149 | + const bullet = line.match(/^[-*]\s+(.+?)\s*$/); |
| 150 | + if (bullet) byVersion[current].push(bullet[1].trim()); |
| 151 | + } |
| 152 | + return byVersion; |
| 153 | +}; |
| 154 | + |
| 155 | +const versions = {}; |
| 156 | +for (const pkg of PACKAGES) { |
| 157 | + const file = join(PACKAGES_DIR, pkg, "CHANGELOG.md"); |
| 158 | + const parsed = parseChangelog(readFileSync(file, "utf-8")); |
| 159 | + for (const [version, changes] of Object.entries(parsed)) { |
| 160 | + if (changes.length === 0) continue; |
| 161 | + versions[version] ??= []; |
| 162 | + versions[version].push({ |
| 163 | + name: pkg, |
| 164 | + changes: changes.map((c) => ({ en: toHtml(c), zh: toHtml(ZH_NOTES[c] ?? c) })), |
| 165 | + }); |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +export default Object.keys(versions) |
| 170 | + .sort(compareDesc) |
| 171 | + .map((version) => { |
| 172 | + const iso = RELEASE_DATES[version] ?? null; |
| 173 | + return { |
| 174 | + version, |
| 175 | + dateISO: iso, |
| 176 | + dateEn: iso ? formatDateEn(iso) : null, |
| 177 | + dateZh: iso ? formatDateZh(iso) : null, |
| 178 | + packages: versions[version], |
| 179 | + }; |
| 180 | + }); |
0 commit comments