motia 0.5.11-beta.120-357906 → 0.5.11-beta.120-391598

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 (42) hide show
  1. package/dist/cjs/cloud/build/builders/node/index.js +4 -1
  2. package/dist/cjs/cloud/build/builders/python/add-package-to-archive.d.ts +2 -0
  3. package/dist/cjs/cloud/build/builders/python/add-package-to-archive.js +56 -0
  4. package/dist/cjs/cloud/build/builders/python/index.d.ts +3 -12
  5. package/dist/cjs/cloud/build/builders/python/index.js +80 -216
  6. package/dist/cjs/cloud/build/builders/python/python-builder.py +226 -0
  7. package/dist/cjs/cloud/endpoints.d.ts +2 -0
  8. package/dist/cjs/cloud/endpoints.js +102 -0
  9. package/dist/cjs/cloud/new-deployment/listeners/streaming-deployment-listener.d.ts +44 -0
  10. package/dist/cjs/cloud/new-deployment/listeners/streaming-deployment-listener.js +278 -0
  11. package/dist/cjs/cloud/new-deployment/streams/deployment-stream.d.ts +46 -0
  12. package/dist/cjs/cloud/new-deployment/streams/deployment-stream.js +108 -0
  13. package/dist/cjs/dev.js +3 -1
  14. package/dist/esm/cloud/build/builders/node/index.js +4 -1
  15. package/dist/esm/cloud/build/builders/python/add-package-to-archive.d.ts +2 -0
  16. package/dist/esm/cloud/build/builders/python/add-package-to-archive.js +49 -0
  17. package/dist/esm/cloud/build/builders/python/index.d.ts +3 -12
  18. package/dist/esm/cloud/build/builders/python/index.js +80 -216
  19. package/dist/esm/cloud/build/builders/python/python-builder.py +226 -0
  20. package/dist/esm/cloud/endpoints.d.ts +2 -0
  21. package/dist/esm/cloud/endpoints.js +98 -0
  22. package/dist/esm/cloud/new-deployment/listeners/streaming-deployment-listener.d.ts +44 -0
  23. package/dist/esm/cloud/new-deployment/listeners/streaming-deployment-listener.js +274 -0
  24. package/dist/esm/cloud/new-deployment/streams/deployment-stream.d.ts +46 -0
  25. package/dist/esm/cloud/new-deployment/streams/deployment-stream.js +103 -0
  26. package/dist/esm/dev.js +3 -1
  27. package/dist/types/cloud/build/builders/python/add-package-to-archive.d.ts +2 -0
  28. package/dist/types/cloud/build/builders/python/index.d.ts +3 -12
  29. package/dist/types/cloud/endpoints.d.ts +2 -0
  30. package/dist/types/cloud/new-deployment/listeners/streaming-deployment-listener.d.ts +44 -0
  31. package/dist/types/cloud/new-deployment/streams/deployment-stream.d.ts +46 -0
  32. package/package.json +4 -4
  33. package/dist/cjs/cloud/build/builders/python/uv-packager.d.ts +0 -14
  34. package/dist/cjs/cloud/build/builders/python/uv-packager.js +0 -76
  35. package/dist/cjs/utils/uv-install.d.ts +0 -6
  36. package/dist/cjs/utils/uv-install.js +0 -129
  37. package/dist/esm/cloud/build/builders/python/uv-packager.d.ts +0 -14
  38. package/dist/esm/cloud/build/builders/python/uv-packager.js +0 -69
  39. package/dist/esm/utils/uv-install.d.ts +0 -6
  40. package/dist/esm/utils/uv-install.js +0 -122
  41. package/dist/types/cloud/build/builders/python/uv-packager.d.ts +0 -14
  42. package/dist/types/utils/uv-install.d.ts +0 -6
