@mastra/deployer 0.0.0-storage-20250225005900

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.turbo/turbo-build.log +30 -0
  2. package/CHANGELOG.md +855 -0
  3. package/LICENSE +44 -0
  4. package/README.md +159 -0
  5. package/dist/_tsup-dts-rollup.d.ts +360 -0
  6. package/dist/build/analyze.d.ts +1 -0
  7. package/dist/build/analyze.js +2 -0
  8. package/dist/build/bundler.d.ts +2 -0
  9. package/dist/build/bundler.js +2 -0
  10. package/dist/build/index.d.ts +10 -0
  11. package/dist/build/index.js +5 -0
  12. package/dist/bundler/index.d.ts +1 -0
  13. package/dist/bundler/index.js +5 -0
  14. package/dist/chunk-AXS5WSIK.js +290 -0
  15. package/dist/chunk-BTUQ6UBB.js +254 -0
  16. package/dist/chunk-IHPQAB7O.js +113 -0
  17. package/dist/chunk-KXS4XYEJ.js +251 -0
  18. package/dist/chunk-SESHBX76.js +267 -0
  19. package/dist/chunk-YNXJO2XU.js +69 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +131 -0
  22. package/dist/server/index.d.ts +2 -0
  23. package/dist/server/index.js +4907 -0
  24. package/dist/templates/instrumentation-template.js +86 -0
  25. package/eslint.config.js +6 -0
  26. package/global.d.ts +0 -0
  27. package/package.json +84 -0
  28. package/public/templates/instrumentation-template.js +86 -0
  29. package/src/build/analyze.ts +271 -0
  30. package/src/build/babel/fix-libsql.ts +41 -0
  31. package/src/build/babel/get-deployer.ts +54 -0
  32. package/src/build/babel/get-telemetry-config.ts +62 -0
  33. package/src/build/babel/remove-deployer.ts +43 -0
  34. package/src/build/bundle.ts +139 -0
  35. package/src/build/bundler.ts +126 -0
  36. package/src/build/deployer.ts +67 -0
  37. package/src/build/deps.ts +149 -0
  38. package/src/build/env.ts +76 -0
  39. package/src/build/fs.ts +66 -0
  40. package/src/build/index.ts +7 -0
  41. package/src/build/isNodeBuiltin.ts +7 -0
  42. package/src/build/plugins/fix-libsql.ts +69 -0
  43. package/src/build/plugins/hono-alias.ts +17 -0
  44. package/src/build/plugins/pino.ts +93 -0
  45. package/src/build/plugins/remove-deployer.ts +37 -0
  46. package/src/build/plugins/telemetry-fix.ts +54 -0
  47. package/src/build/telemetry.ts +76 -0
  48. package/src/build/utils.ts +12 -0
  49. package/src/build/watcher.ts +43 -0
  50. package/src/bundler/index.ts +145 -0
  51. package/src/deploy/base.ts +30 -0
  52. package/src/deploy/index.ts +2 -0
  53. package/src/deploy/log.ts +61 -0
  54. package/src/index.ts +3 -0
  55. package/src/server/handlers/agents.ts +210 -0
  56. package/src/server/handlers/client.ts +36 -0
  57. package/src/server/handlers/error.ts +29 -0
  58. package/src/server/handlers/logs.ts +53 -0
  59. package/src/server/handlers/memory.ts +196 -0
  60. package/src/server/handlers/prompt.ts +128 -0
  61. package/src/server/handlers/root.ts +6 -0
  62. package/src/server/handlers/telemetry.ts +48 -0
  63. package/src/server/handlers/tools.ts +107 -0
  64. package/src/server/handlers/utils.ts +15 -0
  65. package/src/server/handlers/vector.ts +149 -0
  66. package/src/server/handlers/workflows.ts +104 -0
  67. package/src/server/index.ts +1355 -0
  68. package/src/server/openapi.json +434 -0
  69. package/src/server/openapi.script.js +22 -0
  70. package/src/server/types.ts +4 -0
  71. package/src/server/welcome.ts +105 -0
  72. package/tsconfig.json +5 -0
  73. package/vitest.config.ts +8 -0
