motia 0.6.4-beta.131-487060 → 0.6.4-beta.131-508662
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/cloud/build/builders/archiver.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/archiver.js +24 -0
- package/dist/cjs/cloud/build/builders/python/index.d.ts +5 -6
- package/dist/cjs/cloud/build/builders/python/index.js +55 -100
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.js +24 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.js +36 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.js +70 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/read-requirements.test.js +116 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.js +29 -0
- package/dist/cjs/cloud/build/builders/python/python-data/constants.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/constants.js +110 -0
- package/dist/cjs/cloud/build/builders/python/python-data/convert-import-path.d.ts +1 -0
- package/dist/cjs/cloud/build/builders/python/python-data/convert-import-path.js +24 -0
- package/dist/cjs/cloud/build/builders/python/python-data/extract-python-data.d.ts +7 -0
- package/dist/cjs/cloud/build/builders/python/python-data/extract-python-data.js +24 -0
- package/dist/cjs/cloud/build/builders/python/python-data/get-dependencies-from-file.d.ts +7 -0
- package/dist/cjs/cloud/build/builders/python/python-data/get-dependencies-from-file.js +71 -0
- package/dist/cjs/cloud/build/builders/python/python-data/python-errors.d.ts +13 -0
- package/dist/cjs/cloud/build/builders/python/python-data/python-errors.js +28 -0
- package/dist/cjs/cloud/build/builders/python/python-data/read-requirements.d.ts +15 -0
- package/dist/cjs/cloud/build/builders/python/python-data/read-requirements.js +32 -0
- package/dist/cjs/cloud/build/builders/python/python-data/resolve-dep-name.d.ts +6 -0
- package/dist/cjs/cloud/build/builders/python/python-data/resolve-dep-name.js +53 -0
- package/dist/cjs/cloud/build/builders/python/python-data/traverse-tree.d.ts +7 -0
- package/dist/cjs/cloud/build/builders/python/python-data/traverse-tree.js +60 -0
- package/dist/cjs/cloud/build/builders/python/router_template.py +0 -1
- package/dist/cjs/cloud/build/builders/python/uv-packager.d.ts +2 -4
- package/dist/cjs/cloud/build/builders/python/uv-packager.js +20 -72
- package/dist/cjs/generate-locked-data.d.ts +1 -0
- package/dist/cjs/generate-locked-data.js +11 -6
- package/dist/cjs/generate-types.js +3 -8
- package/dist/cjs/utils/activate-python-env.d.ts +1 -0
- package/dist/cjs/utils/activate-python-env.js +9 -4
- package/dist/esm/cloud/build/builders/archiver.d.ts +1 -0
- package/dist/esm/cloud/build/builders/archiver.js +24 -0
- package/dist/esm/cloud/build/builders/python/index.d.ts +5 -6
- package/dist/esm/cloud/build/builders/python/index.js +55 -100
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.js +22 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.js +31 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.js +68 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/read-requirements.test.js +111 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.js +24 -0
- package/dist/esm/cloud/build/builders/python/python-data/constants.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/constants.js +107 -0
- package/dist/esm/cloud/build/builders/python/python-data/convert-import-path.d.ts +1 -0
- package/dist/esm/cloud/build/builders/python/python-data/convert-import-path.js +17 -0
- package/dist/esm/cloud/build/builders/python/python-data/extract-python-data.d.ts +7 -0
- package/dist/esm/cloud/build/builders/python/python-data/extract-python-data.js +20 -0
- package/dist/esm/cloud/build/builders/python/python-data/get-dependencies-from-file.d.ts +7 -0
- package/dist/esm/cloud/build/builders/python/python-data/get-dependencies-from-file.js +67 -0
- package/dist/esm/cloud/build/builders/python/python-data/python-errors.d.ts +13 -0
- package/dist/esm/cloud/build/builders/python/python-data/python-errors.js +21 -0
- package/dist/esm/cloud/build/builders/python/python-data/read-requirements.d.ts +15 -0
- package/dist/esm/cloud/build/builders/python/python-data/read-requirements.js +25 -0
- package/dist/esm/cloud/build/builders/python/python-data/resolve-dep-name.d.ts +6 -0
- package/dist/esm/cloud/build/builders/python/python-data/resolve-dep-name.js +46 -0
- package/dist/esm/cloud/build/builders/python/python-data/traverse-tree.d.ts +7 -0
- package/dist/esm/cloud/build/builders/python/python-data/traverse-tree.js +53 -0
- package/dist/esm/cloud/build/builders/python/router_template.py +0 -1
- package/dist/esm/cloud/build/builders/python/uv-packager.d.ts +2 -4
- package/dist/esm/cloud/build/builders/python/uv-packager.js +20 -72
- package/dist/esm/generate-locked-data.d.ts +1 -0
- package/dist/esm/generate-locked-data.js +9 -5
- package/dist/esm/generate-types.js +3 -5
- package/dist/esm/utils/activate-python-env.d.ts +1 -0
- package/dist/esm/utils/activate-python-env.js +7 -3
- package/dist/types/cloud/build/builders/archiver.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/index.d.ts +5 -6
- package/dist/types/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/__tests__/get-dependencies-from-file.test.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/__tests__/resolve-dep-name.test.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/constants.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/convert-import-path.d.ts +1 -0
- package/dist/types/cloud/build/builders/python/python-data/extract-python-data.d.ts +7 -0
- package/dist/types/cloud/build/builders/python/python-data/get-dependencies-from-file.d.ts +7 -0
- package/dist/types/cloud/build/builders/python/python-data/python-errors.d.ts +13 -0
- package/dist/types/cloud/build/builders/python/python-data/read-requirements.d.ts +15 -0
- package/dist/types/cloud/build/builders/python/python-data/resolve-dep-name.d.ts +6 -0
- package/dist/types/cloud/build/builders/python/python-data/traverse-tree.d.ts +7 -0
- package/dist/types/cloud/build/builders/python/uv-packager.d.ts +2 -4
- package/dist/types/generate-locked-data.d.ts +1 -0
- package/dist/types/utils/activate-python-env.d.ts +1 -0
- package/package.json +7 -4
- package/dist/cjs/cloud/build/builders/python/package-copier.d.ts +0 -13
- package/dist/cjs/cloud/build/builders/python/package-copier.js +0 -38
- package/dist/cjs/cloud/build/builders/python/package-handler.d.ts +0 -27
- package/dist/cjs/cloud/build/builders/python/package-handler.js +0 -209
- package/dist/cjs/cloud/build/builders/python/python-dependency-analyzer.d.ts +0 -31
- package/dist/cjs/cloud/build/builders/python/python-dependency-analyzer.js +0 -348
- package/dist/esm/cloud/build/builders/python/package-copier.d.ts +0 -13
- package/dist/esm/cloud/build/builders/python/package-copier.js +0 -31
- package/dist/esm/cloud/build/builders/python/package-handler.d.ts +0 -27
- package/dist/esm/cloud/build/builders/python/package-handler.js +0 -202
- package/dist/esm/cloud/build/builders/python/python-dependency-analyzer.d.ts +0 -31
- package/dist/esm/cloud/build/builders/python/python-dependency-analyzer.js +0 -341
- package/dist/types/cloud/build/builders/python/package-copier.d.ts +0 -13
- package/dist/types/cloud/build/builders/python/package-handler.d.ts +0 -27
- package/dist/types/cloud/build/builders/python/python-dependency-analyzer.d.ts +0 -31
|
@@ -8,6 +8,7 @@ export declare class Archiver {
|
|
|
8
8
|
private readonly outputStream;
|
|
9
9
|
private uncompressedSize;
|
|
10
10
|
constructor(filePath: string);
|
|
11
|
+
appendDirectory(sourcePath: string, targetPath: string): void;
|
|
11
12
|
append(stream: fs.ReadStream | string, filePath: string): void;
|
|
12
13
|
finalize(): Promise<ArchiveResult>;
|
|
13
14
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.Archiver = void 0;
|
|
7
7
|
const archiver_1 = __importDefault(require("archiver"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
9
10
|
class Archiver {
|
|
10
11
|
constructor(filePath) {
|
|
11
12
|
this.uncompressedSize = 0;
|
|
@@ -13,6 +14,29 @@ class Archiver {
|
|
|
13
14
|
this.outputStream = fs_1.default.createWriteStream(filePath);
|
|
14
15
|
this.archive.pipe(this.outputStream);
|
|
15
16
|
}
|
|
17
|
+
appendDirectory(sourcePath, targetPath) {
|
|
18
|
+
try {
|
|
19
|
+
const items = fs_1.default.readdirSync(sourcePath);
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
const fullPath = path_1.default.join(sourcePath, item);
|
|
22
|
+
try {
|
|
23
|
+
const stat = fs_1.default.statSync(fullPath);
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
this.appendDirectory(fullPath, path_1.default.join(targetPath, item));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.append(fs_1.default.createReadStream(fullPath), targetPath ? path_1.default.join(targetPath, item) : item);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (_error) {
|
|
32
|
+
// Ignore individual file errors
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (_error) {
|
|
37
|
+
// Ignore directory read errors
|
|
38
|
+
}
|
|
39
|
+
}
|
|
16
40
|
append(stream, filePath) {
|
|
17
41
|
// Track uncompressed size
|
|
18
42
|
if (typeof stream === 'string') {
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { ApiRouteConfig, Step } from '@motiadev/core';
|
|
2
|
-
import { Builder, RouterBuildResult, StepBuilder } from '../../builder';
|
|
3
2
|
import { BuildListener } from '../../../new-deployment/listeners/listener.types';
|
|
3
|
+
import { Builder, RouterBuildResult, StepBuilder } from '../../builder';
|
|
4
4
|
export declare class PythonBuilder implements StepBuilder {
|
|
5
5
|
private readonly builder;
|
|
6
6
|
private readonly listener;
|
|
7
|
-
private
|
|
7
|
+
private packager;
|
|
8
8
|
constructor(builder: Builder, listener: BuildListener);
|
|
9
|
+
private getRequirements;
|
|
9
10
|
buildApiSteps(steps: Step<ApiRouteConfig>[]): Promise<RouterBuildResult>;
|
|
10
11
|
build(step: Step): Promise<void>;
|
|
11
|
-
private
|
|
12
|
-
private
|
|
13
|
-
private resolveModulePaths;
|
|
14
|
-
private normalizeStepPath;
|
|
12
|
+
private generatePackage;
|
|
13
|
+
private cleanup;
|
|
15
14
|
private createRouterTemplate;
|
|
16
15
|
private getModuleName;
|
|
17
16
|
}
|
|
@@ -6,40 +6,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.PythonBuilder = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const activate_python_env_1 = require("../../../../utils/activate-python-env");
|
|
10
|
+
const constants_1 = require("../../../new-deployment/constants");
|
|
9
11
|
const archiver_1 = require("../archiver");
|
|
10
12
|
const include_static_files_1 = require("../include-static-files");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const extract_python_data_1 = require("./python-data/extract-python-data");
|
|
14
|
+
const read_requirements_1 = require("./python-data/read-requirements");
|
|
15
|
+
const resolve_dep_name_1 = require("./python-data/resolve-dep-name");
|
|
16
|
+
const uv_packager_1 = require("./uv-packager");
|
|
14
17
|
class PythonBuilder {
|
|
15
18
|
constructor(builder, listener) {
|
|
16
19
|
this.builder = builder;
|
|
17
20
|
this.listener = listener;
|
|
18
21
|
(0, activate_python_env_1.activatePythonVenv)({ baseDir: this.builder.projectDir });
|
|
19
|
-
this.
|
|
22
|
+
this.packager = new uv_packager_1.UvPackager();
|
|
23
|
+
}
|
|
24
|
+
getRequirements() {
|
|
25
|
+
const requirementsFile = path_1.default.join(this.builder.projectDir, 'requirements.txt');
|
|
26
|
+
const depNames = Object.keys((0, read_requirements_1.readRequirements)(requirementsFile, (name) => ({ name, importName: name })));
|
|
27
|
+
const sitePackagesPath = (0, activate_python_env_1.getSitePackagesPath)({ baseDir: this.builder.projectDir });
|
|
28
|
+
const mapper = (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesPath);
|
|
29
|
+
const describer = (name) => {
|
|
30
|
+
const [, to] = mapper.find(([from]) => from === name) ?? [];
|
|
31
|
+
return { name, importName: to ?? name };
|
|
32
|
+
};
|
|
33
|
+
return (0, read_requirements_1.readRequirements)(requirementsFile, describer);
|
|
20
34
|
}
|
|
21
35
|
async buildApiSteps(steps) {
|
|
22
36
|
const zipName = 'router-python.zip';
|
|
23
37
|
const archive = new archiver_1.Archiver(path_1.default.join(constants_1.distDir, zipName));
|
|
24
|
-
|
|
38
|
+
const bundleDir = path_1.default.join(constants_1.distDir, 'python', 'router');
|
|
25
39
|
try {
|
|
26
|
-
|
|
27
|
-
const pythonFiles = steps.map((step) => step.filePath);
|
|
28
|
-
// Build packages using the unified handler
|
|
29
|
-
const buildResult = await this.packageHandler.buildPackages({
|
|
30
|
-
pythonFiles,
|
|
31
|
-
archive,
|
|
32
|
-
projectDir: this.builder.projectDir,
|
|
33
|
-
});
|
|
34
|
-
tempDirToCleanup = buildResult.tempDirCreated;
|
|
35
|
-
// Add all step files to archive
|
|
36
|
-
for (const step of steps) {
|
|
37
|
-
await this.addStepToArchive(step, archive);
|
|
38
|
-
}
|
|
39
|
-
// Add router template
|
|
40
|
+
fs_1.default.mkdirSync(bundleDir, { recursive: true });
|
|
40
41
|
const routerTemplate = this.createRouterTemplate(steps);
|
|
41
42
|
archive.append(routerTemplate, 'router.py');
|
|
42
|
-
|
|
43
|
+
await this.generatePackage(bundleDir, 'router.py', archive, routerTemplate);
|
|
43
44
|
(0, include_static_files_1.includeStaticFiles)(steps, this.builder, archive);
|
|
44
45
|
const { compressedSize, uncompressedSize } = await archive.finalize();
|
|
45
46
|
return { compressedSize, uncompressedSize, path: zipName };
|
|
@@ -48,34 +49,21 @@ class PythonBuilder {
|
|
|
48
49
|
throw new Error(`Failed to build Python API router: ${error}`);
|
|
49
50
|
}
|
|
50
51
|
finally {
|
|
51
|
-
this.
|
|
52
|
+
this.cleanup(bundleDir);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
async build(step) {
|
|
55
|
-
const entrypointPath = step.filePath.replace(this.builder.projectDir, '')
|
|
56
|
+
const entrypointPath = step.filePath.replace(this.builder.projectDir, '');
|
|
56
57
|
const bundlePath = path_1.default.join('python', entrypointPath.replace(/(.*)\.py$/, '$1.zip'));
|
|
58
|
+
const bundleDir = path_1.default.join(constants_1.distDir, 'python', entrypointPath.replace(/(.*)\.py$/, '$1'));
|
|
57
59
|
const outfile = path_1.default.join(constants_1.distDir, bundlePath);
|
|
58
60
|
this.builder.registerStep({ entrypointPath, bundlePath, step, type: 'python' });
|
|
59
61
|
this.listener.onBuildStart(step);
|
|
60
|
-
let tempDirToCleanup;
|
|
61
62
|
try {
|
|
62
63
|
fs_1.default.mkdirSync(path_1.default.dirname(outfile), { recursive: true });
|
|
64
|
+
fs_1.default.mkdirSync(bundleDir, { recursive: true });
|
|
63
65
|
const archive = new archiver_1.Archiver(outfile);
|
|
64
|
-
|
|
65
|
-
const pythonFiles = [step.filePath];
|
|
66
|
-
const internalFiles = await this.findInternalFiles(step.filePath);
|
|
67
|
-
for (const file of internalFiles) {
|
|
68
|
-
pythonFiles.push(path_1.default.join(this.builder.projectDir, file));
|
|
69
|
-
}
|
|
70
|
-
// Build packages using the unified handler
|
|
71
|
-
const buildResult = await this.packageHandler.buildPackages({
|
|
72
|
-
pythonFiles,
|
|
73
|
-
archive,
|
|
74
|
-
projectDir: this.builder.projectDir,
|
|
75
|
-
});
|
|
76
|
-
tempDirToCleanup = buildResult.tempDirCreated;
|
|
77
|
-
// Add step file to archive
|
|
78
|
-
await this.addStepToArchive(step, archive);
|
|
66
|
+
await this.generatePackage(bundleDir, entrypointPath, archive);
|
|
79
67
|
// Include static files
|
|
80
68
|
(0, include_static_files_1.includeStaticFiles)([step], this.builder, archive);
|
|
81
69
|
const { compressedSize, uncompressedSize } = await archive.finalize();
|
|
@@ -87,78 +75,44 @@ class PythonBuilder {
|
|
|
87
75
|
throw err;
|
|
88
76
|
}
|
|
89
77
|
finally {
|
|
90
|
-
this.
|
|
78
|
+
this.cleanup(bundleDir);
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
|
-
async
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for (const file of
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
archive.append(fs_1.default.createReadStream(fullPath), archivePath);
|
|
81
|
+
async generatePackage(bundleDir, entrypointPath, archive, fileContent) {
|
|
82
|
+
const requirements = this.getRequirements();
|
|
83
|
+
const { externalDependencies, files } = (0, extract_python_data_1.extractPythonData)(this.builder.projectDir, entrypointPath, requirements, fileContent);
|
|
84
|
+
// move files
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
fs_1.default.mkdirSync(path_1.default.dirname(path_1.default.join(bundleDir, file)), { recursive: true });
|
|
87
|
+
if (fileContent && file === entrypointPath) {
|
|
88
|
+
fs_1.default.writeFileSync(path_1.default.join(bundleDir, file), fileContent);
|
|
102
89
|
}
|
|
90
|
+
else {
|
|
91
|
+
fs_1.default.copyFileSync(path_1.default.join(this.builder.projectDir, file), path_1.default.join(bundleDir, file));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const dependencies = Object.values(externalDependencies);
|
|
95
|
+
if (dependencies.length > 0) {
|
|
96
|
+
// create requirements.txt
|
|
97
|
+
fs_1.default.writeFileSync(path_1.default.join(bundleDir, 'requirements.txt'), Object.values(externalDependencies).join('\n'));
|
|
98
|
+
await this.packager.packageDependencies(bundleDir);
|
|
103
99
|
}
|
|
100
|
+
// zip entire folder
|
|
101
|
+
archive.appendDirectory(bundleDir, '/');
|
|
104
102
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const visited = new Set();
|
|
108
|
-
const analyzeFile = (filePath) => {
|
|
109
|
-
if (visited.has(filePath) || !fs_1.default.existsSync(filePath))
|
|
110
|
-
return;
|
|
111
|
-
visited.add(filePath);
|
|
112
|
-
files.push(path_1.default.relative(this.builder.projectDir, filePath));
|
|
103
|
+
cleanup(bundleDir) {
|
|
104
|
+
if (bundleDir && fs_1.default.existsSync(bundleDir)) {
|
|
113
105
|
try {
|
|
114
|
-
|
|
115
|
-
const importRegex = /^(?:from\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+import|import\s+([a-zA-Z_][a-zA-Z0-9_.]*))/gm;
|
|
116
|
-
let match;
|
|
117
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
118
|
-
const moduleName = match[1] || match[2];
|
|
119
|
-
this.resolveModulePaths(moduleName, path_1.default.dirname(filePath)).forEach((possiblePath) => {
|
|
120
|
-
if (fs_1.default.existsSync(possiblePath)) {
|
|
121
|
-
analyzeFile(possiblePath);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
}
|
|
106
|
+
fs_1.default.rmSync(bundleDir, { recursive: true, force: true });
|
|
125
107
|
}
|
|
126
108
|
catch (_error) {
|
|
127
|
-
// Ignore
|
|
109
|
+
// Ignore cleanup errors
|
|
128
110
|
}
|
|
129
|
-
}
|
|
130
|
-
analyzeFile(entryFile);
|
|
131
|
-
return files;
|
|
132
|
-
}
|
|
133
|
-
resolveModulePaths(moduleName, currentDir) {
|
|
134
|
-
const parts = moduleName.split('.');
|
|
135
|
-
const baseName = parts[0];
|
|
136
|
-
const subPath = parts.length > 1 ? path_1.default.join(...parts) : baseName;
|
|
137
|
-
return [
|
|
138
|
-
path_1.default.join(currentDir, `${baseName}.py`),
|
|
139
|
-
path_1.default.join(currentDir, baseName, '__init__.py'),
|
|
140
|
-
path_1.default.join(currentDir, `${subPath}.py`),
|
|
141
|
-
path_1.default.join(this.builder.projectDir, `${baseName}.py`),
|
|
142
|
-
path_1.default.join(this.builder.projectDir, baseName, '__init__.py'),
|
|
143
|
-
path_1.default.join(this.builder.projectDir, `${subPath}.py`),
|
|
144
|
-
path_1.default.join(this.builder.projectDir, subPath + '.py'),
|
|
145
|
-
path_1.default.join(this.builder.projectDir, subPath, '__init__.py'),
|
|
146
|
-
];
|
|
147
|
-
}
|
|
148
|
-
normalizeStepPath(step, normalizePythonModulePath) {
|
|
149
|
-
let normalizedStepPath = step.filePath.replace(/[.]step.py$/, '_step.py').replace(`${this.builder.projectDir}/`, '');
|
|
150
|
-
const pathParts = normalizedStepPath.split(path_1.default.sep).map((part) => part
|
|
151
|
-
.replace(/[^a-zA-Z0-9._]/g, '_') // Replace any non-alphanumeric characters (except dots) with underscores
|
|
152
|
-
.replace(/^_/, ''));
|
|
153
|
-
normalizedStepPath = normalizePythonModulePath ? pathParts.join('.') : '/' + pathParts.join(path_1.default.sep);
|
|
154
|
-
return normalizedStepPath;
|
|
111
|
+
}
|
|
155
112
|
}
|
|
156
113
|
createRouterTemplate(steps) {
|
|
157
114
|
const imports = steps
|
|
158
|
-
.map((step, index) => {
|
|
159
|
-
const moduleName = this.getModuleName(step);
|
|
160
|
-
return `route${index}_module = importlib.import_module('${moduleName}')`;
|
|
161
|
-
})
|
|
115
|
+
.map((step, index) => `from ${this.getModuleName(step)} import handler as route${index}_module`)
|
|
162
116
|
.join('\n');
|
|
163
117
|
const routerPaths = steps
|
|
164
118
|
.map((step, index) => {
|
|
@@ -170,10 +124,11 @@ class PythonBuilder {
|
|
|
170
124
|
return fs_1.default
|
|
171
125
|
.readFileSync(path_1.default.join(__dirname, 'router_template.py'), 'utf-8')
|
|
172
126
|
.replace('# {{imports}}', imports)
|
|
173
|
-
.replace('# {{router paths}}', routerPaths);
|
|
127
|
+
.replace(' # {{router paths}}', routerPaths);
|
|
174
128
|
}
|
|
175
129
|
getModuleName(step) {
|
|
176
|
-
return
|
|
130
|
+
// return step path
|
|
131
|
+
return step.filePath.replace(this.builder.projectDir, '').substring(1).replace(/\.py$/, '').replace(/\//g, '.');
|
|
177
132
|
}
|
|
178
133
|
}
|
|
179
134
|
exports.PythonBuilder = PythonBuilder;
|
package/dist/cjs/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cjs/cloud/build/builders/python/python-data/__tests__/convert-import-path.test.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const convert_import_path_1 = require("../convert-import-path");
|
|
4
|
+
describe('convertImportToPath', () => {
|
|
5
|
+
test('converts regular module imports', () => {
|
|
6
|
+
// Regular module imports
|
|
7
|
+
expect((0, convert_import_path_1.convertImportToPath)('mymodule')).toBe('mymodule');
|
|
8
|
+
expect((0, convert_import_path_1.convertImportToPath)('mymodule.submodule')).toBe('mymodule/submodule');
|
|
9
|
+
expect((0, convert_import_path_1.convertImportToPath)('package.module.submodule')).toBe('package/module/submodule');
|
|
10
|
+
});
|
|
11
|
+
test('converts relative imports', () => {
|
|
12
|
+
// Relative imports
|
|
13
|
+
expect((0, convert_import_path_1.convertImportToPath)('.module')).toBe('module');
|
|
14
|
+
expect((0, convert_import_path_1.convertImportToPath)('..module')).toBe('../module');
|
|
15
|
+
expect((0, convert_import_path_1.convertImportToPath)('...module')).toBe('../../module');
|
|
16
|
+
expect((0, convert_import_path_1.convertImportToPath)('..utils.database')).toBe('../utils/database');
|
|
17
|
+
});
|
|
18
|
+
test('handles edge cases', () => {
|
|
19
|
+
// Edge cases
|
|
20
|
+
expect((0, convert_import_path_1.convertImportToPath)('.')).toBe('.');
|
|
21
|
+
expect((0, convert_import_path_1.convertImportToPath)('..')).toBe('..');
|
|
22
|
+
expect((0, convert_import_path_1.convertImportToPath)('...')).toBe('../..');
|
|
23
|
+
});
|
|
24
|
+
});
|
package/dist/cjs/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cjs/cloud/build/builders/python/python-data/__tests__/extract-python-data.test.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const glob_1 = require("glob");
|
|
8
|
+
const read_requirements_1 = require("../read-requirements");
|
|
9
|
+
const extract_python_data_1 = require("../extract-python-data");
|
|
10
|
+
const python_errors_1 = require("../python-errors");
|
|
11
|
+
const mockPackageDescriber = (name) => ({ name, importName: name });
|
|
12
|
+
describe('extractPythonData', () => {
|
|
13
|
+
test('extracts python data correctly', () => {
|
|
14
|
+
const rootDir = path_1.default.join(__dirname, './examples/example-1');
|
|
15
|
+
const requirements = (0, read_requirements_1.readRequirements)(path_1.default.join(rootDir, 'requirements.txt'), mockPackageDescriber);
|
|
16
|
+
const steps = (0, glob_1.globSync)('**/*_step.py', { absolute: false, cwd: path_1.default.join(rootDir, 'steps') });
|
|
17
|
+
for (const file of steps) {
|
|
18
|
+
const result = (0, extract_python_data_1.extractPythonData)(rootDir, `/steps/${file}`, requirements);
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
standardLibDependencies: ['typing', 'enum'],
|
|
21
|
+
externalDependencies: { httpx: 'httpx>=0.28.1', pydantic: 'pydantic>=2.6.1' },
|
|
22
|
+
files: ['/steps/api_step.py', '/src/__init__.py', '/src/pet_store.py', '/src/types.py'],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
test('extracts python data correctly with invalid dependency', () => {
|
|
27
|
+
const rootDir = path_1.default.join(__dirname, './examples/invalid-dependency');
|
|
28
|
+
const requirements = (0, read_requirements_1.readRequirements)(path_1.default.join(rootDir, 'requirements.txt'), mockPackageDescriber);
|
|
29
|
+
expect(() => (0, extract_python_data_1.extractPythonData)(rootDir, `/steps/api_step.py`, requirements)).toThrow(python_errors_1.PythonImportNotFoundError);
|
|
30
|
+
});
|
|
31
|
+
test('extracts python data correctly with compilation error', () => {
|
|
32
|
+
const rootDir = path_1.default.join(__dirname, './examples/compilation-error');
|
|
33
|
+
const requirements = (0, read_requirements_1.readRequirements)(path_1.default.join(rootDir, 'requirements.txt'), mockPackageDescriber);
|
|
34
|
+
expect(() => (0, extract_python_data_1.extractPythonData)(rootDir, `/steps/api_step.py`, requirements)).toThrow(new python_errors_1.PythonError("Compilation error: no viable alternative at input ':' at line 3:10 in /steps/api_step.py", '/steps/api_step.py'));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const get_dependencies_from_file_1 = require("../get-dependencies-from-file");
|
|
4
|
+
describe('getDependenciesFromFile', () => {
|
|
5
|
+
test('categorizes dependencies correctly', () => {
|
|
6
|
+
const pythonCode = `
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
import requests
|
|
11
|
+
from .local_module import something
|
|
12
|
+
from ..parent_module import other
|
|
13
|
+
`;
|
|
14
|
+
const requirements = {
|
|
15
|
+
requests: 'requests==2.25.1',
|
|
16
|
+
};
|
|
17
|
+
const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
|
|
18
|
+
expect(Array.from(deps.standardLibDependencies)).toEqual(['os', 'sys', 'collections']);
|
|
19
|
+
expect(Array.from(deps.externalDependencies)).toEqual(['requests']);
|
|
20
|
+
expect(Array.from(deps.projectDependencies)).toEqual(['.local_module', '..parent_module']);
|
|
21
|
+
});
|
|
22
|
+
test('handles relative imports correctly', () => {
|
|
23
|
+
const pythonCode = 'from ...utils.database import connect';
|
|
24
|
+
const requirements = {};
|
|
25
|
+
const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
|
|
26
|
+
expect(Array.from(deps.projectDependencies)).toEqual(['...utils.database']);
|
|
27
|
+
});
|
|
28
|
+
test('handles import statements with requirements dep', () => {
|
|
29
|
+
const pythonCode = `
|
|
30
|
+
import json
|
|
31
|
+
import numpy
|
|
32
|
+
import mypackage.submodule
|
|
33
|
+
`;
|
|
34
|
+
const requirements = {
|
|
35
|
+
numpy: 'numpy==1.21.0',
|
|
36
|
+
};
|
|
37
|
+
const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
|
|
38
|
+
expect(Array.from(deps.standardLibDependencies)).toEqual(['json']);
|
|
39
|
+
expect(Array.from(deps.externalDependencies)).toEqual(['numpy']);
|
|
40
|
+
expect(Array.from(deps.projectDependencies)).toEqual(['mypackage.submodule']);
|
|
41
|
+
});
|
|
42
|
+
test('handles import statements with requirements dep and alias', () => {
|
|
43
|
+
const pythonCode = `
|
|
44
|
+
import json
|
|
45
|
+
import numpy as np
|
|
46
|
+
import mypackage.submodule
|
|
47
|
+
`;
|
|
48
|
+
const requirements = {
|
|
49
|
+
numpy: 'numpy==1.21.0',
|
|
50
|
+
};
|
|
51
|
+
const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
|
|
52
|
+
expect(Array.from(deps.standardLibDependencies)).toEqual(['json']);
|
|
53
|
+
expect(Array.from(deps.externalDependencies)).toEqual(['numpy']);
|
|
54
|
+
expect(Array.from(deps.projectDependencies)).toEqual(['mypackage.submodule']);
|
|
55
|
+
});
|
|
56
|
+
test('handles empty file', () => {
|
|
57
|
+
const pythonCode = '';
|
|
58
|
+
const requirements = {};
|
|
59
|
+
const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
|
|
60
|
+
expect(deps.standardLibDependencies.size).toBe(0);
|
|
61
|
+
expect(deps.externalDependencies.size).toBe(0);
|
|
62
|
+
expect(deps.projectDependencies.size).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
test('handles unknown external dependencies as project dependencies', () => {
|
|
65
|
+
const pythonCode = 'import unknown_package';
|
|
66
|
+
const requirements = {};
|
|
67
|
+
const deps = (0, get_dependencies_from_file_1.getDependenciesFromFile)(pythonCode, 'test.py', requirements);
|
|
68
|
+
expect(Array.from(deps.projectDependencies)).toEqual(['unknown_package']);
|
|
69
|
+
});
|
|
70
|
+
});
|
package/dist/cjs/cloud/build/builders/python/python-data/__tests__/read-requirements.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
const read_requirements_1 = require("../read-requirements");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = require("os");
|
|
10
|
+
describe('readRequirements', () => {
|
|
11
|
+
let tempFilePath;
|
|
12
|
+
const mockPackageDescriber = jest.fn((name) => ({ name, importName: name }));
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Create a temporary file path
|
|
15
|
+
tempFilePath = path_1.default.join((0, os_1.tmpdir)(), `requirements-${Date.now()}.txt`);
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
// Clean up temporary file if it exists
|
|
19
|
+
if (fs_1.default.existsSync(tempFilePath)) {
|
|
20
|
+
fs_1.default.unlinkSync(tempFilePath);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
test('reads basic requirements correctly', () => {
|
|
24
|
+
const content = `requests==2.25.1
|
|
25
|
+
numpy>=1.20.0
|
|
26
|
+
flask~=2.0`;
|
|
27
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
28
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
29
|
+
expect(Object.keys(requirements)).toHaveLength(3);
|
|
30
|
+
expect(requirements.requests).toBe('requests==2.25.1');
|
|
31
|
+
expect(requirements.numpy).toBe('numpy>=1.20.0');
|
|
32
|
+
expect(requirements.flask).toBe('flask~=2.0');
|
|
33
|
+
});
|
|
34
|
+
test('skips comments and empty lines', () => {
|
|
35
|
+
const content = `# This is a comment
|
|
36
|
+
requests==2.25.1
|
|
37
|
+
|
|
38
|
+
# Another comment
|
|
39
|
+
numpy>=1.20.0
|
|
40
|
+
|
|
41
|
+
flask~=2.0`;
|
|
42
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
43
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
44
|
+
expect(Object.keys(requirements)).toHaveLength(3);
|
|
45
|
+
expect(requirements.requests).toBe('requests==2.25.1');
|
|
46
|
+
expect(requirements.numpy).toBe('numpy>=1.20.0');
|
|
47
|
+
expect(requirements.flask).toBe('flask~=2.0');
|
|
48
|
+
});
|
|
49
|
+
test('handles complex package names and version specifiers', () => {
|
|
50
|
+
const content = `scikit-learn==1.0.2
|
|
51
|
+
python-dateutil==1.4
|
|
52
|
+
Django<4.0.0,>=3.2.0`;
|
|
53
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
54
|
+
const describer = jest.fn((name) => {
|
|
55
|
+
// mocking a describer resolution for these packages
|
|
56
|
+
// we're going to implement it separately
|
|
57
|
+
const map = {
|
|
58
|
+
'scikit-learn': 'sklearn',
|
|
59
|
+
'python-dateutil': 'dateutil',
|
|
60
|
+
Django: 'django',
|
|
61
|
+
};
|
|
62
|
+
return map[name] ? { name, importName: map[name] } : { name, importName: name };
|
|
63
|
+
});
|
|
64
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, describer);
|
|
65
|
+
expect(Object.keys(requirements)).toEqual(['sklearn', 'dateutil', 'django']);
|
|
66
|
+
expect(requirements['sklearn']).toBe('scikit-learn==1.0.2');
|
|
67
|
+
expect(requirements['dateutil']).toBe('python-dateutil==1.4');
|
|
68
|
+
expect(requirements['django']).toBe('Django<4.0.0,>=3.2.0');
|
|
69
|
+
});
|
|
70
|
+
test('handles requirements with extra dependencies', () => {
|
|
71
|
+
const content = `requests[security]==2.25.1
|
|
72
|
+
flask[async]==2.0.1`;
|
|
73
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
74
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
75
|
+
expect(Object.keys(requirements)).toHaveLength(2);
|
|
76
|
+
expect(requirements['requests']).toBe('requests[security]==2.25.1');
|
|
77
|
+
expect(requirements['flask']).toBe('flask[async]==2.0.1');
|
|
78
|
+
});
|
|
79
|
+
test('handles empty requirements file', () => {
|
|
80
|
+
const content = `# Only comments
|
|
81
|
+
|
|
82
|
+
# And empty lines`;
|
|
83
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
84
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
85
|
+
expect(Object.keys(requirements)).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
test('handles malformed lines gracefully', () => {
|
|
88
|
+
const content = `requests==2.25.1
|
|
89
|
+
# Valid package
|
|
90
|
+
numpy>=1.20.0
|
|
91
|
+
==invalid-version-only`;
|
|
92
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
93
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
94
|
+
expect(Object.keys(requirements)).toHaveLength(2);
|
|
95
|
+
expect(requirements.requests).toBe('requests==2.25.1');
|
|
96
|
+
expect(requirements.numpy).toBe('numpy>=1.20.0');
|
|
97
|
+
});
|
|
98
|
+
test('handles mixed line endings', () => {
|
|
99
|
+
const content = 'requests==2.25.1\r\nnumpy>=1.20.0\nflask~=2.0';
|
|
100
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
101
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
102
|
+
expect(Object.keys(requirements)).toHaveLength(3);
|
|
103
|
+
expect(requirements.requests).toBe('requests==2.25.1');
|
|
104
|
+
expect(requirements.numpy).toBe('numpy>=1.20.0');
|
|
105
|
+
expect(requirements.flask).toBe('flask~=2.0');
|
|
106
|
+
});
|
|
107
|
+
test('handles requirements with no versions', () => {
|
|
108
|
+
const content = ['requests', 'numpy', 'flask'].join('\n');
|
|
109
|
+
fs_1.default.writeFileSync(tempFilePath, content);
|
|
110
|
+
const requirements = (0, read_requirements_1.readRequirements)(tempFilePath, mockPackageDescriber);
|
|
111
|
+
expect(Object.keys(requirements)).toHaveLength(3);
|
|
112
|
+
expect(requirements.requests).toBe('requests');
|
|
113
|
+
expect(requirements.numpy).toBe('numpy');
|
|
114
|
+
expect(requirements.flask).toBe('flask');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const resolve_dep_name_1 = require("../resolve-dep-name");
|
|
8
|
+
const python_errors_1 = require("../python-errors");
|
|
9
|
+
const sitePackagesDir = path_1.default.join(__dirname, 'site-packages');
|
|
10
|
+
describe('resolveDepName', () => {
|
|
11
|
+
test('resolves dependency names correctly', () => {
|
|
12
|
+
const depNames = ['httpx'];
|
|
13
|
+
const result = (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesDir);
|
|
14
|
+
expect(result).toEqual([['httpx', 'httpx']]);
|
|
15
|
+
});
|
|
16
|
+
test('resolves multiple dependency names correctly', () => {
|
|
17
|
+
const depNames = ['httpx', 'scikit-learn', 'opencv-python'];
|
|
18
|
+
const result = (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesDir);
|
|
19
|
+
expect(result).toEqual([
|
|
20
|
+
['httpx', 'httpx'],
|
|
21
|
+
['scikit-learn', 'sklearn'],
|
|
22
|
+
['opencv-python', 'cv2'],
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
test('should throw an error if the dependency is not found', () => {
|
|
26
|
+
const depNames = ['httpx', 'scikit-learn', 'opencv-python', 'pydantic'];
|
|
27
|
+
expect(() => (0, resolve_dep_name_1.resolveDepNames)(depNames, sitePackagesDir)).toThrow(new python_errors_1.PythonError('Could not find dependency name in site-packages: pydantic', 'pydantic'));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const STANDARD_LIB_MODULES: Set<string>;
|