motia 0.8.4-beta.142-001319 → 0.8.5-beta.142

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.
package/dist/cjs/cli.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  require("./cloud");
6
6
  const config_utils_1 = require("./cloud/config-utils");
7
+ const analytics_1 = require("./utils/analytics");
7
8
  const version_1 = require("./version");
8
9
  const defaultPort = 3000;
9
10
  const defaultHost = '0.0.0.0';
@@ -48,19 +49,19 @@ commander_1.program
48
49
  commander_1.program
49
50
  .command('generate-types')
50
51
  .description('Generate types.d.ts file for your project')
51
- .action(async () => {
52
+ .action((0, analytics_1.wrapAction)(async () => {
52
53
  const { generateTypes } = require('./generate-types');
53
54
  await generateTypes(process.cwd());
54
55
  process.exit(0);
55
- });
56
+ }));
56
57
  commander_1.program
57
58
  .command('install')
58
59
  .description('Sets up Python virtual environment and install dependencies')
59
60
  .option('-v, --verbose', 'Enable verbose logging')
60
- .action(async (options) => {
61
+ .action((0, analytics_1.wrapAction)(async (options) => {
61
62
  const { install } = require('./install');
62
63
  await install({ isVerbose: options.verbose });
63
- });
64
+ }));
64
65
  commander_1.program
65
66
  .command('dev')
66
67
  .description('Start the development server')
@@ -70,7 +71,7 @@ commander_1.program
70
71
  .option('-d, --debug', 'Enable debug logging')
71
72
  .option('-m, --mermaid', 'Enable mermaid diagram generation')
72
73
  .option('--motia-dir <path>', 'Path where .motia folder will be created')