@@ -68,7 +68,10 @@ class NodeBuilder {
68
68
  async buildApiSteps(steps) {
69
69
  const relativePath = path_1.default.relative(constants_1.distDir, this.builder.projectDir);
70
70
  const getStepPath = (step) => {
71
- return step.filePath.replace(this.builder.projectDir, relativePath).replace(/(.*)\.(ts|js)$/, '$1.js');
71
+ return step.filePath
72
+ .replace(this.builder.projectDir, relativePath)
73
+ .replace(/(.*)\.(ts|js)$/, '$1.js')
74
+ .replace(/\\/g, '/');
72
75
  };
73
76
  const file = fs_1.default
74
77
  .readFileSync(path_1.default.join(__dirname, 'router-template.ts'), 'utf-8')
@@ -0,0 +1,2 @@
1
+ import { Archiver } from '../archiver';
2
+ export declare const addPackageToArchive: (archive: Archiver, sitePackagesDir: string, packageName: string) => Promise<void>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addPackageToArchive = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const colors_1 = __importDefault(require("colors"));
10
+ const shouldIgnore = (filePath) => {
11
+ const ignorePatterns = [/\.pyc$/, /\.egg$/, /__pycache__/, /\.dist-info$/];
12
+ return ignorePatterns.some((pattern) => pattern.test(filePath));
13
+ };
14
+ const addDirectoryToArchive = async (archive, baseDir, dirPath) => {
15
+ const files = fs_1.default.readdirSync(dirPath);
16
+ await Promise.all(files
17
+ .map(async (file) => {
18
+ const fullPath = path_1.default.join(dirPath, file);
19
+ const relativePath = path_1.default.relative(baseDir, fullPath);
20
+ if (shouldIgnore(relativePath)) {
21
+ return;
22
+ }
23
+ const stat = fs_1.default.statSync(fullPath);
24
+ if (stat.isDirectory()) {
25
+ await addDirectoryToArchive(archive, baseDir, fullPath);
26
+ }
27
+ else {
28
+ archive.append(fs_1.default.createReadStream(fullPath), relativePath);
29
+ }
30
+ })
31
+ .filter(Boolean));
32
+ };
33
+ const addPackageToArchive = async (archive, sitePackagesDir, packageName) => {
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;
41
+ }
42
+ }
43
+ if (!fs_1.default.existsSync(fullPath)) {
44
+ console.log(colors_1.default.yellow(`Warning: Package not found in site-packages: ${packageName}`));
45
+ return;
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
+ }
55
+ };
56
+ exports.addPackageToArchive = addPackageToArchive;
@@ -4,18 +4,9 @@ import { BuildListener } from '../../../new-deployment/listeners/listener.types'
4
4
  export declare class PythonBuilder implements StepBuilder {
5
5
  private readonly builder;
6
6
  private readonly listener;
7
- private uvPackager;
8
- private uvConfig;
9
7
  constructor(builder: Builder, listener: BuildListener);
10
- private loadUvConfig;
11
- buildApiSteps(steps: Step<ApiRouteConfig>[]): Promise<RouterBuildResult>;
8
+ private buildStep;
12
9
  build(step: Step): Promise<void>;
13
- private addStepToArchive;
14
- private addPackagesToArchive;
15
- private shouldIgnoreFile;
16
- private normalizeStepPath;
17
- private createRouterTemplate;
18
- private findInternalFiles;
19
- private resolveModulePaths;
20
- private getModuleName;
10
+ buildApiSteps(steps: Step<ApiRouteConfig>[]): Promise<RouterBuildResult>;
11
+ private getPythonBuilderData;
21
12
  }
@@ -4,252 +4,116 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PythonBuilder = void 0;
7
+ const child_process_1 = require("child_process");
7
8
  const fs_1 = __importDefault(require("fs"));
8
9
  const path_1 = __importDefault(require("path"));
10
+ const activate_python_env_1 = require("../../../../utils/activate-python-env");
9
11
  const archiver_1 = require("../archiver");
10
12
  const include_static_files_1 = require("../include-static-files");
13
+ const add_package_to_archive_1 = require("./add-package-to-archive");
11
14
  const constants_1 = require("../../../new-deployment/constants");