@@ -0,0 +1,69 @@
1
+ import * as babel from '@babel/core';
2
+ import { currentTarget } from '@neon-rs/load';
3
+ import { familySync, GLIBC } from 'detect-libc';
4
+ import { dirname, join } from 'path';
5
+ import type { Plugin } from 'rollup';
6
+
7
+ import fsExtra from 'fs-extra/esm';
8
+
9
+ import { rewriteLibsqlImport } from '../babel/fix-libsql';
10
+
11
+ export function libSqlFix(): Plugin {
12
+ return {
13
+ name: 'libSqlFix',
14
+ transform(code, id) {
15
+ if (!id.includes('\\libsql\\index.js') && !id.includes('/libsql/index.js')) {
16
+ return null;
17
+ }
18
+
19
+ return new Promise((resolve, reject) => {
20
+ babel.transform(
21
+ code,
22
+ {
23
+ babelrc: false,
24
+ configFile: false,
25
+ filename: id,
26
+ plugins: [rewriteLibsqlImport],
27
+ },
28
+ (err, result) => {
29
+ if (err) {
30
+ return reject(err);
31
+ }
32
+
33
+ resolve({
34
+ code: result!.code!,
35
+ map: result!.map!,
36
+ });
37
+ },
38
+ );
39
+ });
40
+ },
41
+ async generateBundle({ file, dir }) {
42
+ if (!file && !dir) {
43
+ throw new Error('No output options were given.');
44
+ }
45
+
46
+ const outputDirectory = dir || dirname(file!);
47
+
48
+ let target = currentTarget();
49
+ // Workaround for Bun, which reports a musl target, but really wants glibc...
50
+ if (familySync() == GLIBC) {
51
+ switch (target) {
52
+ case 'linux-x64-musl':
53
+ target = 'linux-x64-gnu';
54
+ break;
55
+ case 'linux-arm64-musl':
56
+ target = 'linux-arm64-gnu';
57
+ break;
58
+ }
59
+ }
60
+
61
+ const fileToCopy = await this.resolve(`@libsql/${target}/index.node`);
62
+ if (fileToCopy) {
63
+ await fsExtra.copy(fileToCopy.id, join(outputDirectory, 'libsql.node'));
64
+ } else {
65
+ // throw new Error(`libsql binding not found for @libsql/${target}/index.node`);
66
+ }
67
+ },
68
+ } satisfies Plugin;
69
+ }
@@ -0,0 +1,17 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import type { Plugin } from 'rollup';
3
+
4
+ // hono is imported from deployer, so we need to resolve from here instead of the project root
5
+ export function aliasHono(): Plugin {
6
+ return {
7
+ name: 'hono-alias',
8
+ resolveId(id: string) {
9
+ if (!id.startsWith('@hono/') && !id.startsWith('hono/') && id !== 'hono' && id !== 'hono-openapi') {
10
+ return;
11
+ }
12
+
13
+ const path = import.meta.resolve(id);
14
+ return fileURLToPath(path);
15
+ },
16
+ } satisfies Plugin;
17
+ }
@@ -0,0 +1,93 @@
1
+ import path from 'path';
2
+ import type { OutputChunk, Plugin } from 'rollup';
3
+
4
+ export function pino() {
5
+ let emittedChunks = new Map();
6
+
7
+ const workerFiles = [
8
+ {
9
+ id: 'thread-stream-worker',
10
+ file: 'pino-thread-stream-worker',
11
+ },
12
+ {
13
+ id: 'pino-worker',
14
+ file: 'pino-worker',
15
+ },
16
+ {
17
+ id: 'pino/file',
18
+ file: 'pino-file',
19
+ },
20
+ {
21
+ id: 'pino-pretty',
22
+ file: 'pino-pretty',
23
+ },
24
+ ];
25
+
26
+ const fileReferences = new Map();
27
+ return {
28
+ name: 'rollup-plugin-pino',
29
+
30
+ async resolveId(id, importee) {
31
+ if (id === 'pino') {
32
+ // resolve pino first
33
+ const resolvedPino = await this.resolve(id, importee);
34
+
35
+ if (resolvedPino) {
36
+ await Promise.all(
37
+ workerFiles.map(async file => {
38
+ const resolvedEntry = await this.resolve(file.id, resolvedPino.id);
39
+
40
+ if (!resolvedEntry) {
41
+ return null;
42
+ }
43
+
44
+ const reference = this.emitFile({
45
+ type: 'chunk',
46
+ id: resolvedEntry.id,
47
+ name: `${file.file}`,
48
+ });
49
+
50
+ fileReferences.set(file.id, reference);
51
+ }),
52
+ );
53
+ }
54
+ }
55
+ },
56
+ renderChunk(code, chunk) {
57
+ if (chunk.type === 'chunk' && (chunk as OutputChunk).isEntry && fileReferences.size && chunk.name === 'index') {
58
+ const importRegex = /^(?:import(?:["'\s]*[\w*${}\n\r\t, ]+from\s*)?["'\s].+[;"'\s]*)$/gm;
59
+
60
+ const codeToInject = `globalThis.__bundlerPathsOverrides = {
61
+ ${Array.from(fileReferences.entries())
62
+ .map(([key, file]) => {
63
+ return '"' + key + '": import.meta.ROLLUP_FILE_URL_' + file;
64
+ })
65
+ .join(',\n')}
66
+ };`;
67
+
68
+ // Find all import matches
69
+ const matches = Array.from(code.matchAll(importRegex));
70
+
71
+ if (matches.length > 0) {
72
+ // Get the last import's position
73
+ const lastImport = matches[matches.length - 1]!;
74
+ const lastImportEnd = lastImport.index + lastImport[0].length;
75
+
76
+ // Insert the code after the last import with a newline
77
+ const newCode = code.slice(0, lastImportEnd) + '\n\n' + codeToInject + '\n\n' + code.slice(lastImportEnd);
78
+
79
+ return {
80
+ code: newCode,
81
+ map: null,
82
+ };
83
+ }
84
+
85
+ // If no imports found, inject at the start of the file
86
+ return {
87
+ code: `${codeToInject}\n\n${code}`,
88
+ map: null,
89
+ };
90
+ }
91
+ },
92
+ } satisfies Plugin;
93
+ }
@@ -0,0 +1,37 @@
1
+ import * as babel from '@babel/core';
2
+ import type { Plugin } from 'rollup';
3
+
4
+ import { removeDeployer as removeDeployerBabelPlugin } from '../babel/remove-deployer';
5
+
6
+ export function removeDeployer(mastraEntry: string): Plugin {
7
+ return {
8
+ name: 'remove-deployer',
9
+ transform(code, id) {
10
+ if (id !== mastraEntry) {
11
+ return;
12
+ }
13
+
14
+ return new Promise((resolve, reject) => {
15
+ babel.transform(
16
+ code,
17
+ {
18
+ babelrc: false,
19
+ configFile: false,
20
+ filename: id,
21
+ plugins: [removeDeployerBabelPlugin],
22
+ },
23
+ (err, result) => {
24
+ if (err) {
25
+ return reject(err);
26
+ }
27
+
28
+ resolve({
29
+ code: result!.code!,
30
+ map: result!.map!,
31
+ });
32
+ },
33
+ );
34
+ });
35
+ },
36
+ } satisfies Plugin;
37
+ }
@@ -0,0 +1,54 @@
1
+ import { dirname, resolve } from 'path';
2
+ import { platform } from 'process';
3
+ import type { Plugin } from 'rollup';
4
+
5
+ function getTelemetryMachineFile() {
6
+ switch (platform) {
7
+ case 'darwin':
8
+ return 'getMachineId-darwin';
9
+ case 'linux':
10
+ return 'getMachineId-linux';
11
+ case 'freebsd':
12
+ return 'getMachineId-bsd';
13
+ case 'win32':
14
+ return 'getMachineId-win';
15
+ default:
16
+ return 'getMachineId-unsupported';
17
+ }
18
+ }
19
+
20
+ export function telemetryFix(): Plugin {
21
+ return {
22
+ name: 'telemetry-fix',
23
+ transform(code, id) {
24
+ if (id.includes('require-in-the-middle')) {
25
+ return code
26
+ .replace(
27
+ `const path = require('path')`,
28
+ `const path = require('path');
29
+ const { createRequire } = require('module');
30
+ const realRequire = createRequire(import.meta.url)`,
31
+ )
32
+ .replaceAll(`require.resolve`, `realRequire.resolve`);
33
+ }
34
+ },
35
+ resolveId(id: string, importer?: string) {
36
+ if (id === './machine-id/getMachineId' && importer) {
37
+ // rewrite dynamic require to correct file
38
+ return { id: resolve(dirname(importer), `./machine-id/${getTelemetryMachineFile()}.js`) };
39
+ }
40
+
41
+ if (id === 'formdata-node') {
42
+ return { id: 'formdata-node', external: false };
43
+ }
44
+ },
45
+ load(id: string) {
46
+ if (id.startsWith('formdata-node')) {
47
+ // Return an empty module
48
+ return 'export default {};';
49
+ }
50
+
51
+ return null;
52
+ },
53
+ } satisfies Plugin;
54
+ }
@@ -0,0 +1,76 @@
1
+ import * as babel from '@babel/core';
2
+ import type { MastraDeployer } from '@mastra/core';
3
+ import { rollup } from 'rollup';
4
+ import esbuild from 'rollup-plugin-esbuild';
5
+
6
+ import { removeAllExceptTelemetryConfig } from './babel/get-telemetry-config';
7
+
8
+ export async function writeTelemetryConfig(
9
+ entryFile: string,
10
+ outputDir: string,
11
+ ): Promise<{
12
+ hasCustomConfig: boolean;
13
+ }> {
14
+ const result = {
15
+ hasCustomConfig: false,
16
+ };
17
+
18
+ const bundle = await rollup({
19
+ input: {
20
+ 'telemetry-config': entryFile,
21
+ },
22
+ treeshake: true,
23
+ plugins: [
24
+ // transpile typescript to something we understand
25
+ esbuild({
26
+ target: 'node20',
27
+ platform: 'node',
28
+ minify: false,
29
+ }),
30
+ {
31
+ name: 'get-telemetry-config',
32
+ transform(code, id) {
33
+ if (!this.getModuleInfo(id)?.isEntry) {
34
+ return;
35
+ }
36
+
37
+ return new Promise((resolve, reject) => {
38
+ babel.transform(
39
+ code,
40
+ {
41
+ babelrc: false,
42
+ configFile: false,
43
+ filename: id,
44
+ plugins: [removeAllExceptTelemetryConfig(result)],
45
+ },
46
+ (err, result) => {
47
+ if (err) {
48
+ return reject(err);
49
+ }
50
+
51
+ resolve({
52
+ code: result!.code!,
53
+ map: result!.map!,
54
+ });
55
+ },
56
+ );
57
+ });
58
+ },
59
+ },
60
+ // let esbuild remove all unused imports
61
+ esbuild({
62
+ target: 'node20',
63
+ platform: 'node',
64
+ minify: false,
65
+ }),
66
+ ],
67
+ });
68
+
69
+ await bundle.write({
70
+ dir: outputDir,
71
+ format: 'es',
72
+ entryFileNames: '[name].mjs',
73
+ });
74
+
75
+ return result;
76
+ }
@@ -0,0 +1,12 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ export function upsertMastraDir({ dir = process.cwd() }: { dir?: string }) {
6
+ const dirPath = join(dir, '.mastra');
7
+
8
+ if (!existsSync(dirPath)) {
9
+ mkdirSync(dirPath, { recursive: true });
10
+ execSync(`echo ".mastra" >> .gitignore`);
11
+ }
12
+ }
@@ -0,0 +1,43 @@
1
+ import type { InputOptions, OutputOptions } from 'rollup';
2
+ import { watch } from 'rollup';
3
+
4
+ import { getInputOptions as getBundlerInputOptions } from './bundler';
5
+ import { aliasHono } from './plugins/hono-alias';
6
+
7
+ export async function getInputOptions(entryFile: string, platform: 'node' | 'browser') {
8
+ const inputOptions = await getBundlerInputOptions(
9
+ entryFile,
10
+ {
11
+ dependencies: new Map(),
12
+ externalDependencies: new Set(),
13
+ invalidChunks: new Set(),
14
+ },
15
+ platform,
16
+ );
17
+
18
+ if (Array.isArray(inputOptions.plugins)) {
19
+ // filter out node-resolve plugin so all node_modules are external
20
+ inputOptions.plugins = inputOptions.plugins.filter(
21
+ // @ts-ignore
22
+ plugin => !plugin || !plugin?.name || plugin.name !== 'node-resolve',
23
+ );
24
+
25
+ inputOptions.plugins.push(aliasHono());
26
+ }
27
+
28
+ return inputOptions;
29
+ }
30
+
31
+ export async function createWatcher(inputOptions: InputOptions, outputOptions: OutputOptions) {
32
+ const watcher = await watch({
33
+ ...inputOptions,
34
+ output: {
35
+ ...outputOptions,
36
+ format: 'esm',
37
+ entryFileNames: '[name].mjs',
38
+ chunkFileNames: '[name].mjs',
39
+ },
40
+ });
41
+
42
+ return watcher;
43
+ }
@@ -0,0 +1,145 @@
1
+ import { MastraBundler } from '@mastra/core/bundler';
2
+ import virtual from '@rollup/plugin-virtual';
3
+ import { copy, ensureDir } from 'fs-extra';
4
+ import { existsSync } from 'node:fs';
5
+ import { writeFile } from 'node:fs/promises';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import type { InputOptions, OutputOptions } from 'rollup';
9
+
10
+ import fsExtra from 'fs-extra/esm';
11
+
12
+ import { analyzeBundle } from '../build/analyze';
13
+ import { createBundler as createBundlerUtil, getInputOptions } from '../build/bundler';
14
+ import { Deps } from '../build/deps';
15
+ import { writeTelemetryConfig } from '../build/telemetry';
16
+
17
+ export abstract class Bundler extends MastraBundler {
18
+ protected analyzeOutputDir = '.build';
19
+ protected outputDir = 'output';
20
+
21
+ constructor(name: string, component: 'BUNDLER' | 'DEPLOYER' = 'BUNDLER') {
22
+ super({ name, component });
23
+ }
24
+
25
+ async prepare(outputDirectory: string): Promise<void> {
26
+ // Clean up the output directory first
27
+ await fsExtra.emptyDir(outputDirectory);
28
+
29
+ await ensureDir(join(outputDirectory, this.analyzeOutputDir));
30
+ await ensureDir(join(outputDirectory, this.outputDir));
31
+ }
32
+
33
+ async writeInstrumentationFile(outputDirectory: string) {
34
+ const instrumentationFile = join(outputDirectory, 'instrumentation.mjs');
35
+ const __dirname = dirname(fileURLToPath(import.meta.url));
36
+
37
+ await copy(join(__dirname, 'templates', 'instrumentation-template.js'), instrumentationFile);
38
+ }
39
+
40
+ async writePackageJson(outputDirectory: string, dependencies: Map<string, string>) {
41
+ this.logger.debug(`Writing project's package.json`);
42
+ await ensureDir(outputDirectory);
43
+ const pkgPath = join(outputDirectory, 'package.json');
44
+
45
+ const dependenciesMap = new Map();
46
+ for (const [key, value] of dependencies.entries()) {
47
+ if (key.startsWith('@')) {
48
+ const pkgChunks = key.split('/');
49
+ dependenciesMap.set(`${pkgChunks[0]}/${pkgChunks[1]}`, value);
50
+ continue;
51
+ }
52
+ dependenciesMap.set(key, value);
53
+ }
54
+
55
+ dependenciesMap.set('@opentelemetry/instrumentation', 'latest');
56
+
57
+ await writeFile(
58
+ pkgPath,
59
+ JSON.stringify(
60
+ {
61
+ name: 'server',
62
+ version: '1.0.0',
63
+ description: '',
64
+ type: 'module',
65
+ main: 'index.mjs',
66
+ scripts: {
67
+ start: 'node ./index.mjs',
68
+ },
69
+ author: 'Mastra',
70
+ license: 'ISC',
71
+ dependencies: Object.fromEntries(dependenciesMap.entries()),
72
+ },
73
+ null,
74
+ 2,
75
+ ),
76
+ );
77
+ }
78
+
79
+ protected createBundler(inputOptions: InputOptions, outputOptions: Partial<OutputOptions> & { dir: string }) {
80
+ return createBundlerUtil(inputOptions, outputOptions);
81
+ }
82
+
83
+ protected async analyze(entry: string, mastraFile: string, outputDirectory: string) {
84
+ return await analyzeBundle(entry, mastraFile, join(outputDirectory, this.analyzeOutputDir), 'node', this.logger);
85
+ }
86
+
87
+ protected async installDependencies(outputDirectory: string, rootDir = process.cwd()) {
88
+ const deps = new Deps(rootDir);
89
+ deps.__setLogger(this.logger);
90
+
91
+ await deps.install({ dir: join(outputDirectory, this.outputDir) });
92
+ }
93
+
94
+ protected async _bundle(
95
+ serverFile: string,
96
+ mastraEntryFile: string,
97
+ outputDirectory: string,
98
+ bundleLocation: string = join(outputDirectory, this.outputDir),
99
+ ): Promise<void> {
100
+ this.logger.info('Start bundling Mastra');
101
+ const isVirtual = serverFile.includes('\n') || existsSync(serverFile);
102
+
103
+ const analyzedBundleInfo = await analyzeBundle(
104
+ serverFile,
105
+ mastraEntryFile,
106
+ join(outputDirectory, this.analyzeOutputDir),
107
+ 'node',
108
+ this.logger,
109
+ );
110
+
111
+ await writeTelemetryConfig(mastraEntryFile, join(outputDirectory, this.outputDir));
112
+
113
+ const dependenciesToInstall = Array.from(analyzedBundleInfo.externalDependencies).reduce((acc, dep) => {
114
+ acc.set(dep, 'latest');
115
+ return acc;
116
+ }, new Map<string, string>());
117
+
118
+ await this.writePackageJson(join(outputDirectory, this.outputDir), dependenciesToInstall);
119
+ await this.writeInstrumentationFile(join(outputDirectory, this.outputDir));
120
+
121
+ this.logger.info('Bundling Mastra application');
122
+ const inputOptions: InputOptions = await getInputOptions(mastraEntryFile, analyzedBundleInfo, 'node');
123
+
124
+ if (isVirtual) {
125
+ inputOptions.input = { index: '#entry' };
126
+
127
+ if (Array.isArray(inputOptions.plugins)) {
128
+ inputOptions.plugins.unshift(virtual({ '#entry': serverFile }));
129
+ } else {
130
+ inputOptions.plugins = [virtual({ '#entry': serverFile })];
131
+ }
132
+ } else {
133
+ inputOptions.input = { index: serverFile };
134
+ }
135
+
136
+ const bundler = await this.createBundler(inputOptions, { dir: bundleLocation });
137
+
138
+ await bundler.write();
139
+ this.logger.info('Bundling Mastra done');
140
+
141
+ this.logger.info('Installing dependencies');
142
+ await this.installDependencies(outputDirectory);
143
+ this.logger.info('Done installing dependencies');
144
+ }
145
+ }
@@ -0,0 +1,30 @@
1
+ import type { IDeployer } from '@mastra/core/deployer';
2
+
3
+ import { Deps } from '../build/deps.js';
4
+ import { FileService } from '../build/fs';
5
+ import { Bundler } from '../bundler';
6
+
7
+ export abstract class Deployer extends Bundler implements IDeployer {
8
+ deps: Deps = new Deps();
9
+
10
+ constructor(args: { name: string }) {
11
+ super(args.name, 'DEPLOYER');
12
+
13
+ this.deps.__setLogger(this.logger);
14
+ }
15
+
16
+ getEnvFiles(): Promise<string[]> {
17
+ const possibleFiles = ['.env.production', '.env'];
18
+
19
+ try {
20
+ const fileService = new FileService();
21
+ const envFile = fileService.getFirstExistingFile(possibleFiles);
22
+
23
+ return Promise.resolve([envFile]);
24
+ } catch {}
25
+
26
+ return Promise.resolve([]);
27
+ }
28
+
29
+ abstract deploy(outputDirectory: string): Promise<void>;
30
+ }
@@ -0,0 +1,2 @@
1
+ export * from './base.js';
2
+ export * from './log.js';
@@ -0,0 +1,61 @@
1
+ import type { Logger } from '@mastra/core/logger';
2
+ import { spawn } from 'child_process';
3
+ import { Transform } from 'stream';
4
+
5
+ export const createPinoStream = (logger: Logger) => {
6
+ return new Transform({
7
+ transform(chunk, _encoding, callback) {
8
+ // Convert Buffer/string to string and trim whitespace
9
+ const line = chunk.toString().trim();
10
+
11
+ if (line) {
12
+ console.log(line);
13
+ // Log each line through Pino
14
+ logger.info(line);
15
+ }
16
+
17
+ // Pass through the original data
18
+ callback(null, chunk);
19
+ },
20
+ });
21
+ };
22
+
23
+ export function createChildProcessLogger({ logger, root }: { logger: Logger; root: string }) {
24
+ const pinoStream = createPinoStream(logger);
25
+ return async ({ cmd, args, env }: { cmd: string; args: string[]; env: Record<string, string> }) => {
26
+ try {
27
+ const subprocess = spawn(cmd, args, {
28
+ cwd: root,
29
+ shell: true,
30
+ env,
31
+ });
32
+
33
+ // Pipe stdout and stderr through the Pino stream
34
+ subprocess.stdout?.pipe(pinoStream);
35
+ subprocess.stderr?.pipe(pinoStream);
36
+
37
+ // Wait for the process to complete
38
+ return new Promise((resolve, reject) => {
39
+ subprocess.on('close', code => {
40
+ pinoStream.end();
41
+ if (code === 0) {
42
+ resolve({ success: true });
43
+ } else {
44
+ reject(new Error(`Process exited with code ${code}`));
45
+ }
46
+ });
47
+
48
+ subprocess.on('error', error => {
49
+ pinoStream.end();
50
+ logger.error('Process failed', { error });
51
+ reject(error);
52
+ });
53
+ });
54
+ } catch (error) {
55
+ console.log(error);
56
+ logger.error('Process failed', { error });
57
+ pinoStream.end();
58
+ return { success: false, error };
59
+ }
60
+ };
61
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './deploy';
2
+ export { Deps, FileService } from './build';
3
+ export { getDeployer } from './build/deployer';