73
- .action(async (arg) => {
74
+ .action((0, analytics_1.wrapAction)(async (arg) => {
74
75
  if (arg.debug) {
75
76
  console.log('🔍 Debug logging enabled');
76
77
  process.env.LOG_LEVEL = 'debug';
@@ -79,7 +80,7 @@ commander_1.program
79
80
  const host = arg.host ? arg.host : defaultHost;
80
81
  const { dev } = require('./dev');
81
82
  await dev(port, host, arg.disableVerbose, arg.mermaid, arg.motiaDir);
82
- });
83
+ }));
83
84
  commander_1.program
84
85
  .command('start')
85
86
  .description('Start a server to run your Motia project')
@@ -88,7 +89,7 @@ commander_1.program
88
89
  .option('-v, --disable-verbose', 'Disable verbose logging')
89
90
  .option('-d, --debug', 'Enable debug logging')
90
91
  .option('--motia-dir <path>', 'Path where .motia folder will be created')
91
- .action(async (arg) => {
92
+ .action((0, analytics_1.wrapAction)(async (arg) => {
92
93
  if (arg.debug) {
93
94
  console.log('🔍 Debug logging enabled');
94
95
  process.env.LOG_LEVEL = 'debug';
@@ -97,89 +98,85 @@ commander_1.program
97
98
  const host = arg.host ? arg.host : defaultHost;
98
99
  const { start } = require('./start');
99
100
  await start(port, host, arg.disableVerbose, arg.motiaDir);
100
- });
101
+ }));
101
102
  commander_1.program
102
103
  .command('emit')
103
104
  .description('Emit an event to the Motia server')
104
105
  .requiredOption('--topic <topic>', 'Event topic/type to emit')
105
106
  .requiredOption('--message <message>', 'Event payload as JSON string')
106
107
  .option('-p, --port <number>', 'Port number (default: 3000)')
107
- .action(async (options) => {
108
+ .action((0, analytics_1.wrapAction)(async (options) => {
108
109
  const port = options.port || 3000;
109
110
  const url = `http://localhost:${port}/emit`;
110
- try {
111
- const response = await fetch(url, {
112
- method: 'POST',
113
- headers: { 'Content-Type': 'application/json' },
114
- body: JSON.stringify({
115
- topic: options.topic,
116
- data: JSON.parse(options.message),
117
- }),
118
- });
119
- if (!response.ok) {
120
- throw new Error(`HTTP error! status: ${response.status}`);
121
- }
122
- const result = await response.json();
123
- console.log('Event emitted successfully:', result);
124
- }
125
- catch (error) {
126
- console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
127
- process.exit(1);
111
+ const response = await fetch(url, {
112
+ method: 'POST',
113
+ headers: { 'Content-Type': 'application/json' },
114
+ body: JSON.stringify({
115
+ topic: options.topic,
116
+ data: JSON.parse(options.message),
117
+ }),
118
+ });
119
+ if (!response.ok) {
120
+ throw new Error(`HTTP error! status: ${response.status}`);
128
121
  }
129
- });
122
+ const result = await response.json();
123
+ console.log('Event emitted successfully:', result);
124
+ }));
130
125
  const generate = commander_1.program.command('generate').description('Generate motia resources');
131
126
  generate
132
127
  .command('step')
133
128
  .description('Create a new step with interactive prompts')
134
129
  .option('-d, --dir <step file path>', 'The path relative to the steps directory, used to create the step file')
135
- .action(async (arg) => {
130
+ .action((0, analytics_1.wrapAction)(async (arg) => {
136
131
  const { createStep } = require('./create-step');
137
132
  await createStep({
138
133
  stepFilePath: arg.dir,
139
134
  });
140
- });
135
+ }));
141
136
  generate
142
137
  .command('openapi')
143
138
  .description('Generate OpenAPI spec for your project')
144
139
  .option('-t, --title <title>', 'Title for the OpenAPI document. Defaults to project name')
145
140
  .option('-v, --version <version>', 'Version for the OpenAPI document. Defaults to 1.0.0', '1.0.0')
146
141
  .option('-o, --output <output>', 'Output file for the OpenAPI document. Defaults to openapi.json', 'openapi.json')
147
- .action(async (options) => {
142
+ .action((0, analytics_1.wrapAction)(async (options) => {
148
143
  const { generateLockedData } = require('./generate-locked-data');
149
144
  const { generateOpenApi } = require('./openapi/generate');
150
145
  const lockedData = await generateLockedData({ projectDir: process.cwd() });
151
146
  const apiSteps = lockedData.apiSteps();
152
147
  generateOpenApi(process.cwd(), apiSteps, options.title, options.version, options.output);
153
148
  process.exit(0);
154
- });
149
+ }));
155
150
  const docker = commander_1.program.command('docker').description('Motia docker commands');
156
151
  docker
157
152
  .command('setup')
158
153
  .description('Setup a motia-docker for your project')
159
- .action(async () => {
154
+ .action((0, analytics_1.wrapAction)(async () => {
160
155
  const { setup } = require('./docker/setup');
161
156
  await setup();
162
157
  process.exit(0);
163
- });
158
+ }));
164
159
  docker
165
160
  .command('run')
166
161
  .description('Build and run your project in a docker container')
167
162
  .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)
168
163
  .option('-n, --project-name <project name>', 'The name for your project')
169
164
  .option('-s, --skip-build', 'Skip docker build')
170
- .action(async (arg) => {
165
+ .action((0, analytics_1.wrapAction)(async (arg) => {
171
166
  const { run } = require('./docker/run');
172
167
  await run(arg.port, arg.projectName, arg.skipBuild);
173
168
  process.exit(0);
174
- });
169
+ }));
175
170
  docker
176
171
  .command('build')
177
172
  .description('Build your project in a docker container')
178
173
  .option('-n, --project-name <project name>', 'The name for your project')
179
- .action(async (arg) => {
174
+ .action((0, analytics_1.wrapAction)(async (arg) => {
180
175
  const { build } = require('./docker/build');
181
176
  await build(arg.projectName);
182
177
  process.exit(0);
183
- });
178
+ }));
184
179
  commander_1.program.version(version_1.version, '-V, --version', 'Output the current version');
185
- commander_1.program.parse(process.argv);
180
+ commander_1.program.parseAsync(process.argv).catch(() => {
181
+ process.exit(1);
182
+ });
@@ -2,7 +2,23 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CliContext = void 0;
4
4
  exports.handler = handler;
5
+ const analytics_node_1 = require("@amplitude/analytics-node");
6
+ const analytics_1 = require("../utils/analytics");
5
7
  const cli_output_manager_1 = require("./cli-output-manager");
8
+ const getCommandName = () => {
9
+ const args = process.argv.slice(2);
10
+ const commandParts = [];
11
+ for (let i = 0; i < args.length && i < 3; i++) {
12
+ const arg = args[i];
13
+ if (!arg.startsWith('-') && !arg.startsWith('--')) {
14
+ commandParts.push(arg);
15
+ }
16
+ else {
17
+ break;
18
+ }
19
+ }
20
+ return commandParts.join(' ') || 'unknown';
21
+ };
6
22
  class CliContext {
7
23
  constructor() {
8
24
  this.output = new cli_output_manager_1.CLIOutputManager();
@@ -27,10 +43,15 @@ exports.CliContext = CliContext;
27
43
  function handler(handler) {
28
44
  return async (args) => {
29
45
  const context = new CliContext();
46
+ const commandName = getCommandName();
30
47
  try {
31
48
  await handler(args, context);
32
49
  }
33
50
  catch (error) {
51
+ (0, analytics_1.logCliError)(commandName, error);
52
+ await (0, analytics_node_1.flush)().promise.catch(() => {
53
+ // Silently fail
54
+ });
34
55
  if (error instanceof Error) {
35
56
  context.log('error', (message) => message.tag('failed').append(error.message));
36
57
  context.exit(1);
@@ -1,3 +1,4 @@
1
1
  import { type CronManager, type LockedData, type MotiaEventManager, type MotiaServer } from '@motiadev/core';
2
- import { Watcher } from './watcher';
3
- export declare const createDevWatchers: (lockedData: LockedData, server: MotiaServer, eventHandler: MotiaEventManager, cronManager: CronManager) => Watcher;
2
+ export declare const createDevWatchers: (lockedData: LockedData, server: MotiaServer, eventHandler: MotiaEventManager, cronManager: CronManager) => {
3
+ stop: () => Promise<void>;
4
+ };
@@ -5,11 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createDevWatchers = void 0;
7
7
  const core_1 = require("@motiadev/core");
8
+ const fs_1 = require("fs");
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const watcher_1 = require("./watcher");
10
- const createDevWatchers = (lockedData, server, eventHandler, cronManager) => {
11
- const stepDir = path_1.default.join(process.cwd(), 'steps');
12
- const watcher = new watcher_1.Watcher(stepDir, lockedData);
11
+ const setupWatcherHandlers = (watcher, lockedData, server, eventHandler, cronManager) => {
13
12
  watcher.onStreamChange((oldStream, stream) => {
14
13
  (0, core_1.trackEvent)('stream_updated', {
15
14
  streamName: stream.config.name,
@@ -80,6 +79,23 @@ const createDevWatchers = (lockedData, server, eventHandler, cronManager) => {
80
79
  cronManager.removeCronJob(step);
81
80
  lockedData.deleteStep(step);
82
81
  });
83
- return watcher;
82
+ };
83
+ const createDevWatchers = (lockedData, server, eventHandler, cronManager) => {
84
+ const baseDir = process.cwd();
85
+ const dirs = [path_1.default.join(baseDir, 'steps'), path_1.default.join(baseDir, 'src')];
86
+ const watchers = [];
87
+ dirs.forEach((dir) => {
88
+ if ((0, fs_1.existsSync)(dir)) {
89
+ const watcher = new watcher_1.Watcher(dir, lockedData);
90
+ setupWatcherHandlers(watcher, lockedData, server, eventHandler, cronManager);
91
+ watcher.init();
92
+ watchers.push(watcher);
93
+ }
94
+ });
95
+ return {
96
+ stop: async () => {
97
+ await Promise.all(watchers.map((watcher) => watcher.stop()));
98
+ },
99
+ };
84
100
  };
85
101
  exports.createDevWatchers = createDevWatchers;
package/dist/cjs/dev.js CHANGED
@@ -57,7 +57,6 @@ const dev = async (port, hostname, disableVerbose, enableMermaid, motiaFileStora
57
57
  mermaidGenerator.initialize(lockedData);
58
58
  (0, core_1.trackEvent)('mermaid_generator_initialized');
59
59
  }
60
- watcher.init();
61
60
  (0, endpoints_1.deployEndpoints)(motiaServer, lockedData);
62
61
  motiaServer.app.get('/__motia', (_, res) => {
63
62
  const meta = {
@@ -8,25 +8,40 @@ const core_1 = require("@motiadev/core");
8
8
  const printer_1 = require("@motiadev/core/dist/src/printer");
9
9
  const colors_1 = __importDefault(require("colors"));
10
10
  const crypto_1 = require("crypto");
11
+ const fs_1 = require("fs");
11
12
  const glob_1 = require("glob");
12
13
  const path_1 = __importDefault(require("path"));
13
14
  const activate_python_env_1 = require("./utils/activate-python-env");
14
15
  const compilation_error_1 = require("./utils/errors/compilation.error");
15
16
  const version = `${(0, crypto_1.randomUUID)()}:${Math.floor(Date.now() / 1000)}`;
17
+ const getStepFilesFromDir = (dir) => {
18
+ if (!(0, fs_1.existsSync)(dir)) {
19
+ return [];
20
+ }
21
+ return [
22
+ ...(0, glob_1.globSync)('**/*.step.{ts,js,rb}', { absolute: true, cwd: dir }),
23
+ ...(0, glob_1.globSync)('**/*_step.{ts,js,py,rb}', { absolute: true, cwd: dir }),
24
+ ];
25
+ };
16
26
  const getStepFiles = (projectDir) => {
17
27
  const stepsDir = path_1.default.join(projectDir, 'steps');
28
+ const srcDir = path_1.default.join(projectDir, 'src');
29
+ return [...getStepFilesFromDir(stepsDir), ...getStepFilesFromDir(srcDir)];
30
+ };
31
+ exports.getStepFiles = getStepFiles;
32
+ const getStreamFilesFromDir = (dir) => {
33
+ if (!(0, fs_1.existsSync)(dir)) {
34
+ return [];
35
+ }
18
36
  return [
19
- ...(0, glob_1.globSync)('**/*.step.{ts,js,rb}', { absolute: true, cwd: stepsDir }),
20
- ...(0, glob_1.globSync)('**/*_step.{ts,js,py,rb}', { absolute: true, cwd: stepsDir }),
37
+ ...(0, glob_1.globSync)('**/*.stream.{ts,js,rb}', { absolute: true, cwd: dir }),
38
+ ...(0, glob_1.globSync)('**/*_stream.{ts,js,py,rb}', { absolute: true, cwd: dir }),
21
39
  ];
22
40
  };
23
- exports.getStepFiles = getStepFiles;
24
41
  const getStreamFiles = (projectDir) => {
25
42
  const stepsDir = path_1.default.join(projectDir, 'steps');
26
- return [
27
- ...(0, glob_1.globSync)('**/*.stream.{ts,js,rb}', { absolute: true, cwd: stepsDir }),
28
- ...(0, glob_1.globSync)('**/*_stream.{ts,js,py,rb}', { absolute: true, cwd: stepsDir }),
29
- ];
43
+ const srcDir = path_1.default.join(projectDir, 'src');
44
+ return [...getStreamFilesFromDir(stepsDir), ...getStreamFilesFromDir(srcDir)];
30
45
  };
31
46
  exports.getStreamFiles = getStreamFiles;
32
47
  // Helper function to recursively collect flow data
@@ -34,7 +49,12 @@ const collectFlows = async (projectDir, lockedData) => {
34
49
  const invalidSteps = [];
35
50
  const stepFiles = (0, exports.getStepFiles)(projectDir);
36
51
  const streamFiles = (0, exports.getStreamFiles)(projectDir);
37
- const deprecatedSteps = (0, glob_1.globSync)('**/*.step.py', { absolute: true, cwd: path_1.default.join(projectDir, 'steps') });
52
+ const stepsDir = path_1.default.join(projectDir, 'steps');
53
+ const srcDir = path_1.default.join(projectDir, 'src');
54
+ const deprecatedSteps = [
55
+ ...((0, fs_1.existsSync)(stepsDir) ? (0, glob_1.globSync)('**/*.step.py', { absolute: true, cwd: stepsDir }) : []),
56
+ ...((0, fs_1.existsSync)(srcDir) ? (0, glob_1.globSync)('**/*.step.py', { absolute: true, cwd: srcDir }) : []),
57
+ ];
38
58
  const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'));
39
59
  if (hasPythonFiles) {
40
60
  (0, activate_python_env_1.activatePythonVenv)({ baseDir: projectDir });
@@ -1,3 +1,5 @@
1
1
  export declare const enableAnalytics: () => void;
2
2
  export declare const disableAnalytics: () => void;
3
3
  export declare const identifyUser: () => void;
4
+ export declare const logCliError: (command: string, error: unknown) => void;
5
+ export declare const wrapAction: <T extends (...args: any[]) => Promise<any>>(action: T) => T;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.identifyUser = exports.disableAnalytics = exports.enableAnalytics = void 0;
3
+ exports.wrapAction = exports.logCliError = exports.identifyUser = exports.disableAnalytics = exports.enableAnalytics = void 0;
4
4
  const analytics_node_1 = require("@amplitude/analytics-node");
5
5
  const core_1 = require("@motiadev/core");
6
6
  const utils_1 = require("@motiadev/core/dist/src/analytics/utils");
@@ -38,3 +38,51 @@ const identifyUser = () => {
38
38
  }
39
39
  };
40
40
  exports.identifyUser = identifyUser;
41
+ const logCliError = (command, error) => {
42
+ try {
43
+ const errorMessage = error instanceof Error ? error.message : String(error);
44
+ const errorType = error instanceof Error ? error.constructor.name : 'Unknown';
45
+ const errorStack = error instanceof Error && error.stack ? error.stack.split('\n').slice(0, 10).join('\n') : undefined;
46
+ const truncatedMessage = errorMessage.length > 500 ? `${errorMessage.substring(0, 500)}...` : errorMessage;
47
+ (0, core_1.trackEvent)('cli_command_error', {
48
+ command,
49
+ error_message: truncatedMessage,
50
+ error_type: errorType,
51
+ ...(errorStack && { error_stack: errorStack }),
52
+ });
53
+ }
54
+ catch (logError) {
55
+ // Silently fail to not disrupt CLI operations
56
+ }
57
+ };
58
+ exports.logCliError = logCliError;
59
+ const getCommandNameFromArgs = () => {
60
+ const args = process.argv.slice(2);
61
+ const commandParts = [];
62
+ for (let i = 0; i < args.length && i < 3; i++) {
63
+ const arg = args[i];
64
+ if (!arg.startsWith('-') && !arg.startsWith('--')) {
65
+ commandParts.push(arg);
66
+ }
67
+ else {
68
+ break;
69
+ }
70
+ }
71
+ return commandParts.join(' ') || 'unknown';
72
+ };
73
+ const wrapAction = (action) => {
74
+ return (async (...args) => {
75
+ try {
76
+ return await action(...args);
77
+ }
78
+ catch (error) {
79
+ const commandName = getCommandNameFromArgs();
80
+ (0, exports.logCliError)(commandName, error);
81
+ await (0, analytics_node_1.flush)().promise.catch(() => {
82
+ // Silently fail
83
+ });
84
+ throw error;
85
+ }
86
+ });
87
+ };
88
+ exports.wrapAction = wrapAction;
package/dist/esm/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { program } from 'commander';
3
3
  import './cloud';
4
4
  import { handler } from './cloud/config-utils';
5
+ import { wrapAction } from './utils/analytics';
5
6
  import { version } from './version';
6
7
  const defaultPort = 3000;
7
8
  const defaultHost = '0.0.0.0';
@@ -46,19 +47,19 @@ program
46
47
  program
47
48
  .command('generate-types')
48
49
  .description('Generate types.d.ts file for your project')
49
- .action(async () => {
50
+ .action(wrapAction(async () => {
50
51
  const { generateTypes } = require('./generate-types');
51
52
  await generateTypes(process.cwd());
52
53
  process.exit(0);
53
- });
54
+ }));
54
55
  program
55
56
  .command('install')
56
57
  .description('Sets up Python virtual environment and install dependencies')
57
58
  .option('-v, --verbose', 'Enable verbose logging')
58
- .action(async (options) => {
59
+ .action(wrapAction(async (options) => {
59
60
  const { install } = require('./install');
60
61
  await install({ isVerbose: options.verbose });
61
- });
62
+ }));
62
63
  program
63
64
  .command('dev')
64
65
  .description('Start the development server')
@@ -68,7 +69,7 @@ program
68
69
  .option('-d, --debug', 'Enable debug logging')
69
70
  .option('-m, --mermaid', 'Enable mermaid diagram generation')
70
71
  .option('--motia-dir <path>', 'Path where .motia folder will be created')
71
- .action(async (arg) => {
72
+ .action(wrapAction(async (arg) => {
72
73
  if (arg.debug) {
73
74
  console.log('🔍 Debug logging enabled');
74
75
  process.env.LOG_LEVEL = 'debug';
@@ -77,7 +78,7 @@ program
77
78
  const host = arg.host ? arg.host : defaultHost;
78
79
  const { dev } = require('./dev');
79
80
  await dev(port, host, arg.disableVerbose, arg.mermaid, arg.motiaDir);
80
- });
81
+ }));
81
82
  program
82
83
  .command('start')
83
84
  .description('Start a server to run your Motia project')
@@ -86,7 +87,7 @@ program
86
87
  .option('-v, --disable-verbose', 'Disable verbose logging')
87
88
  .option('-d, --debug', 'Enable debug logging')
88
89
  .option('--motia-dir <path>', 'Path where .motia folder will be created')
89
- .action(async (arg) => {
90
+ .action(wrapAction(async (arg) => {
90
91
  if (arg.debug) {
91
92
  console.log('🔍 Debug logging enabled');
92
93
  process.env.LOG_LEVEL = 'debug';
@@ -95,89 +96,85 @@ program
95
96
  const host = arg.host ? arg.host : defaultHost;
96
97
  const { start } = require('./start');
97
98
  await start(port, host, arg.disableVerbose, arg.motiaDir);
98
- });
99
+ }));
99
100
  program
100
101
  .command('emit')
101
102
  .description('Emit an event to the Motia server')
102
103
  .requiredOption('--topic <topic>', 'Event topic/type to emit')
103
104
  .requiredOption('--message <message>', 'Event payload as JSON string')
104
105
  .option('-p, --port <number>', 'Port number (default: 3000)')
105
- .action(async (options) => {
106
+ .action(wrapAction(async (options) => {
106
107
  const port = options.port || 3000;
107
108
  const url = `http://localhost:${port}/emit`;
108
- try {
109
- const response = await fetch(url, {
110
- method: 'POST',
111
- headers: { 'Content-Type': 'application/json' },
112
- body: JSON.stringify({
113
- topic: options.topic,
114
- data: JSON.parse(options.message),
115
- }),
116
- });
117
- if (!response.ok) {
118
- throw new Error(`HTTP error! status: ${response.status}`);
119
- }
120
- const result = await response.json();
121
- console.log('Event emitted successfully:', result);
122
- }
123
- catch (error) {
124
- console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
125
- process.exit(1);
109
+ const response = await fetch(url, {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({
113
+ topic: options.topic,
114
+ data: JSON.parse(options.message),
115
+ }),
116
+ });
117
+ if (!response.ok) {
118
+ throw new Error(`HTTP error! status: ${response.status}`);
126
119
  }
127
- });
120
+ const result = await response.json();
121
+ console.log('Event emitted successfully:', result);
122
+ }));
128
123
  const generate = program.command('generate').description('Generate motia resources');
129
124
  generate
130
125
  .command('step')
131
126
  .description('Create a new step with interactive prompts')
132
127
  .option('-d, --dir <step file path>', 'The path relative to the steps directory, used to create the step file')
133
- .action(async (arg) => {
128
+ .action(wrapAction(async (arg) => {
134
129
  const { createStep } = require('./create-step');
135
130
  await createStep({
136
131
  stepFilePath: arg.dir,
137
132
  });
138
- });
133
+ }));
139
134
  generate
140
135
  .command('openapi')
141
136
  .description('Generate OpenAPI spec for your project')
142
137
  .option('-t, --title <title>', 'Title for the OpenAPI document. Defaults to project name')
143
138
  .option('-v, --version <version>', 'Version for the OpenAPI document. Defaults to 1.0.0', '1.0.0')
144
139
  .option('-o, --output <output>', 'Output file for the OpenAPI document. Defaults to openapi.json', 'openapi.json')
145
- .action(async (options) => {
140
+ .action(wrapAction(async (options) => {
146
141
  const { generateLockedData } = require('./generate-locked-data');
147
142
  const { generateOpenApi } = require('./openapi/generate');
148
143
  const lockedData = await generateLockedData({ projectDir: process.cwd() });
149
144
  const apiSteps = lockedData.apiSteps();
150
145
  generateOpenApi(process.cwd(), apiSteps, options.title, options.version, options.output);
151
146
  process.exit(0);
152
- });
147
+ }));
153
148
  const docker = program.command('docker').description('Motia docker commands');
154
149
  docker
155
150
  .command('setup')
156
151
  .description('Setup a motia-docker for your project')
157
- .action(async () => {
152
+ .action(wrapAction(async () => {
158
153
  const { setup } = require('./docker/setup');
159
154
  await setup();
160
155
  process.exit(0);
161
- });
156
+ }));
162
157
  docker
163
158
  .command('run')
164
159
  .description('Build and run your project in a docker container')
165
160
  .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)
166
161
  .option('-n, --project-name <project name>', 'The name for your project')
167
162
  .option('-s, --skip-build', 'Skip docker build')
168
- .action(async (arg) => {
163
+ .action(wrapAction(async (arg) => {
169
164
  const { run } = require('./docker/run');
170
165
  await run(arg.port, arg.projectName, arg.skipBuild);
171
166
  process.exit(0);
172
- });
167
+ }));
173
168
  docker
174
169
  .command('build')
175
170
  .description('Build your project in a docker container')
176
171
  .option('-n, --project-name <project name>', 'The name for your project')
177
- .action(async (arg) => {
172
+ .action(wrapAction(async (arg) => {
178
173
  const { build } = require('./docker/build');
179
174
  await build(arg.projectName);
180
175
  process.exit(0);
181
- });
176
+ }));
182
177
  program.version(version, '-V, --version', 'Output the current version');
183
- program.parse(process.argv);
178
+ program.parseAsync(process.argv).catch(() => {
179
+ process.exit(1);
180
+ });
@@ -1,4 +1,20 @@
1
+ import { flush } from '@amplitude/analytics-node';
2
+ import { logCliError } from '../utils/analytics';
1
3
  import { CLIOutputManager } from './cli-output-manager';
4
+ const getCommandName = () => {
5
+ const args = process.argv.slice(2);
6
+ const commandParts = [];
7
+ for (let i = 0; i < args.length && i < 3; i++) {
8
+ const arg = args[i];
9
+ if (!arg.startsWith('-') && !arg.startsWith('--')) {
10
+ commandParts.push(arg);
11
+ }
12
+ else {
13
+ break;
14
+ }
15
+ }
16
+ return commandParts.join(' ') || 'unknown';
17
+ };
2
18
  export class CliContext {
3
19
  constructor() {
4
20
  this.output = new CLIOutputManager();
@@ -22,10 +38,15 @@ export class CliContext {
22
38
  export function handler(handler) {
23
39
  return async (args) => {
24
40
  const context = new CliContext();
41
+ const commandName = getCommandName();
25
42
  try {
26
43
  await handler(args, context);
27
44
  }
28
45
  catch (error) {
46
+ logCliError(commandName, error);
47
+ await flush().promise.catch(() => {
48
+ // Silently fail
49
+ });
29
50
  if (error instanceof Error) {
30
51
  context.log('error', (message) => message.tag('failed').append(error.message));
31
52
  context.exit(1);
@@ -1,3 +1,4 @@
1
1
  import { type CronManager, type LockedData, type MotiaEventManager, type MotiaServer } from '@motiadev/core';
2
- import { Watcher } from './watcher';
3
- export declare const createDevWatchers: (lockedData: LockedData, server: MotiaServer, eventHandler: MotiaEventManager, cronManager: CronManager) => Watcher;
2
+ export declare const createDevWatchers: (lockedData: LockedData, server: MotiaServer, eventHandler: MotiaEventManager, cronManager: CronManager) => {
3
+ stop: () => Promise<void>;
4
+ };
@@ -1,9 +1,8 @@
1
1
  import { isApiStep, isCronStep, isEventStep, trackEvent, } from '@motiadev/core';
2
+ import { existsSync } from 'fs';
2
3
  import path from 'path';
3
4
  import { Watcher } from './watcher';
4
- export const createDevWatchers = (lockedData, server, eventHandler, cronManager) => {
5
- const stepDir = path.join(process.cwd(), 'steps');
6
- const watcher = new Watcher(stepDir, lockedData);
5
+ const setupWatcherHandlers = (watcher, lockedData, server, eventHandler, cronManager) => {
7
6
  watcher.onStreamChange((oldStream, stream) => {
8
7
  trackEvent('stream_updated', {
9
8
  streamName: stream.config.name,
@@ -74,5 +73,22 @@ export const createDevWatchers = (lockedData, server, eventHandler, cronManager)
74
73
  cronManager.removeCronJob(step);
75
74
  lockedData.deleteStep(step);
76
75
  });
77
- return watcher;
76
+ };
77
+ export const createDevWatchers = (lockedData, server, eventHandler, cronManager) => {
78
+ const baseDir = process.cwd();
79
+ const dirs = [path.join(baseDir, 'steps'), path.join(baseDir, 'src')];
80
+ const watchers = [];
81
+ dirs.forEach((dir) => {
82
+ if (existsSync(dir)) {
83
+ const watcher = new Watcher(dir, lockedData);
84
+ setupWatcherHandlers(watcher, lockedData, server, eventHandler, cronManager);
85
+ watcher.init();
86
+ watchers.push(watcher);
87
+ }
88
+ });
89
+ return {
90
+ stop: async () => {
91
+ await Promise.all(watchers.map((watcher) => watcher.stop()));
92
+ },
93
+ };
78
94
  };
package/dist/esm/dev.js CHANGED
@@ -51,7 +51,6 @@ export const dev = async (port, hostname, disableVerbose, enableMermaid, motiaFi
51
51
  mermaidGenerator.initialize(lockedData);
52
52
  trackEvent('mermaid_generator_initialized');
53
53
  }
54
- watcher.init();
55
54
  deployEndpoints(motiaServer, lockedData);
56
55
  motiaServer.app.get('/__motia', (_, res) => {
57
56
  const meta = {
@@ -2,31 +2,51 @@ import { getStepConfig, getStreamConfig, LockedData } from '@motiadev/core';
2
2
  import { NoPrinter, Printer } from '@motiadev/core/dist/src/printer';
3
3
  import colors from 'colors';
4
4
  import { randomUUID } from 'crypto';
5
+ import { existsSync } from 'fs';
5
6
  import { globSync } from 'glob';
6
7
  import path from 'path';
7
8
  import { activatePythonVenv } from './utils/activate-python-env';
8
9
  import { CompilationError } from './utils/errors/compilation.error';
9
10
  const version = `${randomUUID()}:${Math.floor(Date.now() / 1000)}`;
11
+ const getStepFilesFromDir = (dir) => {
12
+ if (!existsSync(dir)) {
13
+ return [];
14
+ }
15
+ return [
16
+ ...globSync('**/*.step.{ts,js,rb}', { absolute: true, cwd: dir }),
17
+ ...globSync('**/*_step.{ts,js,py,rb}', { absolute: true, cwd: dir }),
18
+ ];
19
+ };
10
20
  export const getStepFiles = (projectDir) => {
11
21
  const stepsDir = path.join(projectDir, 'steps');
22
+ const srcDir = path.join(projectDir, 'src');
23
+ return [...getStepFilesFromDir(stepsDir), ...getStepFilesFromDir(srcDir)];
24
+ };
25
+ const getStreamFilesFromDir = (dir) => {
26
+ if (!existsSync(dir)) {
27
+ return [];
28
+ }
12
29
  return [
13
- ...globSync('**/*.step.{ts,js,rb}', { absolute: true, cwd: stepsDir }),
14
- ...globSync('**/*_step.{ts,js,py,rb}', { absolute: true, cwd: stepsDir }),
30
+ ...globSync('**/*.stream.{ts,js,rb}', { absolute: true, cwd: dir }),
31
+ ...globSync('**/*_stream.{ts,js,py,rb}', { absolute: true, cwd: dir }),
15
32
  ];
16
33
  };
17
34
  export const getStreamFiles = (projectDir) => {
18
35
  const stepsDir = path.join(projectDir, 'steps');
19
- return [
20
- ...globSync('**/*.stream.{ts,js,rb}', { absolute: true, cwd: stepsDir }),
21
- ...globSync('**/*_stream.{ts,js,py,rb}', { absolute: true, cwd: stepsDir }),
22
- ];
36
+ const srcDir = path.join(projectDir, 'src');
37
+ return [...getStreamFilesFromDir(stepsDir), ...getStreamFilesFromDir(srcDir)];
23
38
  };
24
39
  // Helper function to recursively collect flow data
25
40
  export const collectFlows = async (projectDir, lockedData) => {
26
41
  const invalidSteps = [];
27
42
  const stepFiles = getStepFiles(projectDir);
28
43
  const streamFiles = getStreamFiles(projectDir);
29
- const deprecatedSteps = globSync('**/*.step.py', { absolute: true, cwd: path.join(projectDir, 'steps') });
44
+ const stepsDir = path.join(projectDir, 'steps');
45
+ const srcDir = path.join(projectDir, 'src');
46
+ const deprecatedSteps = [
47
+ ...(existsSync(stepsDir) ? globSync('**/*.step.py', { absolute: true, cwd: stepsDir }) : []),
48
+ ...(existsSync(srcDir) ? globSync('**/*.step.py', { absolute: true, cwd: srcDir }) : []),
49
+ ];
30
50
  const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'));
31
51
  if (hasPythonFiles) {
32
52
  activatePythonVenv({ baseDir: projectDir });
@@ -1,3 +1,5 @@
1
1
  export declare const enableAnalytics: () => void;
2
2
  export declare const disableAnalytics: () => void;
3
3
  export declare const identifyUser: () => void;
4
+ export declare const logCliError: (command: string, error: unknown) => void;
5
+ export declare const wrapAction: <T extends (...args: any[]) => Promise<any>>(action: T) => T;
@@ -1,5 +1,5 @@
1
- import { add, Identify, identify, init, setOptOut, Types } from '@amplitude/analytics-node';
2
- import { getUserIdentifier, isAnalyticsEnabled } from '@motiadev/core';
1
+ import { add, flush, Identify, identify, init, setOptOut, Types } from '@amplitude/analytics-node';
2
+ import { getUserIdentifier, isAnalyticsEnabled, trackEvent } from '@motiadev/core';
3
3
  import { getProjectName } from '@motiadev/core/dist/src/analytics/utils';
4
4
  import { version } from '../version';
5
5
  import { MotiaEnrichmentPlugin } from './amplitude/enrichment-plugin';
@@ -32,3 +32,49 @@ export const identifyUser = () => {
32
32
  // Silently fail
33
33
  }
34
34
  };
35
+ export const logCliError = (command, error) => {
36
+ try {
37
+ const errorMessage = error instanceof Error ? error.message : String(error);
38
+ const errorType = error instanceof Error ? error.constructor.name : 'Unknown';
39
+ const errorStack = error instanceof Error && error.stack ? error.stack.split('\n').slice(0, 10).join('\n') : undefined;
40
+ const truncatedMessage = errorMessage.length > 500 ? `${errorMessage.substring(0, 500)}...` : errorMessage;
41
+ trackEvent('cli_command_error', {
42
+ command,
43
+ error_message: truncatedMessage,
44
+ error_type: errorType,
45
+ ...(errorStack && { error_stack: errorStack }),
46
+ });
47
+ }
48
+ catch (logError) {
49
+ // Silently fail to not disrupt CLI operations
50
+ }
51
+ };
52
+ const getCommandNameFromArgs = () => {
53
+ const args = process.argv.slice(2);
54
+ const commandParts = [];
55
+ for (let i = 0; i < args.length && i < 3; i++) {
56
+ const arg = args[i];
57
+ if (!arg.startsWith('-') && !arg.startsWith('--')) {
58
+ commandParts.push(arg);
59
+ }
60
+ else {
61
+ break;
62
+ }
63
+ }
64
+ return commandParts.join(' ') || 'unknown';
65
+ };
66
+ export const wrapAction = (action) => {
67
+ return (async (...args) => {
68
+ try {
69
+ return await action(...args);
70
+ }
71
+ catch (error) {
72
+ const commandName = getCommandNameFromArgs();
73
+ logCliError(commandName, error);
74
+ await flush().promise.catch(() => {
75
+ // Silently fail
76
+ });
77
+ throw error;
78
+ }
79
+ });
80
+ };
@@ -1,3 +1,4 @@
1
1
  import { type CronManager, type LockedData, type MotiaEventManager, type MotiaServer } from '@motiadev/core';
2
- import { Watcher } from './watcher';
3
- export declare const createDevWatchers: (lockedData: LockedData, server: MotiaServer, eventHandler: MotiaEventManager, cronManager: CronManager) => Watcher;
2
+ export declare const createDevWatchers: (lockedData: LockedData, server: MotiaServer, eventHandler: MotiaEventManager, cronManager: CronManager) => {
3
+ stop: () => Promise<void>;
4
+ };
@@ -1,3 +1,5 @@
1
1
  export declare const enableAnalytics: () => void;
2
2
  export declare const disableAnalytics: () => void;
3
3
  export declare const identifyUser: () => void;
4
+ export declare const logCliError: (command: string, error: unknown) => void;
5
+ export declare const wrapAction: <T extends (...args: any[]) => Promise<any>>(action: T) => T;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "motia",
3
3
  "description": "A Modern Unified Backend Framework for APIs, Events and Agents",
4
- "version": "0.8.4-beta.142-001319",
4
+ "version": "0.8.5-beta.142",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -46,9 +46,9 @@
46
46
  "python-ast": "^0.1.0",
47
47
  "table": "^6.9.0",
48
48
  "ts-node": "^10.9.2",
49
- "@motiadev/stream-client-node": "0.8.4-beta.142-001319",
50
- "@motiadev/workbench": "0.8.4-beta.142-001319",
51
- "@motiadev/core": "0.8.4-beta.142-001319"
49
+ "@motiadev/core": "0.8.5-beta.142",
50
+ "@motiadev/workbench": "0.8.5-beta.142",
51
+ "@motiadev/stream-client-node": "0.8.5-beta.142"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@amplitude/analytics-types": "^2.9.2",