motia 0.5.11-beta.120-433270 → 0.5.11-beta.120-685453

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 (91) hide show
  1. package/dist/cjs/cli.js +0 -8
  2. package/dist/cjs/cloud/build/builders/python/add-package-to-archive.js +19 -16
  3. package/dist/cjs/cloud/build/builders/python/index.js +18 -10
  4. package/dist/cjs/cloud/build/builders/python/python-builder.py +204 -22
  5. package/dist/cjs/create/index.js +1 -13
  6. package/dist/cjs/create/templates/default/motia-workbench.json +15 -16
  7. package/dist/cjs/create/templates/default/services/pet-store.ts.txt +24 -0
  8. package/dist/cjs/create/templates/default/services/types.ts.txt +21 -0
  9. package/dist/cjs/create/templates/default/steps/01-api.step.ts-features.json.txt +67 -0
  10. package/dist/cjs/create/templates/{basic-tutorial → default/steps}/01-api.step.ts.txt +5 -13
  11. package/dist/cjs/create/templates/default/steps/02-process-food-order.step.ts-features.json.txt +67 -0
  12. package/dist/{esm/create/templates/basic-tutorial → cjs/create/templates/default/steps}/02-process-food-order.step.ts.txt +21 -7
  13. package/dist/cjs/create/templates/default/steps/03-state-audit-cron.step.ts-features.json.txt +26 -0
  14. package/dist/cjs/create/templates/default/steps/03-state-audit-cron.step.ts.txt +51 -0
  15. package/dist/cjs/create/templates/default/steps/04-notification.step.ts.txt +35 -0
  16. package/dist/cjs/create/templates/default/tutorial.tsx.txt +643 -0
  17. package/dist/cjs/create/templates/generate.js +2 -8
  18. package/dist/cjs/create/templates/generate.ts +2 -11
  19. package/dist/cjs/create/templates/index.js +0 -1
  20. package/dist/cjs/create/templates/index.ts +0 -1
  21. package/dist/cjs/dev-watchers.js +2 -3
  22. package/dist/cjs/generate-locked-data.js +2 -2
  23. package/dist/cjs/generate-types.js +2 -2
  24. package/dist/cjs/utils/install-lambda-python-packages.js +1 -1
  25. package/dist/cjs/watcher.d.ts +1 -2
  26. package/dist/cjs/watcher.js +5 -6
  27. package/dist/esm/cli.js +0 -8
  28. package/dist/esm/cloud/build/builders/python/add-package-to-archive.js +19 -16
  29. package/dist/esm/cloud/build/builders/python/index.js +18 -10
  30. package/dist/esm/cloud/build/builders/python/python-builder.py +204 -22
  31. package/dist/esm/create/index.js +1 -13
  32. package/dist/esm/create/templates/default/motia-workbench.json +15 -16
  33. package/dist/esm/create/templates/default/services/pet-store.ts.txt +24 -0
  34. package/dist/esm/create/templates/default/services/types.ts.txt +21 -0
  35. package/dist/esm/create/templates/default/steps/01-api.step.ts-features.json.txt +67 -0
  36. package/dist/esm/create/templates/{basic-tutorial → default/steps}/01-api.step.ts.txt +5 -13
  37. package/dist/esm/create/templates/default/steps/02-process-food-order.step.ts-features.json.txt +67 -0
  38. package/dist/{cjs/create/templates/basic-tutorial → esm/create/templates/default/steps}/02-process-food-order.step.ts.txt +21 -7
  39. package/dist/esm/create/templates/default/steps/03-state-audit-cron.step.ts-features.json.txt +26 -0
  40. package/dist/esm/create/templates/default/steps/03-state-audit-cron.step.ts.txt +51 -0
  41. package/dist/esm/create/templates/default/steps/04-notification.step.ts.txt +35 -0
  42. package/dist/esm/create/templates/default/tutorial.tsx.txt +643 -0
  43. package/dist/esm/create/templates/generate.js +2 -8
  44. package/dist/esm/create/templates/generate.ts +2 -11
  45. package/dist/esm/create/templates/index.js +0 -1
  46. package/dist/esm/create/templates/index.ts +0 -1
  47. package/dist/esm/dev-watchers.js +2 -3
  48. package/dist/esm/generate-locked-data.js +2 -2
  49. package/dist/esm/generate-types.js +2 -2
  50. package/dist/esm/utils/install-lambda-python-packages.js +1 -1
  51. package/dist/esm/watcher.d.ts +1 -2
  52. package/dist/esm/watcher.js +5 -6
  53. package/dist/types/watcher.d.ts +1 -2
  54. package/package.json +4 -4
  55. package/dist/cjs/cloud/build/builders/python/trace_packages.py +0 -212
  56. package/dist/cjs/cloud/build/builders/python/trace_project_files.py +0 -41
  57. package/dist/cjs/create/setup-tutorial-flow.d.ts +0 -6
  58. package/dist/cjs/create/setup-tutorial-flow.js +0 -30
  59. package/dist/cjs/create/templates/basic-tutorial/03-state-audit-cron.step.ts.txt +0 -42
  60. package/dist/cjs/create/templates/basic-tutorial/04_new_order_notifications.step.py.txt +0 -27
  61. package/dist/cjs/create/templates/basic-tutorial/motia-workbench.json +0 -28
  62. package/dist/cjs/create/templates/basic-tutorial/services/pet-store.ts.txt +0 -32
  63. package/dist/cjs/create/templates/default/00-noop.step.ts.txt +0 -33
  64. package/dist/cjs/create/templates/default/00-noop.step.tsx.txt +0 -18
  65. package/dist/cjs/create/templates/default/01-api.step.ts.txt +0 -70
  66. package/dist/cjs/create/templates/default/02-test-state.step.ts.txt +0 -53
  67. package/dist/cjs/create/templates/default/03-check-state-change.step.ts.txt +0 -54
  68. package/dist/esm/cloud/build/builders/python/trace_packages.py +0 -212
  69. package/dist/esm/cloud/build/builders/python/trace_project_files.py +0 -41
  70. package/dist/esm/create/setup-tutorial-flow.d.ts +0 -6
  71. package/dist/esm/create/setup-tutorial-flow.js +0 -23
  72. package/dist/esm/create/templates/basic-tutorial/03-state-audit-cron.step.ts.txt +0 -42
  73. package/dist/esm/create/templates/basic-tutorial/04_new_order_notifications.step.py.txt +0 -27
  74. package/dist/esm/create/templates/basic-tutorial/motia-workbench.json +0 -28
  75. package/dist/esm/create/templates/basic-tutorial/services/pet-store.ts.txt +0 -32
  76. package/dist/esm/create/templates/default/00-noop.step.ts.txt +0 -33
  77. package/dist/esm/create/templates/default/00-noop.step.tsx.txt +0 -18
  78. package/dist/esm/create/templates/default/01-api.step.ts.txt +0 -70
  79. package/dist/esm/create/templates/default/02-test-state.step.ts.txt +0 -53
  80. package/dist/esm/create/templates/default/03-check-state-change.step.ts.txt +0 -54
  81. package/dist/types/create/setup-tutorial-flow.d.ts +0 -6
  82. /package/dist/cjs/create/templates/python/{00_noop_step.py.txt → steps/00_noop_step.py.txt} +0 -0
  83. /package/dist/cjs/create/templates/python/{00_noop_step.tsx.txt → steps/00_noop_step.tsx.txt} +0 -0
  84. /package/dist/cjs/create/templates/python/{01_api_step.py.txt → steps/01_api_step.py.txt} +0 -0
  85. /package/dist/cjs/create/templates/python/{02_test_state_step.py.txt → steps/02_test_state_step.py.txt} +0 -0
  86. /package/dist/cjs/create/templates/python/{03_check_state_change_step.py.txt → steps/03_check_state_change_step.py.txt} +0 -0
  87. /package/dist/esm/create/templates/python/{00_noop_step.py.txt → steps/00_noop_step.py.txt} +0 -0
  88. /package/dist/esm/create/templates/python/{00_noop_step.tsx.txt → steps/00_noop_step.tsx.txt} +0 -0
  89. /package/dist/esm/create/templates/python/{01_api_step.py.txt → steps/01_api_step.py.txt} +0 -0
  90. /package/dist/esm/create/templates/python/{02_test_state_step.py.txt → steps/02_test_state_step.py.txt} +0 -0
  91. /package/dist/esm/create/templates/python/{03_check_state_change_step.py.txt → steps/03_check_state_change_step.py.txt} +0 -0
