Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions internal/build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# LangChain Build System

A modern build system for LangChain JavaScript/TypeScript packages that provides fast compilation, type checking, automated secret management, and advanced code generation for monorepo workspaces.

## Overview

This build system is designed to handle the complex requirements of LangChain's multi-package monorepo. It automatically discovers packages in the workspace, compiles them with optimal settings, and includes specialized tooling for LangChain's security patterns and dynamic loading capabilities.

### Key Features

- 🚀 **Fast Compilation**: Uses [tsdown](https://github.com/privatenumber/tsdown) for high-performance TypeScript bundling with Rolldown
- 📦 **Monorepo Aware**: Automatically discovers and builds all non-private packages in yarn workspaces
- 🔍 **Secret Management**: Built-in scanning and validation of LangChain's `lc_secrets` patterns
- 📝 **Type Generation**: Generates both ESM and CommonJS outputs with TypeScript declarations
- ✅ **Quality Checks**: Integrated type checking with [arethetypeswrong](https://github.com/arethetypeswrong/arethetypeswrong), [publint](https://github.com/bluwy/publint), and unused dependency detection
- 🗺️ **Import Maps**: Automatic generation of import maps for convenient bulk imports
- 📋 **Import Constants**: Dynamic detection and export of optional dependency entrypoints
- 🎯 **Selective Building**: Build all packages or target specific ones with flexible filtering
- 👀 **Watch Mode**: Real-time compilation with file watching capabilities
- 🛠️ **Rich CLI**: Full-featured command-line interface with comprehensive options

## Architecture

The build system consists of:

```
infra/build/
├── index.ts # Main build orchestrator
├── cli.ts # Command-line interface
├── types.ts # TypeScript type definitions
├── utils.ts # Utility functions
├── plugins/
│ ├── README.md # Plugin documentation
│ ├── lc-secrets.ts # LangChain secrets scanning plugin
│ ├── import-map.ts # Import map generation plugin
│ └── import-constants.ts # Import constants generation plugin
├── package.json # Build system dependencies
└── README.md # This documentation
```

### Core Technologies

- **[tsdown](https://github.com/privatenumber/tsdown)** - Fast TypeScript bundler with Rolldown
- **[TypeScript Compiler API](https://github.com/microsoft/TypeScript)** - For source code analysis and type checking
- **[yarn workspaces](https://yarnpkg.com/features/workspaces)** - For monorepo package discovery
- **[unplugin-unused](https://github.com/unplugin/unplugin-unused)** - For unused dependency detection
- **Node.js built-ins** - File system operations and process management

## Usage

### CLI Commands

```bash
# Get help
yarn build:new --help

# Build all packages in the workspace
yarn build:new

# Build with watch mode for development
yarn build:new --watch

# Build specific packages
yarn build:new @langchain/core
yarn build:new @langchain/core langchain @langchain/openai

# Exclude packages from build
yarn build:new --exclude @langchain/community
yarn build:new -e @langchain/aws -e @langchain/openai

# Skip various build steps
yarn build:new --no-emit # Skip type declarations
yarn build:new --skip-unused # Skip unused dependency check
yarn build:new --skip-clean # Skip cleaning build directory
yarn build:new --skip-sourcemap # Skip sourcemap generation
```

## Development

### Adding New Packages

1. Create package directory under appropriate workspace
2. Add `package.json` with proper exports field
3. Add `tsconfig.json` extending workspace config
4. Run build - it will be automatically discovered

### package.json Requirements

Each package must have a properly configured `exports` field that includes an `input` property to tell the build system which source file to compile for each entrypoint:

```json
{
"name": "@langchain/example",
"exports": {
".": {
"input": "./src/index.ts", // ← Required: Source file for this entrypoint
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./tools": {
"input": "./src/tools/index.ts", // ← Required: Source file for tools entrypoint
"import": "./dist/tools/index.js",
"require": "./dist/tools/index.cjs",
"types": "./dist/tools/index.d.ts"
}
}
}
```

**Important**: The `input` property is required for the build system to understand which TypeScript source file should be compiled for each export. Without this property, the entrypoint will be ignored during build.
229 changes: 229 additions & 0 deletions internal/build/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
#!/usr/bin/env node
import { parseArgs } from "node:util";
import { compilePackages } from "./index.js";
import type { CompilePackageOptions } from "./types.js";

/**
* CLI configuration with descriptions for auto-generated help
*/
const cliName = "yarn build:new";
const cliConfig = {
name: cliName,
description:
"CLI program for compiling or watching packages in the repository",
options: {
watch: {
type: "boolean" as const,
short: "w",
default: false,
description: "Watch for changes and recompile automatically",
},
help: {
type: "boolean" as const,
short: "h",
default: false,
description: "Show this help message",
},
exclude: {
type: "string" as const,
short: "e",
multiple: true,
description:
"Exclude packages from the build (can be specified multiple times)",
},
noEmit: {
type: "boolean" as const,
default: false,
description: "Skip emitting type declarations",
},
skipUnused: {
type: "boolean" as const,
default: false,
description: "Skip unused dependency check on packages",
},
skipClean: {
type: "boolean" as const,
default: false,
description: "Skip cleaning the build directory",
},
skipSourcemap: {
type: "boolean" as const,
default: false,
description:
"Skip generating sourcemaps (e.g. `.map` and `.d.ts.map` files)",
},
},
/**
* only supported in later node versions
*/
positionals: {
name: "package-query...",
description:
" Optional queries to filter packages (e.g., package name patterns)\n Multiple queries can be provided and will be processed together",
},
examples: [
{ command: cliName, description: "Compile all packages" },
{
command: `${cliName} --watch`,
description: "Watch and recompile all packages",
},
{
command: `${cliName} langchain`,
description: 'Compile packages matching "langchain"',
},
{
command: `${cliName} langchain core`,
description: 'Compile packages matching "langchain" or "core"',
},
{
command: `${cliName} --no-emit`,
description: "Compile all packages without emitting type declarations",
},
{
command: `${cliName} --watch core openai`,
description: 'Watch packages matching "core" or "openai"',
},
{
command: `${cliName} --exclude langchain-community`,
description: "Compile all packages except langchain-community",
},
{
command: `${cliName} --exclude langchain-community --exclude langchain-aws`,
description:
"Compile all packages except langchain-community and langchain-aws",
},
{
command: `${cliName} -e community -e aws langchain`,
description:
'Compile packages matching "langchain" but exclude those matching "community" or "aws"',
},
],
};

/**
* Generate help text from CLI configuration
*/
function generateHelp(config: typeof cliConfig): string {
const lines: string[] = [];

lines.push(`Usage: ${config.name} [options] [${config.positionals.name}]`);
lines.push("");
lines.push("Options:");

Object.entries(config.options).forEach(([key, option]) => {
const shortFlag =
"short" in option && option.short ? `-${option.short}, ` : " ";
const longFlag = `--${key}`;
const padding = " ".repeat(Math.max(0, 15 - longFlag.length));
lines.push(
` ${shortFlag}${longFlag}${padding}${option.description}${
"default" in option && option.default
? ` (default: ${option.default})`
: ""
}`
);
});

lines.push("");
lines.push("Arguments:");
lines.push(
` ${config.positionals.name.padEnd(15)}${config.positionals.description}`
);

lines.push("");
lines.push("Examples:");
config.examples.forEach((example) => {
lines.push(` # ${example.description}`);
lines.push(` ${example.command}`);
lines.push("");
});

lines.push(
`Copyright © ${new Date().getFullYear()} LangChain, Inc. All rights reserved.`
);
return lines.join("\n");
}

/**
* CLI program for compiling or watching packages in the repository
*/
async function main() {
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: cliConfig.options,
allowPositionals: true,
});

if (values.help) {
console.log(generateHelp(cliConfig));
process.exit(0);
}

const packageQuery = positionals;
const watch = values.watch;
const noEmit = values.noEmit;
const skipUnused = values.skipUnused;
const skipClean = values.skipClean;
const skipSourcemap = values.skipSourcemap;
const exclude = Array.isArray(values.exclude)
? values.exclude
: values.exclude
? [values.exclude]
: [];

const opts: CompilePackageOptions = {
packageQuery,
watch,
exclude,
noEmit,
skipUnused,
skipClean,
skipSourcemap,
};

try {
const packageLabel =
packageQuery.length > 0
? `packages matching: ${packageQuery.map((q) => `"${q}"`).join(", ")}`
: "all packages";
console.log(`${watch ? "Watching" : "Compiling"} ${packageLabel}...`);
if (exclude.length > 0) {
console.log(`Excluding: ${exclude.map((q) => `"${q}"`).join(", ")}`);
}
await compilePackages(opts);

if (!watch) {
console.log("✅ Compilation completed successfully!");
}
} catch (error) {
console.error("❌ Compilation failed:", error);
process.exit(1);
}
}

// Handle uncaught errors
process.on("uncaughtException", (error) => {
console.error("❌ Uncaught exception:", error);
process.exit(1);
});

process.on("unhandledRejection", (reason) => {
console.error("❌ Unhandled rejection:", reason);
process.exit(1);
});

// Handle graceful shutdown for watch mode
process.on("SIGINT", () => {
console.log("\n👋 Gracefully shutting down...");
process.exit(0);
});

process.on("SIGTERM", () => {
console.log("\n👋 Gracefully shutting down...");
process.exit(0);
});

main().catch((error) => {
console.error("❌ CLI execution failed:", error);
process.exit(1);
});
Loading
Loading