commit 3af4d06af681a85f366c8dd2b6a1faa5a4b96c35 Author: aricjean Date: Tue May 12 13:16:02 2026 -0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..1380ac3 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# infrastructure-runtime-adapter + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.13. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..e5a60b2 --- /dev/null +++ b/bun.lock @@ -0,0 +1,49 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "infrastructure-runtime-adapter", + "dependencies": { + "c12": "^4.0.0-beta.5", + "pathe": "^2.0.3", + "std-env": "^4.1.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + + "@types/node": ["@types/node@25.7.0", "", { "dependencies": { "undici-types": "~7.21.0" } }, "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg=="], + + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + + "c12": ["c12@4.0.0-beta.5", "", { "dependencies": { "confbox": "^0.2.4", "defu": "^6.1.7", "exsolve": "^1.0.8", "pathe": "^2.0.3", "pkg-types": "^2.3.1", "rc9": "^3.0.1" }, "peerDependencies": { "chokidar": "^5", "dotenv": "*", "giget": "*", "jiti": "*", "magicast": "*" }, "optionalPeers": ["chokidar", "dotenv", "giget", "jiti", "magicast"] }, "sha512-yWGCPCQGJeFq4R0mFg5HOhC3Rg+B0PCdM+ldXWUhughoGgeeq8/tjRmXh4/lmhKWyhf+KOFxB/JMXf0Yv1Fd5A=="], + + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], + + "rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="], + + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.21.0", "", {}, "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ=="], + } +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f67b2c6 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..4aab588 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "infrastructure-runtime-adapter", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "c12": "^4.0.0-beta.5", + "pathe": "^2.0.3", + "std-env": "^4.1.0" + } +} diff --git a/runtime/adapters/bun.ts b/runtime/adapters/bun.ts new file mode 100644 index 0000000..e1df68a --- /dev/null +++ b/runtime/adapters/bun.ts @@ -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, + }; + }, + }, +}); diff --git a/runtime/types.ts b/runtime/types.ts new file mode 100644 index 0000000..3005b42 --- /dev/null +++ b/runtime/types.ts @@ -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; + +export interface SpawnOptions { + cwd?: string; + env?: Record; + /** + * 参考 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; + kill: (signal?: string) => void; + // 符合 WinterCG 的流定义 + stdout?: ReadableStream; + stderr?: ReadableStream; + stdin?: WritableStream; +} + +export interface RuntimeContext { + fs: { + // --- 基础接口 + exists: (path: string) => Promise; + read: (path: string) => Promise; + /** 使用我们定义的兼容类型 */ + write: (path: string, data: CompatibleBodyInit) => Promise; + + /** + * 流式写入:适合导出大型 SQL 备份或持续写入日志 + * 遵循 Web 文件系统规范命名习惯 + * 返回 WritableStream,用于流式写入 + * 流式写入句柄 (对齐 Web WritableStream 概念) + * 适合大文件写入或持续日志 + */ + openWritable: (path: string) => WritableStream; + /** 创建目录:recursive 选项是实现“一键初始化数据库项目”的关键 */ + mkdir: ( + path: string, + options?: { recursive?: boolean }, + ) => Promise; + /** 删除:支持递归删除整个数据库文件夹 */ + remove: (path: string) => Promise; + /** 软链接:这就是你实现 dbs/ 链接的核心能力 */ + symlink: (target: string, path: string) => Promise; + /** 读取目录下的文件列表 */ + readdir: ( + path: string, + options?: { recursive?: boolean }, + ) => Promise; + + // --- 建议增加的增强接口 --- + /** 获取文件状态:用于判断配置文件是否被篡改或检查数据大小 */ + stat: (path: string) => Promise; + /** 监听文件:如果 .env 或 schema 变了,管理程序可以自动重载或提醒 */ + watch?: ( + path: string, + callback: (event: "change" | "rename") => void, + ) => () => void; + }; + proc: { + //test + spawn: (args: string[], options?: SpawnOptions) => ProcHandle; + }; +} diff --git a/runtime/utils/path.ts b/runtime/utils/path.ts new file mode 100644 index 0000000..a18fa8b --- /dev/null +++ b/runtime/utils/path.ts @@ -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; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b2e7497 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "types": ["bun"], + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}