This commit is contained in:
aricjean
2026-05-12 13:16:02 -07:00
commit 3af4d06af6
9 changed files with 323 additions and 0 deletions

80
runtime/adapters/bun.ts Normal file
View File

@@ -0,0 +1,80 @@
import type {
RuntimeContext,
ProcHandle,
CompatibleBodyInit,
SpawnOptions,
} from "../types";
import { resolve } from "pathe"; // Unjs 标准:跨平台路径处理
import { isCI } from "std-env";
export const createBunRuntime = (): RuntimeContext => ({
fs: {
exists: (path) => Bun.file(path).exists(),
// 返回标准 Response极大简化了后续的文件解析逻辑
read: async (path) => new Response(Bun.file(path)),
write: async (path, data: CompatibleBodyInit) => {
await Bun.write(path, data as any);
},
openWritable: (path) => {
const file = Bun.file(path);
// 利用 Bun.file().writer() 转换为符合 Web 标准的 WritableStream
return new WritableStream({
async write(chunk) {
const writer = file.writer();
writer.write(chunk);
writer.flush();
},
});
},
mkdir: async (path, options) => {
const fs = await import("node:fs/promises");
await fs.mkdir(path, options);
},
remove: async (path) => {
const fs = await import("node:fs/promises");
await fs.rm(path, { recursive: true, force: true });
},
symlink: async (target, path) => {
const fs = await import("node:fs/promises");
await fs.symlink(target, path);
},
readdir: async (path) => {
const fs = await import("node:fs/promises");
return await fs.readdir(path);
},
stat: async (path) => {
const { stat } = await import("node:fs/promises");
const s = await stat(path);
return {
size: s.size,
mtime: s.mtime,
isDirectory: s.isDirectory(),
};
},
},
proc: {
spawn: (args: string[], options: SpawnOptions = {}): ProcHandle => {
// 核心改进:自动处理交互逻辑
// 如果是在 CI 环境,强制使用 pipe 避免挂起;否则根据用户需求或自动选择
const mode = options.stdio || (isCI ? "pipe" : "inherit");
const process = Bun.spawn(args, {
cwd: options.cwd,
env: options.env,
stdout: mode,
stderr: mode,
stdin: mode,
});
return {
pid: process.pid,
exited: process.exited,
kill: (sig) => process.kill(sig as any),
// 仅在非继承模式下暴露流,避免类型与行为冲突
stdout: mode === "pipe" ? (process.stdout as any) : undefined,
stderr: mode === "pipe" ? (process.stderr as any) : undefined,
stdin: mode === "pipe" ? (process.stdin as any) : undefined,
};
},
},
});

85
runtime/types.ts Normal file
View File

@@ -0,0 +1,85 @@
import type { ReadableStream, WritableStream } from "node:stream/web";
/**
* 显式定义 BodyInit 兼容类型,确保在没有 DOM 类型的环境下也能编译
* 涵盖了 string, Blob, ArrayBuffer, 以及 ReadableStream
*/
export type CompatibleBodyInit =
| string
| Blob
| ArrayBuffer
| Uint8Array
| ReadableStream<Uint8Array>;
export interface SpawnOptions {
cwd?: string;
env?: Record<string, string>;
/**
* 参考 Unjs/Nitro 的设计:
* 'pipe': 走 Web Stream适合自动化处理
* 'inherit': 走物理 TTY适合交互式 CLI (如你的 drizzle-kit 报错场景)
*/
stdio?: "pipe" | "inherit";
}
export interface FileStat {
size: number;
mtime: Date;
isDirectory: boolean;
}
export interface ProcHandle {
pid?: number;
exited: Promise<number | null>;
kill: (signal?: string) => void;
// 符合 WinterCG 的流定义
stdout?: ReadableStream<Uint8Array>;
stderr?: ReadableStream<Uint8Array>;
stdin?: WritableStream<Uint8Array>;
}
export interface RuntimeContext {
fs: {
// --- 基础接口
exists: (path: string) => Promise<boolean>;
read: (path: string) => Promise<Response>;
/** 使用我们定义的兼容类型 */
write: (path: string, data: CompatibleBodyInit) => Promise<void>;
/**
* 流式写入:适合导出大型 SQL 备份或持续写入日志
* 遵循 Web 文件系统规范命名习惯
* 返回 WritableStream用于流式写入
* 流式写入句柄 (对齐 Web WritableStream 概念)
* 适合大文件写入或持续日志
*/
openWritable: (path: string) => WritableStream<Uint8Array>;
/** 创建目录recursive 选项是实现“一键初始化数据库项目”的关键 */
mkdir: (
path: string,
options?: { recursive?: boolean },
) => Promise<void>;
/** 删除:支持递归删除整个数据库文件夹 */
remove: (path: string) => Promise<void>;
/** 软链接:这就是你实现 dbs/ 链接的核心能力 */
symlink: (target: string, path: string) => Promise<void>;
/** 读取目录下的文件列表 */
readdir: (
path: string,
options?: { recursive?: boolean },
) => Promise<string[]>;
// --- 建议增加的增强接口 ---
/** 获取文件状态:用于判断配置文件是否被篡改或检查数据大小 */
stat: (path: string) => Promise<FileStat>;
/** 监听文件:如果 .env 或 schema 变了,管理程序可以自动重载或提醒 */
watch?: (
path: string,
callback: (event: "change" | "rename") => void,
) => () => void;
};
proc: {
//test
spawn: (args: string[], options?: SpawnOptions) => ProcHandle;
};
}

12
runtime/utils/path.ts Normal file
View File

@@ -0,0 +1,12 @@
import { isWindows } from "std-env";
/**
* 获取跨平台兼容的二进制名称 (逻辑层)
*/
export function getBinName(name: string) {
// 仅在 Windows 且没有后缀时尝试添加 .exe
if (isWindows && !name.endsWith(".exe") && !name.includes(".")) {
return `${name}.exe`;
}
return name;
}