chore(deno): migrate toolchain from Node 24 + pnpm to Deno 2.9.7 + TypeScript 7.0.2 (W1.5)

- root deno.json workspace (nodeModulesDir auto), npm versions registered once in imports, deno.lock;
  remove pnpm-lock/pnpm-workspace/root package.json; members keep minimal package.json (name/type/exports)
- tasks: dev, typecheck (tsc 7.0.2 per member + deno check), lint/fmt (biome, useImportExtensions),
  test (vitest via deno), check:tokens, build (web + deno compile), ci, db:migrate/reset, gen:types
- tsconfig base moved to config/ (no root tsconfig.json); deno types -> types/deno.d.ts; web drops baseUrl
- server: Deno.serve entry with graceful shutdown, --role migrate|preflight|probe, WEB_ROOT static SPA hosting;
  drop @hono/node-server, tsx, esbuild build.mjs
- logger: redact child() bindings and serialize err by message/stack; sync stdout destination; tests
- relative imports get explicit extensions (sites/mp-sync paths left for follow-up)
- crash-recovery tests spawn `deno run -A`; tasks tests use unique per-file schemas
- single-container Dockerfile + entrypoint (migrate -> preflight -> api+worker watchdog), docker-compose.yml
- docs: README, runbook, ADR-001 superseded, plan §1 validation row

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-27 18:53:06 +08:00
1 parent d9f2af2f3c
commit 304feea141
226 files changed
+6601 -8620

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import type { HealthStatus } from "@umt/contracts";
import { apiData, TTL_SECOND } from "~/lib/api";
import { apiData, TTL_SECOND } from "~/lib/api.ts";
/** 服务健康状态,15 秒轮询 */
export function useHealth() {
@@ -1,7 +1,7 @@
import { KeyRound } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "~/components/ui/button";
import { Button } from "~/components/ui/button.tsx";
import {
Dialog,
DialogContent,
@@ -10,9 +10,9 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Input } from "~/components/ui/input";
import { clearAuth, getToken, setToken } from "~/lib/api";
} from "~/components/ui/dialog.tsx";
import { Input } from "~/components/ui/input.tsx";
import { clearAuth, getToken, setToken } from "~/lib/api.ts";
/** 设置本机使用的 API Key(存 localStorage,首版单管理员) */
export function ApiKeyDialog() {
@@ -1,7 +1,7 @@
import type { LucideIcon } from "lucide-react";
import { Inbox } from "lucide-react";
import type { ReactNode } from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
/** 空态:图标 + 标题 + 说明 + 可选操作。用于"尚无数据"与"功能建设中"占位 */
export function EmptyState({
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { findNavItem, NAV_ITEMS } from "./nav";
import { findNavItem, NAV_ITEMS } from "./nav.ts";
describe("控制台导航", () => {
it("12 个工作区,路径唯一", () => {
@@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
/** 控制台页面标题:标题 + 一句说明 + 右侧操作区 */
export function PageHeader({
@@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
export type StatusTone = "success" | "warning" | "destructive" | "neutral";
@@ -1,8 +1,8 @@
import { Construction } from "lucide-react";
import { useLocation } from "react-router";
import { EmptyState } from "./empty-state";
import { findNavItem } from "./nav";
import { PageHeader } from "./page-header";
import { EmptyState } from "./empty-state.tsx";
import { findNavItem } from "./nav.ts";
import { PageHeader } from "./page-header.tsx";
/** 工作区占位页:标题与说明取自导航表,正文为"建设中"空态 */
export function WorkspacePlaceholder({ milestone = "W3" }: { milestone?: string }) {
+1 -1
View File
@@ -1,4 +1,4 @@
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
/**
* 全站背景上左上角那束淡淡的棱镜漏光(样式见 app.css 的 .prism-leak)。
+2 -2
View File
@@ -3,8 +3,8 @@ import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { flushSync } from "react-dom";
import { Button } from "~/components/ui/button";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button.tsx";
import { cn } from "~/lib/utils.ts";
export function ThemeToggle({ className }: { className?: string }) {
const [mounted, setMounted] = useState(false);
+2 -2
View File
@@ -1,7 +1,7 @@
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
import type * as React from "react";
import { Button } from "~/components/ui/button";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button.tsx";
import { cn } from "~/lib/utils.ts";
function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
+1 -1
View File
@@ -3,7 +3,7 @@
import { Avatar as AvatarPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Avatar({
className,
+1 -1
View File
@@ -2,7 +2,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+1 -1
View File
@@ -2,7 +2,7 @@ import { ChevronRight, MoreHorizontal } from "lucide-react";
import { Slot } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
+1 -1
View File
@@ -2,7 +2,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
const buttonVariants = cva(
// 全站只有这一套按钮(与动漫站同一思路):形状一律胶囊 / 正圆,颜色与材质只由 variant 决定,
+1 -1
View File
@@ -2,7 +2,7 @@ import { zhCN } from "date-fns/locale";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker, type DayPickerProps } from "react-day-picker";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
// 日历(react-day-picker v10)的玻璃族皮肤:全部走语义 token + 玻璃 hover
// 语言(中性灰纱、选中 primary 药丸 + 顶部内高光、今日描边)。键盘导航
+1 -1
View File
@@ -1,6 +1,6 @@
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
+2 -2
View File
@@ -1,8 +1,8 @@
import { XIcon } from "lucide-react";
import { Dialog as DialogPrimitive } from "radix-ui";
import type * as React from "react";
import { Button } from "~/components/ui/button";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button.tsx";
import { cn } from "~/lib/utils.ts";
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
+1 -1
View File
@@ -1,6 +1,6 @@
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
+1 -1
View File
@@ -3,7 +3,7 @@
import { Label as LabelPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
+1 -1
View File
@@ -1,5 +1,5 @@
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
const PAGE_SIZE_OPTIONS = [10, 15, 20, 50, 100];
+1 -1
View File
@@ -1,7 +1,7 @@
import { Popover as PopoverPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
+1 -1
View File
@@ -2,7 +2,7 @@ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { Select as SelectPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
+1 -1
View File
@@ -1,7 +1,7 @@
import { Separator as SeparatorPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Separator({
className,
+1 -1
View File
@@ -2,7 +2,7 @@ import { XIcon } from "lucide-react";
import { Dialog as SheetPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
+1 -1
View File
@@ -1,7 +1,7 @@
import { Slider as SliderPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Slider({
className,
+1 -1
View File
@@ -1,7 +1,7 @@
import { Switch as SwitchPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "~/lib/utils";
import { cn } from "~/lib/utils.ts";
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
+1 -1
View File
@@ -1,4 +1,4 @@
// 由 scripts/gen-design-tokens.mjs 从 app/app.css 生成,不要手改;改了 app.css 跑 pnpm gen:tokens
// 由 scripts/gen-design-tokens.mjs 从 app/app.css 生成,不要手改;改了 app.css 跑 deno task gen:tokens
export const DESIGN_TOKENS = {
"fontSizes": [
"micro",
+1 -1
View File
@@ -1,7 +1,7 @@
import { type ClassValue, clsx } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";
import { DESIGN_TOKENS } from "./design-tokens.generated";
import { DESIGN_TOKENS } from "./design-tokens.generated.ts";
/**
* 自定义工具类的 token(字号 / 圆角 / 缓动 / 时长)要告诉 tailwind-merge 它们属于哪一组:
+1 -1
View File
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ThemeProvider } from "next-themes";
import { useState } from "react";
import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import { Toaster } from "~/components/ui/sonner";
import { Toaster } from "~/components/ui/sonner.tsx";
import type { Route } from "./+types/root";
import "@fontsource-variable/inter-tight";
+4 -4
View File
@@ -1,8 +1,8 @@
import { Bot, Send } from "lucide-react";
import { EmptyState } from "~/components/console/empty-state";
import { PageHeader } from "~/components/console/page-header";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { EmptyState } from "~/components/console/empty-state.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { Button } from "~/components/ui/button.tsx";
import { Input } from "~/components/ui/input.tsx";
/** Agent 会话页占位:消息流 + 输入框骨架,W2/W3 接入 agent_run 与 SSE 增量 */
export default function AgentSession() {
+5 -5
View File
@@ -1,9 +1,9 @@
import { Activity, AlertTriangle, CircleCheck, HardDrive, ListTodo, Radio, Sparkles } from "lucide-react";
import { useHealth } from "~/api/health";
import { EmptyState } from "~/components/console/empty-state";
import { PageHeader } from "~/components/console/page-header";
import { StatusBadge } from "~/components/console/status-badge";
import { formatDateTime } from "~/lib/utils";
import { useHealth } from "~/api/health.ts";
import { EmptyState } from "~/components/console/empty-state.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { formatDateTime } from "~/lib/utils.ts";
// 首页六区块(REQ §13.4.3),W3 接入正式状态投影
const SECTIONS = [
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function IdentityPage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function InboxPage() {
+7 -7
View File
@@ -1,13 +1,13 @@
import { ChevronLeft, ChevronRight, Clapperboard, Menu } from "lucide-react";
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { Link, NavLink, Outlet, useLocation } from "react-router";
import { ApiKeyDialog } from "~/components/console/api-key-dialog";
import { findNavItem, NAV_GROUPS, NAV_ITEMS, type NavGroup } from "~/components/console/nav";
import PrismLeak from "~/components/prism-leak";
import { ThemeToggle } from "~/components/theme-toggle";
import { Button } from "~/components/ui/button";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "~/components/ui/sheet";
import { cn } from "~/lib/utils";
import { ApiKeyDialog } from "~/components/console/api-key-dialog.tsx";
import { findNavItem, NAV_GROUPS, NAV_ITEMS, type NavGroup } from "~/components/console/nav.ts";
import PrismLeak from "~/components/prism-leak.tsx";
import { ThemeToggle } from "~/components/theme-toggle.tsx";
import { Button } from "~/components/ui/button.tsx";
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "~/components/ui/sheet.tsx";
import { cn } from "~/lib/utils.ts";
const PORTAL_NAME = "媒体自动化";
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function LibraryPage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function OrganizePage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function PreferencesPage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function SettingsPage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function SitesPage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function SourcesPage() {
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function SubscriptionsPage() {
+1 -1
View File
@@ -1,4 +1,4 @@
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder";
import { WorkspacePlaceholder } from "~/components/console/workspace-placeholder.tsx";
// 占位:标题与说明取自 components/console/nav.ts
export default function TasksPage() {
+2 -2
View File
@@ -1,6 +1,6 @@
import { Link } from "react-router";
import { EmptyState } from "~/components/console/empty-state";
import { buttonVariants } from "~/components/ui/button";
import { EmptyState } from "~/components/console/empty-state.tsx";
import { buttonVariants } from "~/components/ui/button.tsx";
export default function NotFound() {
return (