mastra
Version:
cli for mastra
472 lines (468 loc) • 17.1 kB
JavaScript
import { PosthogAnalytics } from './chunk-SDQ6DRUS.js';
export { PosthogAnalytics } from './chunk-SDQ6DRUS.js';
import { DepsService, create, checkPkgJson, checkAndInstallCoreDeps, interactivePrompt, init, logger, FileService as FileService$1 } from './chunk-4QQ2IFQI.js';
export { create } from './chunk-4QQ2IFQI.js';
import { Command } from 'commander';
import { join as join$1, dirname, basename } from 'node:path';
import { getWatcherInputOptions, writeTelemetryConfig, createWatcher, FileService as FileService$2 } from '@mastra/deployer/build';
import { Bundler } from '@mastra/deployer/bundler';
import * as fsExtra2 from 'fs-extra';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { join } from 'path';
import { FileService, getDeployer } from '@mastra/deployer';
import { execa } from 'execa';
import { stat } from 'node:fs/promises';
import { config } from 'dotenv';
var BuildBundler = class extends Bundler {
constructor() {
super("Build");
}
getEnvFiles() {
const possibleFiles = [".env.production", ".env.local", ".env"];
try {
const fileService = new FileService$2();
const envFile = fileService.getFirstExistingFile(possibleFiles);
return Promise.resolve([envFile]);
} catch (err) {
}
return Promise.resolve([]);
}
async prepare(outputDirectory) {
await super.prepare(outputDirectory);
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const playgroundServePath = join$1(outputDirectory, this.outputDir, "playground");
await fsExtra2.copy(join$1(dirname(__dirname), "src/playground/dist"), playgroundServePath, {
overwrite: true
});
}
bundle(entryFile, outputDirectory) {
return this._bundle(this.getEntry(), entryFile, outputDirectory);
}
getEntry() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
return readFileSync(join$1(__dirname, "templates", "dev.entry.js"), "utf8");
}
};
// src/commands/build/build.ts
async function build({ dir }) {
const mastraDir = dir ?? process.cwd();
const outputDirectory = join$1(mastraDir, ".mastra");
const deployer = new BuildBundler();
const fs = new FileService$1();
const mastraEntryFile = fs.getFirstExistingFile([
join$1(mastraDir, "src", "mastra", "index.ts"),
join$1(mastraDir, "src", "mastra", "index.js")
]);
console.log(join$1(mastraDir, "index.ts"), join$1(mastraDir, "index.js"));
await deployer.prepare(outputDirectory);
await deployer.bundle(mastraEntryFile, outputDirectory);
}
async function deploy({ dir }) {
let mastraDir = dir || join(process.cwd(), "src/mastra");
try {
const outputDirectory = join(process.cwd(), ".mastra");
const fs = new FileService$1();
const mastraEntryFile = fs.getFirstExistingFile([join(mastraDir, "index.ts"), join(mastraDir, "index.js")]);
const deployer = await getDeployer(mastraEntryFile, outputDirectory);
if (!deployer) {
logger.warn("No deployer found.");
return;
}
try {
await deployer.prepare(outputDirectory);
await deployer.bundle(mastraEntryFile, outputDirectory);
try {
await deployer.deploy(outputDirectory);
} catch (error) {
console.error("[Mastra Deploy] - Error deploying:", error);
}
} catch (err) {
if (err instanceof Error) {
logger.debug(`error: ${err.message}`, { error: err });
}
}
} catch (error) {
if (error instanceof Error) {
logger.debug(`error: ${error.message}`, { error });
}
logger.warn("No deployer found.");
}
}
var DevBundler = class extends Bundler {
mastraToolsPaths = [];
constructor() {
super("Dev");
}
getEnvFiles() {
const possibleFiles = [".env.development", ".env.local", ".env"];
try {
const fileService = new FileService();
const envFile = fileService.getFirstExistingFile(possibleFiles);
return Promise.resolve([envFile]);
} catch {
}
return Promise.resolve([]);
}
async loadEnvVars() {
const superEnvVars = await super.loadEnvVars();
superEnvVars.set("MASTRA_TOOLS_PATH", this.mastraToolsPaths.join(","));
return superEnvVars;
}
async writePackageJson() {
}
async prepare(outputDirectory) {
await super.prepare(outputDirectory);
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const playgroundServePath = join$1(outputDirectory, this.outputDir, "playground");
await fsExtra2.copy(join$1(dirname(__dirname), "src/playground/dist"), playgroundServePath, {
overwrite: true
});
}
async watch(entryFile, outputDirectory, toolsPaths) {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envFiles = await this.getEnvFiles();
const inputOptions = await getWatcherInputOptions(entryFile, "node");
await writeTelemetryConfig(entryFile, join$1(outputDirectory, this.outputDir));
await this.writeInstrumentationFile(join$1(outputDirectory, this.outputDir));
if (toolsPaths?.length) {
for (const toolPath of toolsPaths) {
if (await fsExtra2.pathExists(toolPath)) {
const toolName = basename(toolPath);
const toolOutputPath = join$1(outputDirectory, this.outputDir, "tools", toolName);
const fileService = new FileService();
const entryFile2 = fileService.getFirstExistingFile([
join$1(toolPath, "index.ts"),
join$1(toolPath, "index.js"),
toolPath
// if toolPath itself is a file
]);
if (!entryFile2 || (await stat(entryFile2)).isDirectory()) {
this.logger.warn(`No entry file found in ${toolPath}, skipping...`);
continue;
}
const toolInputOptions = await getWatcherInputOptions(entryFile2, "node");
const watcher2 = await createWatcher(
{
...toolInputOptions,
input: {
index: entryFile2
}
},
{
dir: toolOutputPath
}
);
await new Promise((resolve, reject) => {
const cb = (event) => {
if (event.code === "BUNDLE_END") {
watcher2.off("event", cb);
resolve(void 0);
}
if (event.code === "ERROR") {
watcher2.off("event", cb);
reject(event);
}
};
watcher2.on("event", cb);
});
this.mastraToolsPaths.push(join$1(toolOutputPath, "index.mjs"));
} else {
this.logger.warn(`Tool path ${toolPath} does not exist, skipping...`);
}
}
}
const outputDir = join$1(outputDirectory, this.outputDir);
const copyPublic = this.copyPublic.bind(this);
const watcher = await createWatcher(
{
...inputOptions,
plugins: [
// @ts-ignore - types are good
// eslint-disable-next-line @typescript-eslint/no-misused-promises
...inputOptions.plugins,
{
name: "env-watcher",
buildStart() {
for (const envFile of envFiles) {
this.addWatchFile(envFile);
}
}
},
{
name: "tools-watcher",
buildStart() {
if (toolsPaths?.length) {
for (const toolPath of toolsPaths) {
this.addWatchFile(toolPath);
}
}
}
},
{
name: "public-dir-watcher",
buildStart() {
this.addWatchFile(join$1(dirname(entryFile), "public"));
},
buildEnd() {
return copyPublic(dirname(entryFile), outputDirectory);
}
}
],
input: {
index: join$1(__dirname, "templates", "dev.entry.js")
}
},
{
dir: outputDir
}
);
this.logger.info("Starting watcher...");
return new Promise((resolve, reject) => {
const cb = (event) => {
if (event.code === "BUNDLE_END") {
this.logger.info("Bundling finished, starting server...");
watcher.off("event", cb);
resolve(watcher);
}
if (event.code === "ERROR") {
console.log(event);
this.logger.error("Bundling failed, stopping watcher...");
watcher.off("event", cb);
reject(event);
}
};
watcher.on("event", cb);
});
}
async bundle() {
}
};
// src/commands/dev/dev.ts
var currentServerProcess;
var isRestarting = false;
var startServer = async (dotMastraPath, port, env) => {
try {
logger.info("[Mastra Dev] - Starting server...");
const instrumentation = import.meta.resolve("@opentelemetry/instrumentation/hook.mjs");
currentServerProcess = execa(
"node",
["--import=./instrumentation.mjs", `--import=${instrumentation}`, "index.mjs"],
{
cwd: dotMastraPath,
env: {
...Object.fromEntries(env),
PORT: port.toString() || process.env.PORT || "4111",
MASTRA_DEFAULT_STORAGE_URL: `file:${join(dotMastraPath, "..", "mastra.db")}`
},
stdio: "inherit",
reject: false
}
);
if (currentServerProcess?.exitCode && currentServerProcess?.exitCode !== 0) {
if (!currentServerProcess) {
throw new Error(`Server failed to start`);
}
throw new Error(`Server failed to start with error: ${currentServerProcess.stderr}`);
}
await new Promise((resolve) => setTimeout(resolve, 1e3));
try {
await fetch(`http://localhost:${port}/__refresh`, {
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
} catch {
await new Promise((resolve) => setTimeout(resolve, 1500));
try {
await fetch(`http://localhost:${port}/__refresh`, {
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
} catch {
}
}
if (currentServerProcess.exitCode !== null) {
logger.error("Server failed to start with error:", { message: currentServerProcess.stderr });
return;
}
} catch (err) {
const execaError = err;
if (execaError.stderr) logger.error("Server error output:", { stderr: execaError.stderr });
if (execaError.stdout) logger.debug("Server output:", { stdout: execaError.stdout });
}
};
async function rebundleAndRestart(dotMastraPath, port, bundler, tools) {
if (isRestarting) {
return;
}
isRestarting = true;
try {
if (currentServerProcess) {
logger.debug("Stopping current server...");
currentServerProcess.kill("SIGINT");
}
const env = await bundler.loadEnvVars();
await startServer(join(dotMastraPath, "output"), port, env);
} finally {
isRestarting = false;
}
}
async function dev({ port, dir, root, tools }) {
const rootDir = root || process.cwd();
const mastraDir = join(rootDir, dir || "src/mastra");
const dotMastraPath = join(rootDir, ".mastra");
const defaultToolsPath = join(mastraDir, "tools");
const discoveredTools = [defaultToolsPath, ...tools || []];
const fileService = new FileService();
const entryFile = fileService.getFirstExistingFile([join(mastraDir, "index.ts"), join(mastraDir, "index.js")]);
const bundler = new DevBundler();
await bundler.prepare(dotMastraPath);
const watcher = await bundler.watch(entryFile, dotMastraPath, discoveredTools);
const env = await bundler.loadEnvVars();
await startServer(join(dotMastraPath, "output"), port, env);
watcher.on("event", (event) => {
if (event.code === "BUNDLE_END") {
logger.info("[Mastra Dev] - Bundling finished, restarting server...");
rebundleAndRestart(dotMastraPath, port, bundler);
}
});
process.on("SIGINT", () => {
logger.info("[Mastra Dev] - Stopping server...");
if (currentServerProcess) {
currentServerProcess.kill();
}
watcher.close();
process.exit(0);
});
}
var depsService = new DepsService();
var version = await depsService.getPackageVersion();
var analytics = new PosthogAnalytics({
apiKey: "phc_SBLpZVAB6jmHOct9CABq3PF0Yn5FU3G2FgT4xUr2XrT",
host: "https://us.posthog.com",
version
});
var program = new Command();
var origin = process.env.MASTRA_ANALYTICS_ORIGIN;
program.version(`${version}`, "-v, --version").description(`Mastra CLI ${version}`).action(() => {
try {
analytics.trackCommand({
command: "version",
origin
});
console.log(`Mastra CLI: ${version}`);
} catch {
}
});
program.command("create").description("Create a new Mastra project").option("--default", "Quick start with defaults(src, OpenAI, no examples)").option("-c, --components <components>", "Comma-separated list of components (agents, tools, workflows)").option("-l, --llm <model-provider>", "Default model provider (openai, anthropic, groq, google, or cerebras))").option("-k, --llm-api-key <api-key>", "API key for the model provider").option("-e, --example", "Include example code").option("-t, --timeout [timeout]", "Configurable timeout for package installation, defaults to 60000 ms").option(
"-p, --project-name <string>",
"Project name that will be used in package.json and as the project directory name."
).action(async (args) => {
await analytics.trackCommandExecution({
command: "create",
args,
execution: async () => {
const timeout = args?.timeout ? args?.timeout === true ? 6e4 : parseInt(args?.timeout, 10) : void 0;
if (args.default) {
await create({
components: ["agents", "tools", "workflows"],
llmProvider: "openai",
addExample: false,
timeout
});
return;
}
await create({
components: args.components ? args.components.split(",") : [],
llmProvider: args.llm,
addExample: args.example,
llmApiKey: args["llm-api-key"],
timeout,
projectName: args.projectName
});
},
origin
});
});
program.command("init").description("Initialize Mastra in your project").option("--default", "Quick start with defaults(src, OpenAI, no examples)").option("-d, --dir <directory>", "Directory for Mastra files to (defaults to src/)").option("-c, --components <components>", "Comma-separated list of components (agents, tools, workflows)").option("-l, --llm <model-provider>", "Default model provider (openai, anthropic, or groq))").option("-k, --llm-api-key <api-key>", "API key for the model provider").option("-e, --example", "Include example code").action(async (args) => {
await analytics.trackCommandExecution({
command: "init",
args,
execution: async () => {
await checkPkgJson();
await checkAndInstallCoreDeps();
if (!Object.keys(args).length) {
const result = await interactivePrompt();
await init({
...result,
llmApiKey: result?.llmApiKey
});
return;
}
if (args?.default) {
await init({
directory: "src/",
components: ["agents", "tools", "workflows"],
llmProvider: "openai",
addExample: false
});
return;
}
const componentsArr = args.components ? args.components.split(",") : [];
await init({
directory: args.dir,
components: componentsArr,
llmProvider: args.llm,
addExample: args.example,
llmApiKey: args["llm-api-key"]
});
return;
},
origin
});
});
program.command("dev").description("Start mastra server").option("-d, --dir <dir>", "Path to your mastra folder").option("-r, --root <root>", "Path to your root folder").option("-t, --tools <toolsDirs>", "Comma-separated list of paths to tool files to include").option("-p, --port <port>", "Port number for the development server (defaults to 4111)").action((args) => {
analytics.trackCommand({
command: "dev",
origin
});
dev({
port: args?.port ? parseInt(args.port) : 4111,
dir: args?.dir,
root: args?.root,
tools: args?.tools ? args.tools.split(",") : []
}).catch((err) => {
logger.error(err.message);
});
});
program.command("build").description("Build your Mastra project").option("-d, --dir <path>", "Path to directory").action(async (args) => {
await analytics.trackCommandExecution({
command: "mastra build",
args,
execution: async () => {
await build({ dir: args.dir });
},
origin
});
});
program.command("deploy").description("Deploy your Mastra project").option("-d, --dir <path>", "Path to directory").action(async (args) => {
config({ path: [".env", ".env.production"] });
await analytics.trackCommandExecution({
command: "mastra deploy",
args,
execution: async () => {
await deploy({ dir: args.dir });
},
origin
});
});
program.parse(process.argv);