package/dist/cjs/cli.js CHANGED
@@ -156,14 +156,6 @@ generate
156
156
  stepFilePath: arg.dir,
157
157
  });
158
158
  });
159
- generate
160
- .command('tutorial-flow')
161
- .description('Download the tutorial flow into an existing motia project')
162
- .action((0, config_utils_1.handler)(async (_, context) => {
163
- const { createTutorialFlow } = require('./create/setup-tutorial-flow');
164
- await createTutorialFlow({ context });
165
- process.exit(0);
166
- }));
167
159
  const docker = commander_1.program.command('docker').description('Motia docker commands');
168
160
  docker
169
161
  .command('setup')
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.addPackageToArchive = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const colors_1 = __importDefault(require("colors"));
9
10
  const shouldIgnore = (filePath) => {
10
11
  const ignorePatterns = [/\.pyc$/, /\.egg$/, /__pycache__/, /\.dist-info$/];
11
12
  return ignorePatterns.some((pattern) => pattern.test(filePath));
@@ -30,24 +31,26 @@ const addDirectoryToArchive = async (archive, baseDir, dirPath) => {
30
31
  .filter(Boolean));
31
32
  };
32
33
  const addPackageToArchive = async (archive, sitePackagesDir, packageName) => {
33
- const packageNameVariations = [packageName, `${packageName}.py`];
34
- // Iterate over all possible package name variations
35
- for (const pkg of packageNameVariations) {
36
- let fullPath = path_1.default.join(sitePackagesDir, pkg);
37
- if (!fs_1.default.existsSync(fullPath)) {
38
- // If not found, try next package name variation
39
- continue;
34
+ // First try the package name as is
35
+ let fullPath = path_1.default.join(sitePackagesDir, packageName);
36
+ // If not found, try with .py extension
37
+ if (!fs_1.default.existsSync(fullPath)) {
38
+ const pyPath = path_1.default.join(sitePackagesDir, `${packageName}.py`);
39
+ if (fs_1.default.existsSync(pyPath)) {
40
+ fullPath = pyPath;
40
41
  }
41
- const stat = fs_1.default.statSync(fullPath);
42
- if (stat.isDirectory()) {
43
- await addDirectoryToArchive(archive, sitePackagesDir, fullPath);
44
- }
45
- else {
46
- const relativePath = path_1.default.relative(sitePackagesDir, fullPath);
47
- archive.append(fs_1.default.createReadStream(fullPath), relativePath);
48
- }
49
- // package added successfully
42
+ }
43
+ if (!fs_1.default.existsSync(fullPath)) {
44
+ console.log(colors_1.default.yellow(`Warning: Package not found in site-packages: ${packageName}`));
50
45
  return;
51
46
  }
47
+ const stat = fs_1.default.statSync(fullPath);
48
+ if (stat.isDirectory()) {
49
+ await addDirectoryToArchive(archive, sitePackagesDir, fullPath);
50
+ }
51
+ else {
52
+ const relativePath = path_1.default.relative(sitePackagesDir, fullPath);
53
+ archive.append(fs_1.default.createReadStream(fullPath), relativePath);
54
+ }
52
55
  };
53
56
  exports.addPackageToArchive = addPackageToArchive;
@@ -23,33 +23,41 @@ class PythonBuilder {
23
23
  const normalizedEntrypointPath = entrypointPath.replace(/[.]step.py$/, '_step.py');
24
24
  const sitePackagesDir = `${process.env.PYTHON_SITE_PACKAGES}-lambda`;
25
25
  // Get Python builder response
26
- const { packages, files } = await this.getPythonBuilderData(step);
26
+ const { packages } = await this.getPythonBuilderData(step);
27
27
  // Add main file to archive
28
28
  if (!fs_1.default.existsSync(step.filePath)) {
29
29
  throw new Error(`Source file not found: ${step.filePath}`);
30
30
  }
31
31
  archive.append(fs_1.default.createReadStream(step.filePath), path_1.default.relative(this.builder.projectDir, normalizedEntrypointPath));
32
- files.forEach((file) => archive.append(fs_1.default.createReadStream(file), path_1.default.relative(this.builder.projectDir, file)));
33
- if (packages.length > 0) {
34
- await Promise.all(packages.map((pkg) => (0, add_package_to_archive_1.addPackageToArchive)(archive, sitePackagesDir, pkg.name)));
35
- this.listener.onBuildProgress(step, `Added ${packages.length} packages to archive`);
36
- }
32
+ await Promise.all(packages.map(async (packageName) => (0, add_package_to_archive_1.addPackageToArchive)(archive, sitePackagesDir, packageName)));
37
33
  return normalizedEntrypointPath;
38
34
  }
39
35
  async build(step) {
40
36
  const entrypointPath = step.filePath.replace(this.builder.projectDir, '');
41
37
  const bundlePath = path_1.default.join('python', entrypointPath.replace(/(.*)\.py$/, '$1.zip'));
38
+ const normalizedEntrypointPath = entrypointPath.replace(/[.]step.py$/, '_step.py');
42
39
  const outfile = path_1.default.join(constants_1.distDir, bundlePath);
43
40
  try {
44
41
  // Create output directory
45
42
  fs_1.default.mkdirSync(path_1.default.dirname(outfile), { recursive: true });
46
43
  this.listener.onBuildStart(step);
47
- // Create the step zip archive
44
+ // Get Python builder response
45
+ const { packages } = await this.getPythonBuilderData(step);
48
46
  const stepArchiver = new archiver_1.Archiver(outfile);
49
- // Build the step
50
47
  const stepPath = await this.buildStep(step, stepArchiver);
51
- // Add static files to the archive
48
+ // Add main file to archive
49
+ if (!fs_1.default.existsSync(step.filePath)) {
50
+ throw new Error(`Source file not found: ${step.filePath}`);
51
+ }
52
+ stepArchiver.append(fs_1.default.createReadStream(step.filePath), path_1.default.relative(this.builder.projectDir, normalizedEntrypointPath));
53
+ // Add all imported files to archive
54
+ this.listener.onBuildProgress(step, 'Adding imported files to archive...');
55
+ const sitePackagesDir = `${process.env.PYTHON_SITE_PACKAGES}-lambda`;
52
56
  (0, include_static_files_1.includeStaticFiles)([step], this.builder, stepArchiver);
57
+ if (packages.length > 0) {
58
+ await Promise.all(packages.map(async (packageName) => (0, add_package_to_archive_1.addPackageToArchive)(stepArchiver, sitePackagesDir, packageName)));
59
+ this.listener.onBuildProgress(step, `Added ${packages.length} packages to archive`);
60
+ }
53
61
  // Finalize the archive and wait for completion
54
62
  const size = await stepArchiver.finalize();
55
63
  this.builder.registerStep({ entrypointPath: stepPath, bundlePath, step, type: 'python' });
@@ -92,7 +100,7 @@ class PythonBuilder {
92
100
  }
93
101
  async getPythonBuilderData(step) {
94
102
  return new Promise((resolve, reject) => {
95
- const child = (0, child_process_1.spawn)('python', [path_1.default.join(__dirname, 'python-builder.py'), this.builder.projectDir, step.filePath], {
103
+ const child = (0, child_process_1.spawn)('python', [path_1.default.join(__dirname, 'python-builder.py'), step.filePath], {
96
104
  cwd: this.builder.projectDir,
97
105
  stdio: [undefined, undefined, 'pipe', 'ipc'],
98
106
  });
@@ -1,39 +1,221 @@
1
1
  import os
2
2
  import sys
3
3
  import json
4
+ import importlib.util
4
5
  import traceback
6
+ import site
7
+ import builtins
8
+ import ast
9
+ import importlib.metadata
10
+ import subprocess
11
+ import re
12
+ from typing import Set, List, Tuple, Optional, Dict, Any
13
+ from pathlib import Path
14
+ from functools import lru_cache
5
15
 
6
- from trace_packages import trace_packages
7
- from trace_project_files import trace_project_files
16
+ NODEIPCFD = int(os.environ["NODE_CHANNEL_FD"])
8
17
 
9
- NODEIPCFD = int(os.environ.get("NODE_CHANNEL_FD", 0))
18
+ # Cache for built-in modules to avoid repeated checks
19
+ _builtin_modules_cache: Set[str] = set()
10
20
 
11
- def main() -> None:
12
- """Main entry point for the script."""
13
- if len(sys.argv) != 3:
14
- print("Usage: python python-builder.py <project_dir> <entry_file>")
15
- sys.exit(2)
21
+ @lru_cache(maxsize=1024)
22
+ def is_valid_package_name(name: str) -> bool:
23
+ """Check if a name is a valid package name."""
24
+ if not name or name.startswith('_'):
25
+ return False
26
+
27
+ # Skip common special cases
28
+ invalid_names = {'__main__', 'module', 'cython_runtime', 'builtins'}
29
+ return name not in invalid_names
30
+
31
+ @lru_cache(maxsize=1024)
32
+ def get_package_name(module_name: str) -> str:
33
+ """Get the top-level package name from a module name."""
34
+ return module_name.split('.')[0]
35
+
36
+ @lru_cache(maxsize=1024)
37
+ def clean_package_name(package_name: str) -> str:
38
+ """Clean package name by removing version specifiers and other metadata."""
39
+ # Remove version specifiers and conditions using regex
40
+ package_name = re.sub(r'[<>=~!;].*$', '', package_name)
41
+ # Remove any remaining whitespace and convert underscores to hyphens
42
+ return package_name.strip().replace('_', '-')
16
43
 
17
- project_dir = os.path.abspath(sys.argv[1])
18
- entry_file = os.path.abspath(sys.argv[2])
44
+ @lru_cache(maxsize=1024)
45
+ def extract_base_package_name(dependency_spec: str) -> str:
46
+ """
47
+ Extract the base package name from a complex dependency specification.
48
+ Handles cases like:
49
+ - 'package (>=1.2.1,<2.0.0)'
50
+ - 'package[extra] (>=1.2.1)'
51
+ - 'package ; extra == "vlm"'
52
+ - 'package (>=1.2.1) ; sys_platform == "darwin"'
53
+ """
54
+ # First, remove any conditions after semicolon
55
+ base_spec = dependency_spec.split(';')[0].strip()
56
+
57
+ # Extract the package name before any version specifiers or extras
58
+ match = re.match(r'^([a-zA-Z0-9_.-]+)(?:\[[^\]]+\])?(?:\s*\([^)]*\))?$', base_spec)
59
+
60
+ return clean_package_name(match.group(1) if match else base_spec)
19
61
 
62
+ @lru_cache(maxsize=1024)
63
+ def is_package_installed(package_name: str) -> bool:
64
+ """Check if a package is installed in the current environment."""
65
+ try:
66
+ # Try both hyphenated and non-hyphenated versions
67
+ try:
68
+ importlib.metadata.distribution(package_name)
69
+ return True
70
+ except importlib.metadata.PackageNotFoundError:
71
+ # Try with hyphens replaced by underscores
72
+ alt_name = package_name.replace('-', '_')
73
+ if alt_name != package_name:
74
+ importlib.metadata.distribution(alt_name)
75
+ return True
76
+ return False
77
+ except importlib.metadata.PackageNotFoundError:
78
+ return False
79
+
80
+ @lru_cache(maxsize=1024)
81
+ def is_builtin_module(module_name: str) -> bool:
82
+ """Check if a module is a Python built-in module."""
83
+ if module_name in _builtin_modules_cache:
84
+ return True
85
+
20
86
  try:
21
- # Find project dependencies
22
- packages = trace_packages(project_dir, entry_file)
23
- files = trace_project_files(project_dir, entry_file)
87
+ module = importlib.import_module(module_name)
24
88
 
25
- # Prepare output
89
+ # Built-in modules either have no __file__ attribute or their file is in the standard library
90
+ if not hasattr(module, '__file__'):
91
+ _builtin_modules_cache.add(module_name)
92
+ return True
93
+
94
+ # Get the standard library path
95
+ stdlib_path = os.path.dirname(os.__file__)
96
+
97
+ # Check if the module's file is in the standard library
98
+ is_builtin = module.__file__ and module.__file__.startswith(stdlib_path)
99
+ if is_builtin:
100
+ _builtin_modules_cache.add(module_name)
101
+ return is_builtin
102
+ except ImportError:
103
+ return False
104
+
105
+ def get_direct_imports(file_path: str) -> Set[str]:
106
+ """Extract direct imports from a Python file using AST parsing."""
107
+ direct_imports = set()
108
+
109
+ try:
110
+ with open(file_path, 'r') as f:
111
+ content = f.read()
112
+
113
+ tree = ast.parse(content)
114
+ for node in ast.walk(tree):
115
+ if isinstance(node, ast.Import):
116
+ for name in node.names:
117
+ base_pkg = name.name.split('.')[0]
118
+ if is_valid_package_name(base_pkg) and not is_builtin_module(base_pkg):
119
+ direct_imports.add(base_pkg)
120
+ elif isinstance(node, ast.ImportFrom):
121
+ if node.module:
122
+ base_pkg = node.module.split('.')[0]
123
+ if is_valid_package_name(base_pkg) and not is_builtin_module(base_pkg):
124
+ direct_imports.add(base_pkg)
125
+ except Exception as e:
126
+ print(f"Warning: Could not parse imports from {file_path}: {str(e)}")
127
+
128
+ return direct_imports
129
+
130
+ @lru_cache(maxsize=1024)
131
+ def is_optional_dependency(req: str) -> bool:
132
+ """Check if a dependency is an optional dependency."""
133
+ return '[' in req or 'extra ==' in req
134
+
135
+ def get_package_dependencies(package_name: str, processed: Set[str] = None) -> Set[str]:
136
+ """Get all dependencies (including sub-dependencies) for a given package."""
137
+ if processed is None:
138
+ processed = set()
139
+
140
+ if package_name in processed or is_builtin_module(package_name):
141
+ return set()
142
+
143
+ processed.add(package_name)
144
+ all_dependencies = set()
145
+
146
+ try:
147
+ # Try to get the distribution
148
+ try:
149
+ dist = importlib.metadata.distribution(package_name)
150
+ except importlib.metadata.PackageNotFoundError:
151
+ print(f'Warning: Package {package_name} not found')
152
+ return all_dependencies
153
+
154
+ # Filter out optional dependencies
155
+ sub_dependencies = list(filter(lambda dep: not is_optional_dependency(dep), dist.requires or []))
156
+
157
+ # Get direct dependencies
158
+ for req in sub_dependencies:
159
+ base_pkg = extract_base_package_name(req)
160
+
161
+ if base_pkg and base_pkg not in processed:
162
+ # Try both hyphenated and non-hyphenated versions
163
+ for dep_name in [base_pkg, base_pkg.replace('-', '_'), base_pkg.replace('_', '-')]:
164
+ try:
165
+ importlib.import_module(dep_name)
166
+ all_dependencies.add(dep_name)
167
+ # Recursively get sub-dependencies
168
+ all_dependencies.update(get_package_dependencies(dep_name, processed))
169
+ break
170
+ except ImportError:
171
+ continue
172
+
173
+ except Exception as e:
174
+ print(f"Warning: Error processing {package_name}: {str(e)}")
175
+
176
+ return all_dependencies
177
+
178
+ def trace_imports(entry_file: str) -> List[str]:
179
+ """Find all imported Python packages and files starting from an entry file."""
180
+ entry_file = os.path.abspath(entry_file)
181
+ module_dir = os.path.dirname(entry_file)
182
+
183
+ if module_dir not in sys.path:
184
+ sys.path.insert(0, module_dir)
185
+
186
+ # Get direct imports from the entry file
187
+ direct_imports = get_direct_imports(entry_file)
188
+
189
+ # Initialize sets to track packages
190
+ all_packages = set()
191
+ processed_packages = set()
192
+
193
+ # Process each direct import and its dependencies
194
+ for package_name in direct_imports:
195
+ if is_valid_package_name(package_name):
196
+ all_packages.add(package_name)
197
+ # Get all dependencies including sub-dependencies
198
+ all_packages.update(get_package_dependencies(package_name, processed_packages))
199
+
200
+ # Filter out built-in packages
201
+ non_builtin_packages = {pkg for pkg in all_packages if not is_builtin_module(pkg)}
202
+
203
+ return sorted(list(non_builtin_packages))
204
+
205
+ def main() -> None:
206
+ """Main entry point for the script."""
207
+ if len(sys.argv) != 2:
208
+ print("Usage: python python-builder.py <entry_file>", file=sys.stderr)
209
+ sys.exit(1)
210
+
211
+ entry_file = sys.argv[1]
212
+ try:
213
+ packages = trace_imports(entry_file)
26
214
  output = {
27
- 'packages': packages,
28
- 'files': files,
215
+ 'packages': packages
29
216
  }
30
-
31
- # Output as JSON
32
217
  bytes_message = (json.dumps(output) + '\n').encode('utf-8')
33
- if NODEIPCFD > 0:
34
- os.write(NODEIPCFD, bytes_message)
35
- else:
36
- print(bytes_message)
218
+ os.write(NODEIPCFD, bytes_message)
37
219
  sys.exit(0)
38
220
  except Exception as e:
39
221
  print(f"Error: {str(e)}", file=sys.stderr)
@@ -177,20 +177,8 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
177
177
  fs_1.default.cpSync(cursorTemplateDir, cursorTargetDir, { recursive: true });
178
178
  context.log('cursor-folder-created', (message) => message.tag('success').append('Folder').append('.cursor', 'cyan').append('has been created.'));
179
179
  }
180
- const stepsDir = path_1.default.join(rootDir, 'steps');
181
- if (!(0, utils_1.checkIfDirectoryExists)(stepsDir)) {
182
- fs_1.default.mkdirSync(stepsDir);
183
- context.log('steps-directory-created', (message) => message.tag('success').append('Folder').append('steps', 'cyan').append('has been created.'));
184
- }
185
- if (!(0, utils_1.checkIfDirectoryExists)(path_1.default.join(stepsDir, 'basic-tutorial'))) {
186
- fs_1.default.mkdirSync(path_1.default.join(stepsDir, 'basic-tutorial'));
187
- }
188
- await (0, setup_template_1.setupTemplate)('basic-tutorial', stepsDir, context);
189
180
  if (template) {
190
- if (!(0, utils_1.checkIfDirectoryExists)(path_1.default.join(stepsDir, template))) {
191
- fs_1.default.mkdirSync(path_1.default.join(stepsDir, template));
192
- }
193
- await (0, setup_template_1.setupTemplate)(template, stepsDir, context);
181
+ await (0, setup_template_1.setupTemplate)(template, rootDir, context);
194
182
  }
195
183
  const packageManager = await installNodeDependencies(rootDir, context);
196
184
  if (template === 'python') {
@@ -1,28 +1,27 @@
1
1
  [
2
2
  {
3
- "id": "default",
3
+ "id": "basic-tutorial",
4
4
  "config": {
5
- "steps/00-noop.step.ts": {
6
- "x": -507,
7
- "y": 111,
5
+ "steps/03-state-audit-cron.step.ts": {
6
+ "x": -165,
7
+ "y": 217,
8
8
  "sourceHandlePosition": "right"
9
9
  },
10
- "steps/01-api.step.ts": {
11
- "x": -281,
12
- "y": 94,
13
- "sourceHandlePosition": "right",
10
+ "steps/02-process-food-order.step.ts": {
11
+ "x": 211,
12
+ "y": 17,
13
+ "sourceHandlePosition": "bottom",
14
14
  "targetHandlePosition": "left"
15
15
  },
16
- "steps/02-test-state.step.ts": {
17
- "x": 60,
18
- "y": 36,
19
- "sourceHandlePosition": "bottom",
16
+ "steps/01-api.step.ts": {
17
+ "x": -100,
18
+ "y": 3,
19
+ "sourceHandlePosition": "right",
20
20
  "targetHandlePosition": "left"
21
21
  },
22
- "steps/03-check-state-change.step.ts": {
23
- "x": 78,
24
- "y": 199,
25
- "targetHandlePosition": "top"
22
+ "steps/04-notification.step.ts": {
23
+ "x": 300,
24
+ "y": 264
26
25
  }
27
26
  }
28
27
  }
@@ -0,0 +1,24 @@
1
+ import { Order, Pet } from './types'
2
+
3
+ export const petStoreService = {
4
+ createPet: async (pet: Omit<Pet, 'id'>): Promise<Pet> => {
5
+ const response = await fetch('https://petstore.swagger.io/v2/pet', {
6
+ method: 'POST',
7
+ body: JSON.stringify({
8
+ name: pet.name,
9
+ photoUrls: [pet.photoUrl],
10
+ status: 'available',
11
+ }),
12
+ headers: { 'Content-Type': 'application/json' },
13
+ })
14
+ return response.json()
15
+ },
16
+ createOrder: async (order: Omit<Order, 'id'>): Promise<Order> => {
17
+ const response = await fetch('https://petstore.swagger.io/v2/store/order', {
18
+ method: 'POST',
19
+ body: JSON.stringify(order),
20
+ headers: { 'Content-Type': 'application/json' },
21
+ })
22
+ return response.json()
23
+ },
24
+ }
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod'
2
+
3
+ export const petSchema = z.object({
4
+ id: z.number(),
5
+ name: z.string(),
6
+ photoUrl: z.string(),
7
+ })
8
+
9
+ export const orderStatusSchema = z.enum(['placed', 'approved', 'delivered'])
10
+
11
+ export const orderSchema = z.object({
12
+ id: z.string(),
13
+ quantity: z.number(),
14
+ petId: z.number(),
15
+ shipDate: z.string(),
16
+ status: orderStatusSchema,
17
+ })
18
+
19
+ export type Pet = z.infer<typeof petSchema>
20
+ export type Order = z.infer<typeof orderSchema>
21
+ export type OrderStatus = z.infer<typeof orderStatusSchema>
@@ -0,0 +1,67 @@
1
+ [
2
+ {
3
+ "id": "step-configuration",
4
+ "title": "Step Configuration",
5
+ "description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
6
+ "lines": [
7
+ "6-30"
8
+ ]
9
+ },
10
+ {
11
+ "id": "api-configuration",
12
+ "title": "API Step",
13
+ "description": "Definition of an API endpoint",
14
+ "lines": [
15
+ "12-13"
16
+ ]
17
+ },
18
+ {
19
+ "id": "request-body",
20
+ "title": "Request body",
21
+ "description": "Definition of the expected request body. Motia will automatically generate types based on this schema.",
22
+ "lines": [
23
+ "14-25"
24
+ ]
25
+ },
26
+ {
27
+ "id": "response-payload",
28
+ "title": "Response Payload",
29
+ "description": "Definition of the expected response payload, Motia will generate the types automatically based on this schema. This is also important to create the Open API spec later.",
30
+ "lines": [
31
+ "26-28"
32
+ ]
33
+ },
34
+ {
35
+ "id": "event-driven-architecture",
36
+ "title": "Emits",
37
+ "description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
38
+ "lines": [
39
+ "29",
40
+ "39-46"
41
+ ]
42
+ },
43
+ {
44
+ "id": "handler",
45
+ "title": "Handler",
46
+ "description": "The handler is the function that will be executed when the step is triggered. This one receives the request body and emits events.",
47
+ "lines": [
48
+ "32-50"
49
+ ]
50
+ },
51
+ {
52
+ "id": "logger",
53
+ "title": "Logger",
54
+ "description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function. We encourage you to use it instead of console.log. It will automatically be tied to the trace id of the request.",
55
+ "lines": [
56
+ "33"
57
+ ]
58
+ },
59
+ {
60
+ "id": "http-response",
61
+ "title": "HTTP Response",
62
+ "description": "The handler can return a response to the client. This is how we can return a response to the client. It must comply with the responseSchema defined in the step configuration.",
63
+ "lines": [
64
+ "49"
65
+ ]
66
+ }
67
+ ]
@@ -1,6 +1,7 @@
1
1
  import { ApiRouteConfig, Handlers } from 'motia'
2
2
  import { z } from 'zod'
3
- import { petStoreService } from './services/pet-store'
3
+ import { petStoreService } from '../services/pet-store'
4
+ import { petSchema } from '../services/types'
4
5
 
5
6
  export const config: ApiRouteConfig = {
6
7
  type: 'api',
@@ -23,10 +24,7 @@ export const config: ApiRouteConfig = {
23
24
  .optional(),
24
25
  }),
25
26
  responseSchema: {
26
- 200: z.object({
27
- message: z.string(),
28
- traceId: z.string(),
29
- }),
27
+ 200: petSchema,
30
28
  },
31
29
  emits: ['process-food-order'],
32
30
  }
@@ -35,7 +33,6 @@ export const handler: Handlers['ApiTrigger'] = async (req, { logger, emit, trace
35
33
  logger.info('Step 01 – Processing API Step', { body: req.body })
36
34
 
37
35
  const { pet, foodOrder } = req.body
38
-
39
36
  const newPetRecord = await petStoreService.createPet(pet)
40
37
 
41
38
  if (foodOrder) {
@@ -43,16 +40,11 @@ export const handler: Handlers['ApiTrigger'] = async (req, { logger, emit, trace
43
40
  topic: 'process-food-order',
44
41
  data: {
45
42
  ...foodOrder,
43
+ email: 'test@test.com', // sample email
46
44
  petId: newPetRecord.id,
47
45
  },
48
46
  })
49
47
  }
50
48
 
51
- return {
52
- status: 200,
53
- body: {
54
- traceId,
55
- message: 'Your pet has been registered and your order is being processed',
56
- },
57
- }
49
+ return { status: 200, body: newPetRecord }
58
50
  }
@@ -0,0 +1,67 @@
1
+ [
2
+ {
3
+ "id": "step-configuration",
4
+ "title": "Step Configuration",
5
+ "description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
6
+ "lines": [
7
+ "5-17"
8
+ ]
9
+ },
10
+ {
11
+ "id": "event-configuration",
12
+ "title": "Event Step",
13
+ "description": "Definition of an event step that subscribes to specific topics",
14
+ "lines": [
15
+ "6",
16
+ "10-11"
17
+ ]
18
+ },
19
+ {
20
+ "id": "input-schema",
21
+ "title": "Input Schema",
22
+ "description": "Definition of the expected input data structure from the subscribed topic. Motia will automatically generate types based on this schema.",
23
+ "lines": [
24
+ "12-16"
25
+ ]
26
+ },
27
+ {
28
+ "id": "event-emits",
29
+ "title": "Emits",
30
+ "description": "We can define the events that this step will emit, triggering other Motia Steps.",
31
+ "lines": [
32
+ "11"
33
+ ]
34
+ },
35
+ {
36
+ "id": "handler",
37
+ "title": "Handler",
38
+ "description": "The handler is the function that will be executed when the step receives an event from its subscribed topic. It processes the input data and can emit new events.",
39
+ "lines": [
40
+ "19-44"
41
+ ]
42
+ },
43
+ {
44
+ "id": "state",
45
+ "title": "State Management",
46
+ "description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
47
+ "lines": [
48
+ "28"
49
+ ]
50
+ },
51
+ {
52
+ "id": "event-emission",
53
+ "title": "Event Emission",
54
+ "description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
55
+ "lines": [
56
+ "30-43"
57
+ ]
58
+ },
59
+ {
60
+ "id": "logger",
61
+ "title": "Logger",
62
+ "description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function and automatically ties to the trace id of the request.",
63
+ "lines": [
64
+ "20"
65
+ ]
66
+ }
67
+ ]