UNPKG

@langgraph-js/bundler

Version:

Build tool for LangGraph.js applications that packages graph configurations into deployable modules

255 lines (252 loc) 8.36 kB
import { build } from 'vite'; import fs from 'fs'; import path from 'path'; import { nodeExternals } from 'rollup-plugin-node-externals'; // import { visualizer } from "rollup-plugin-visualizer"; import condition from './condition.js'; // 常量定义 const INVALID_AGENT_NAMES = [ 'auth', 'dev', 'start', 'entrypoint', 'callbacks', 'http', ]; const DEFAULT_DIST_DIR = './dist'; /** * 读取并验证 Langgraph 配置 */ function loadConfig(cwd) { const configPath = path.join(cwd, 'langgraph.json'); if (!fs.existsSync(configPath)) { throw new Error(`Cannot find langgraph.json in ${cwd}`); } const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); if (!config.graphs || Object.keys(config.graphs).length === 0) { throw new Error('No graphs defined in langgraph.json'); } return config; } /** * 准备输出目录 */ function prepareOutputDirectory(config, cwd) { const distDir = config.dist || DEFAULT_DIST_DIR; const absoluteDistDir = path.resolve(cwd, distDir); if (!fs.existsSync(absoluteDistDir)) { fs.mkdirSync(absoluteDistDir, { recursive: true }); } return absoluteDistDir; } /** * 处理构建入口点 */ function prepareEntries(config, cwd) { const entries = {}; // 处理图形入口点 for (const [name, graphPath] of Object.entries(config.graphs)) { const [filePath] = graphPath.split(':'); entries[name] = path.resolve(cwd, filePath); } // 处理认证入口点 if (config.auth?.path) { const [filePath] = config.auth.path.split(':'); entries['auth'] = path.resolve(cwd, filePath); } if (config.callbacks?.path) { const filePath = config.callbacks.path; entries['callbacks'] = path.resolve(cwd, filePath); } // 处理 HTTP 应用入口点 if (config.http?.app) { const [filePath] = config.http.app.split(':'); entries['http'] = path.resolve(cwd, filePath); } return entries; } /** * 创建用于构建的调整后的配置 */ function createBuildConfig(config) { const buildConfig = { ...config }; // 调整图形路径 if (buildConfig.graphs) { const adjustedGraphs = {}; for (const [name, graphPath] of Object.entries(buildConfig.graphs)) { const [_, exportName] = graphPath.split(':'); if (INVALID_AGENT_NAMES.includes(name)) { throw new Error(`Invalid agent name: ${name} ${graphPath}`); } else { adjustedGraphs[name] = `./${name}.js:${exportName || name}`; } } buildConfig.graphs = adjustedGraphs; } // 调整认证路径 if (buildConfig.auth?.path) { const [_, exportName] = buildConfig.auth.path.split(':'); buildConfig.auth.path = `./auth.js:${exportName || 'auth'}`; } if (buildConfig.callbacks?.path) { buildConfig.callbacks.path = `./callbacks.js`; } // 调整 HTTP 应用路径 if (buildConfig.http?.app) { const [_, exportName] = buildConfig.http.app.split(':'); buildConfig.http.app = `./http.js:${exportName || 'default'}`; } return buildConfig; } /** * 生成开发服务器代码 */ function generateDevCode(buildConfig) { return `import { startServer } from "@langgraph-js/api/server"; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const config = ${JSON.stringify(buildConfig, null, 2)}; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const cwd = process.env.LANGGRAPH_CWD || __dirname; const result = await startServer({ cwd, ...config, }); console.log("LangGraph is running on http://" + result.host); console.log(cwd); `; } /** * 生成Hono服务器入口点代码 */ function generateEntrypointCode(buildConfig) { return `import { createHonoServer } from "@langgraph-js/api/server"; import path from "node:path"; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const cwd = process.env.LANGGRAPH_CWD || __dirname; const config = ${JSON.stringify(buildConfig, null, 2)}; const schema = { cwd, ...config, }; const result = await createHonoServer(schema); export default { fetch: result.app.fetch, }; `; } /** * 准备辅助入口文件 */ function prepareHelperEntries(buildConfig, cwd) { const tempDir = path.join(cwd, '.langgraph-temp'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } const helperEntries = {}; // 创建开发服务器入口文件 const startFilePath = path.join(tempDir, 'start.js'); fs.writeFileSync(startFilePath, generateDevCode(buildConfig)); helperEntries['start'] = startFilePath; // 创建Hono服务器入口文件 const entrypointFilePath = path.join(tempDir, 'entrypoint.js'); fs.writeFileSync(entrypointFilePath, generateEntrypointCode(buildConfig)); helperEntries['entrypoint'] = entrypointFilePath; return { tempDir, entries: helperEntries }; } /** * 构建 Langgraph 项目 */ export async function buildLanggraph(cwd = process.cwd(), databaseType = 'postgres') { console.log(`Building langgraph in ${cwd}...\nUsing database type: ${databaseType}`); try { // 1. 加载配置 const config = loadConfig(cwd); // 2. 准备输出目录 const absoluteDistDir = prepareOutputDirectory(config, cwd); // 3. 准备主要入口点 const mainEntries = prepareEntries(config, cwd); // 4. 创建构建配置 const buildConfig = createBuildConfig(config); // 5. 准备辅助入口文件 const { tempDir, entries: helperEntries } = prepareHelperEntries(buildConfig, cwd); try { // 6. 构建主要图表和认证入口 await build({ configFile: false, plugins: [ nodeExternals({ deps: false, include: ['cloudflare:sockets', 'typescript'], }), ], define: { global: 'globalThis', }, build: { target: 'esnext', lib: { entry: mainEntries, formats: ['es'], }, outDir: absoluteDistDir, emptyOutDir: false, minify: true, commonjsOptions: { strictRequires: false, }, rollupOptions: { output: { preserveModules: false, exports: 'named', }, }, }, }); // 7. 构建辅助入口文件 await build({ configFile: false, mode: 'production', plugins: [ nodeExternals({ deps: false, include: ['cloudflare:sockets', 'typescript', 'better-sqlite3'], }), condition({ env: 'node-' + databaseType, }), ], define: { global: 'globalThis', }, build: { target: 'esnext', lib: { entry: helperEntries, formats: ['es'], fileName: (_, entryName) => `${entryName}.js`, }, outDir: absoluteDistDir, emptyOutDir: false, minify: true, }, }); console.log(`构建完成。输出目录: ${absoluteDistDir}`); } finally { // 清理临时目录 if (fs.existsSync(tempDir)) { fs.rmSync(tempDir, { recursive: true, force: true }); } } } catch (error) { console.error('构建失败:', error); throw error; } } //# sourceMappingURL=build.js.map