12
- const uv_packager_1 = require("./uv-packager");
13
15
  class PythonBuilder {
14
16
  constructor(builder, listener) {
15
17
  this.builder = builder;
16
18
  this.listener = listener;
17
- this.uvConfig = { ...uv_packager_1.defaultUvConfig, ...this.loadUvConfig() };
18
- this.uvPackager = new uv_packager_1.UvPackager(this.builder.projectDir, this.uvConfig);
19
+ (0, activate_python_env_1.activatePythonVenv)({ baseDir: this.builder.projectDir });
19
20
  }
20
- loadUvConfig() {
21
- const configFiles = ['uv.config.json', '.uvrc.json'];
22
- for (const configFile of configFiles) {
23
- const configPath = path_1.default.join(this.builder.projectDir, configFile);
24
- if (fs_1.default.existsSync(configPath)) {
25
- try {
26
- const configContent = fs_1.default.readFileSync(configPath, 'utf-8');
27
- return JSON.parse(configContent);
28
- }
29
- catch (err) {
30
- console.warn(`Warning: Failed to load UV config from ${configFile}`);
31
- }
32
- }
33
- }
34
- return {};
35
- }
36
- async buildApiSteps(steps) {
37
- const zipName = 'router-python.zip';
38
- const archive = new archiver_1.Archiver(path_1.default.join(constants_1.distDir, zipName));
39
- if (!await this.uvPackager.checkUvInstalled()) {
40
- throw new Error('UV is not installed. Please install UV: curl -LsSf https://astral.sh/uv/install.sh | sh');
41
- }
42
- const tempSitePackages = path_1.default.join(constants_1.distDir, `temp-python-packages-${Date.now()}`);
43
- try {
44
- await this.uvPackager.packageDependencies(tempSitePackages);
45
- // Pequena pausa para garantir que o filesystem sincronize
46
- await new Promise(resolve => setTimeout(resolve, 100));
47
- await this.addPackagesToArchive(archive, tempSitePackages);
48
- for (const step of steps) {
49
- await this.addStepToArchive(step, archive);
50
- }
51
- const routerTemplate = this.createRouterTemplate(steps);
52
- archive.append(routerTemplate, 'router.py');
53
- (0, include_static_files_1.includeStaticFiles)(steps, this.builder, archive);
54
- const size = await archive.finalize();
55
- return { size, path: zipName };
56
- }
57
- catch (error) {
58
- throw new Error(`Failed to build Python API router: ${error}`);
59
- }
60
- finally {
61
- if (fs_1.default.existsSync(tempSitePackages)) {
62
- fs_1.default.rmSync(tempSitePackages, { recursive: true, force: true });
63
- }
21
+ async buildStep(step, archive) {
22
+ const entrypointPath = step.filePath.replace(this.builder.projectDir, '');
23
+ const normalizedEntrypointPath = entrypointPath.replace(/[.]step.py$/, '_step.py');
24
+ const sitePackagesDir = `${process.env.PYTHON_SITE_PACKAGES}-lambda`;
25
+ // Get Python builder response
26
+ const { packages } = await this.getPythonBuilderData(step);
27
+ // Add main file to archive
28
+ if (!fs_1.default.existsSync(step.filePath)) {
29
+ throw new Error(`Source file not found: ${step.filePath}`);
64
30
  }
31
+ archive.append(fs_1.default.createReadStream(step.filePath), path_1.default.relative(this.builder.projectDir, normalizedEntrypointPath));
32
+ await Promise.all(packages.map(async (packageName) => (0, add_package_to_archive_1.addPackageToArchive)(archive, sitePackagesDir, packageName)));
33
+ return normalizedEntrypointPath;
65
34
  }
66
35
  async build(step) {
67
36
  const entrypointPath = step.filePath.replace(this.builder.projectDir, '');
68
37
  const bundlePath = path_1.default.join('python', entrypointPath.replace(/(.*)\.py$/, '$1.zip'));
38
+ const normalizedEntrypointPath = entrypointPath.replace(/[.]step.py$/, '_step.py');
69
39
  const outfile = path_1.default.join(constants_1.distDir, bundlePath);
70
- this.builder.registerStep({ entrypointPath, bundlePath, step, type: 'python' });
71
- this.listener.onBuildStart(step);
72
40
  try {
73
- if (!await this.uvPackager.checkUvInstalled()) {
74
- throw new Error('UV is not installed. Please install UV: curl -LsSf https://astral.sh/uv/install.sh | sh');
75
- }
41
+ // Create output directory
76
42
  fs_1.default.mkdirSync(path_1.default.dirname(outfile), { recursive: true });
77
- const archive = new archiver_1.Archiver(outfile);
78
- const tempSitePackages = path_1.default.join(constants_1.distDir, `temp-python-packages-${Date.now()}`);
79
- try {
80
- await this.uvPackager.packageDependencies(tempSitePackages);
81
- await new Promise(resolve => setTimeout(resolve, 100));
82
- await this.addStepToArchive(step, archive);
83
- await this.addPackagesToArchive(archive, tempSitePackages);
84
- (0, include_static_files_1.includeStaticFiles)([step], this.builder, archive);
85
- const size = await archive.finalize();
86
- this.listener.onBuildEnd(step, size);
43
+ this.listener.onBuildStart(step);
44
+ // Get Python builder response
45
+ const { packages } = await this.getPythonBuilderData(step);
46
+ const stepArchiver = new archiver_1.Archiver(outfile);
47
+ const stepPath = await this.buildStep(step, stepArchiver);
48
+ // Add main file to archive
49
+ if (!fs_1.default.existsSync(step.filePath)) {
50
+ throw new Error(`Source file not found: ${step.filePath}`);
87
51
  }
88
- finally {
89
- if (fs_1.default.existsSync(tempSitePackages)) {
90
- fs_1.default.rmSync(tempSitePackages, { recursive: true, force: true });
91
- }
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`;
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`);
92
60
  }
61
+ // Finalize the archive and wait for completion
62
+ const size = await stepArchiver.finalize();
63
+ this.builder.registerStep({ entrypointPath: stepPath, bundlePath, step, type: 'python' });
64
+ this.listener.onBuildEnd(step, size);
93
65
  }
94
66
  catch (err) {
95
67
  this.listener.onBuildError(step, err);
96
68
  throw err;
97
69
  }
98
70
  }
99
- async addStepToArchive(step, archive) {
100
- const normalizedPath = this.normalizeStepPath(step, false);
101
- archive.append(fs_1.default.createReadStream(step.filePath), normalizedPath);
102
- const internalFiles = await this.findInternalFiles(step.filePath);
103
- for (const file of internalFiles) {
104
- const fullPath = path_1.default.join(this.builder.projectDir, file);
105
- if (fs_1.default.existsSync(fullPath) && fullPath !== step.filePath) {
106
- const archivePath = file.replace(/\.step\.py$/, '_step.py');
107
- archive.append(fs_1.default.createReadStream(fullPath), archivePath);
108
- }
109
- }
110
- }
111
- async addPackagesToArchive(archive, sitePackagesDir) {
112
- if (!fs_1.default.existsSync(sitePackagesDir)) {
113
- console.warn(`Warning: Site packages directory not found: ${sitePackagesDir}`);
114
- return;
115
- }
116
- // Verificar se o diretório está acessível
117
- try {
118
- fs_1.default.accessSync(sitePackagesDir, fs_1.default.constants.R_OK);
119
- }
120
- catch (error) {
121
- console.warn(`Warning: Cannot access site packages directory: ${sitePackagesDir}`);
122
- return;
123
- }
124
- const addDirectory = (dirPath, basePath = sitePackagesDir) => {
125
- try {
126
- const items = fs_1.default.readdirSync(dirPath);
127
- for (const item of items) {
128
- const fullPath = path_1.default.join(dirPath, item);
129
- const relativePath = path_1.default.relative(basePath, fullPath);
130
- if (this.shouldIgnoreFile(relativePath)) {
131
- continue;
132
- }
133
- try {
134
- const stat = fs_1.default.statSync(fullPath);
135
- if (stat.isDirectory()) {
136
- addDirectory(fullPath, basePath);
137
- }
138
- else {
139
- archive.append(fs_1.default.createReadStream(fullPath), relativePath);
140
- }
141
- }
142
- catch (error) {
143
- console.warn(`Warning: Could not process file ${fullPath}: ${error}`);
144
- }
145
- }
146
- }
147
- catch (error) {
148
- console.warn(`Warning: Could not read directory ${dirPath}: ${error}`);
149
- }
71
+ async buildApiSteps(steps) {
72
+ const getStepPath = (step) => {
73
+ const normalizedEntrypointPath = step.filePath.replace(/[.]step.py$/, '_step.py');
74
+ return normalizedEntrypointPath
75
+ .replace(`${this.builder.projectDir}/`, '')
76
+ .replace(/(.*)\.py$/, '$1')
77
+ .replace(/\\/g, '.')
78
+ .replace(/\//g, '.');
150
79
  };
151
- addDirectory(sitePackagesDir);
152
- }
153
- shouldIgnoreFile(filePath) {
154
- const ignorePatterns = [
155
- /\.pyc$/,
156
- /\.pyo$/,
157
- /\.egg$/,
158
- /\.egg-info$/,
159
- /__pycache__/,
160
- /\.dist-info$/,
161
- /^tests?\//,
162
- /^docs?\//,
163
- /^examples?\//,
164
- /\.pytest_cache/,
165
- ];
166
- return ignorePatterns.some((pattern) => pattern.test(filePath));
167
- }
168
- normalizeStepPath(step, normalizePythonModulePath) {
169
- let normalizedStepPath = step.filePath
170
- .replace(/[.]step.py$/, '_step.py') // Replace .step.py with _step.py
171
- .replace(`${this.builder.projectDir}/`, ''); // Remove the project directory from the path
172
- const pathParts = normalizedStepPath.split(path_1.default.sep).map((part) => part
173
- .replace(/^[0-9]+/g, '') // Remove numeric prefixes
174
- .replace(/[^a-zA-Z0-9._]/g, '_') // Replace any non-alphanumeric characters (except dots) with underscores
175
- .replace(/^_/, '')); // Remove leading underscore
176
- normalizedStepPath = normalizePythonModulePath
177
- ? pathParts.join('.') // Convert path delimiter to dot (python module separator)
178
- : '/' + pathParts.join(path_1.default.sep);
179
- return normalizedStepPath;
180
- }
181
- createRouterTemplate(steps) {
182
- const imports = steps
183
- .map((step, index) => {
184
- const moduleName = this.getModuleName(step);
185
- return `from ${moduleName} import handler as route${index}_handler, config as route${index}_config`;
186
- })
187
- .join('\n');
188
- const routerPaths = steps
189
- .map((step, index) => {
190
- const method = step.config.method.toUpperCase();
191
- const path = step.config.path;
192
- return ` '${method} ${path}': RouterPath('${step.config.name}', '${step.config.method.toLowerCase()}', route${index}_handler, route${index}_config)`;
193
- })
194
- .join(',\n');
195
- return fs_1.default
80
+ const zipName = 'router-python.zip';
81
+ const archive = new archiver_1.Archiver(path_1.default.join(constants_1.distDir, zipName));
82
+ const dependencies = ['uvicorn', 'pydantic', 'pydantic_core', 'uvloop', 'starlette', 'typing_inspection'];
83
+ const lambdaSitePackages = `${process.env.PYTHON_SITE_PACKAGES}-lambda`;
84
+ await Promise.all(dependencies.map(async (packageName) => (0, add_package_to_archive_1.addPackageToArchive)(archive, lambdaSitePackages, packageName)));
85
+ for (const step of steps) {
86
+ await this.buildStep(step, archive);
87
+ }
88
+ const file = fs_1.default
196
89
  .readFileSync(path_1.default.join(__dirname, 'router_template.py'), 'utf-8')
197
- .replace('# {{imports}}', imports)
198
- .replace('# {{router paths}}', routerPaths);
90
+ .replace('# {{imports}}', steps
91
+ .map((step, index) => `from ${getStepPath(step)} import handler as route${index}_handler, config as route${index}_config`)
92
+ .join('\n'))
93
+ .replace('# {{router paths}}', steps
94
+ .map((step, index) => `'${step.config.method} ${step.config.path}': RouterPath('${step.config.name}', '${step.config.method.toLowerCase()}', route${index}_handler, route${index}_config)`)
95
+ .join(',\n '));
96
+ archive.append(file, 'router.py');
97
+ (0, include_static_files_1.includeStaticFiles)(steps, this.builder, archive);
98
+ // Finalize the archive and wait for completion
99
+ const size = await archive.finalize();
100
+ return { size, path: zipName };
199
101
  }
200
- async findInternalFiles(entryFile) {
201
- const files = [];
202
- const visited = new Set();
203
- const analyzeFile = (filePath) => {
204
- if (visited.has(filePath) || !fs_1.default.existsSync(filePath)) {
205
- return;
206
- }
207
- visited.add(filePath);
208
- files.push(path_1.default.relative(this.builder.projectDir, filePath));
209
- try {
210
- const content = fs_1.default.readFileSync(filePath, 'utf-8');
211
- // Regex melhorada para capturar imports mais complexos
212
- const importRegex = /^(?:from\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+import|import\s+([a-zA-Z_][a-zA-Z0-9_.]*))/gm;
213
- let match;
214
- while ((match = importRegex.exec(content)) !== null) {
215
- const moduleName = match[1] || match[2]; // from X import Y ou import X
216
- // Verificar diferentes formas de import
217
- this.resolveModulePaths(moduleName, path_1.default.dirname(filePath)).forEach(possiblePath => {
218
- if (fs_1.default.existsSync(possiblePath)) {
219
- analyzeFile(possiblePath);
220
- }
221
- });
102
+ async getPythonBuilderData(step) {
103
+ return new Promise((resolve, reject) => {
104
+ const child = (0, child_process_1.spawn)('python', [path_1.default.join(__dirname, 'python-builder.py'), step.filePath], {
105
+ cwd: this.builder.projectDir,
106
+ stdio: [undefined, undefined, 'pipe', 'ipc'],
107
+ });
108
+ const err = [];
109
+ child.on('stderr', (data) => err.push(data.toString()));
110
+ child.on('message', resolve);
111
+ child.on('close', (code) => {
112
+ if (code !== 0) {
113
+ reject(new Error(err.join('')));
222
114
  }
223
- }
224
- catch (error) {
225
- console.warn(`Could not analyze file: ${filePath}`);
226
- }
227
- };
228
- analyzeFile(entryFile);
229
- return files;
230
- }
231
- resolveModulePaths(moduleName, currentDir) {
232
- const parts = moduleName.split('.');
233
- const baseName = parts[0];
234
- const subPath = parts.length > 1 ? path_1.default.join(...parts) : baseName;
235
- return [
236
- // Relativo ao arquivo atual
237
- path_1.default.join(currentDir, `${baseName}.py`),
238
- path_1.default.join(currentDir, baseName, '__init__.py'),
239
- path_1.default.join(currentDir, `${subPath}.py`),
240
- // Relativo ao diretório do projeto
241
- path_1.default.join(this.builder.projectDir, `${baseName}.py`),
242
- path_1.default.join(this.builder.projectDir, baseName, '__init__.py'),
243
- path_1.default.join(this.builder.projectDir, `${subPath}.py`),
244
- // Para utils.database -> utils/database.py
245
- path_1.default.join(this.builder.projectDir, subPath + '.py'),
246
- path_1.default.join(this.builder.projectDir, subPath, '__init__.py'),
247
- ];
248
- }
249
- getModuleName(step) {
250
- return this.normalizeStepPath(step, true)
251
- .replace(/\.py$/, '')
252
- .replace(/\//g, '.');
115
+ });
116
+ });
253
117
  }
254
118
  }
255
119
  exports.PythonBuilder = PythonBuilder;
@@ -0,0 +1,226 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import importlib.util
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
15
+
16
+ NODEIPCFD = int(os.environ["NODE_CHANNEL_FD"])
17
+
18
+ # Cache for built-in modules to avoid repeated checks
19
+ _builtin_modules_cache: Set[str] = set()
20
+
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('_', '-')
43
+
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)
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
+
86
+ try:
87
+ module = importlib.import_module(module_name)
88
+
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)
214
+ output = {
215
+ 'packages': packages
216
+ }
217
+ bytes_message = (json.dumps(output) + '\n').encode('utf-8')
218
+ os.write(NODEIPCFD, bytes_message)
219
+ sys.exit(0)
220
+ except Exception as e:
221
+ print(f"Error: {str(e)}", file=sys.stderr)
222
+ traceback.print_exc(file=sys.stderr)
223
+ sys.exit(3)
224
+
225
+ if __name__ == "__main__":
226
+ main()
@@ -0,0 +1,2 @@
1
+ import { LockedData, MotiaServer } from '@motiadev/core';
2
+ export declare const deployEndpoints: (server: MotiaServer, lockedData: LockedData) => void;