motia 0.0.1 → 0.0.2

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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Motia Snap
2
+
3
+ Motia Snap is a command-line interface tool to get your Motia project setup.
4
+
5
+ ## Installation
6
+
7
+ To install the CLI globally, run:
8
+
9
+ ```sh
10
+ npm install -g @motiadev/snap
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Below are the available commands for the Motia Snap:
16
+
17
+ ### `motia init`
18
+
19
+ Initializes a new Motia project in the current directory.
20
+
21
+ ```sh
22
+ motia init
23
+ ```
24
+
25
+ ### `motia build`
26
+
27
+ Builds a lock file based on your current project setup which is then used by the Motia ecosystem.
28
+
29
+ ```sh
30
+ motia build
31
+ ```
32
+
33
+ ### `motia dev`
34
+
35
+ Initiates a dev environment for your project allowing you to use Motia Workbench (visualization tool for your flows).
36
+
37
+ ```sh
38
+ motia dev
39
+ ```
40
+
41
+ ## Help
42
+
43
+ For more information on a specific command, you can use the `--help` flag:
44
+
45
+ ```sh
46
+ motia <command> --help
47
+ ```
48
+
49
+ ## License
50
+
51
+ This project is licensed under the MIT License.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const path_1 = __importDefault(require("path"));
9
+ const defaultPort = 3000;
10
+ require('dotenv/config');
11
+ require('ts-node').register({
12
+ transpileOnly: true,
13
+ compilerOptions: { module: 'commonjs' },
14
+ });
15
+ commander_1.program
16
+ .command('dev')
17
+ .description('Start the development server')
18
+ .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)
19
+ .option('-d, --debug', 'Enable debug logging')
20
+ .action(async (arg) => {
21
+ if (arg.debug) {
22
+ console.log('🔍 Debug logging enabled');
23
+ process.env.LOG_LEVEL = 'debug';
24
+ }
25
+ const port = arg.port ? parseInt(arg.port) : defaultPort;
26
+ const { dev } = require('./dev');
27
+ await dev(port);
28
+ });
29
+ commander_1.program
30
+ .command('get-config')
31
+ .description('Get the generated config for your project')
32
+ .option('-o, --output <port>', 'Path to write the generated config')
33
+ .action(async (arg) => {
34
+ const { generateLockedData } = require('./src/generate/locked-data');
35
+ const lockedData = await generateLockedData(path_1.default.join(process.cwd()));
36
+ if (arg.output) {
37
+ const fs = require('fs');
38
+ fs.writeFileSync(path_1.default.join(arg.output, '.motia.generated.json'), JSON.stringify(lockedData, null, 2));
39
+ console.log(`📄 Wrote locked data to ${arg.output}`);
40
+ return;
41
+ }
42
+ console.log(JSON.stringify(lockedData, null, 2));
43
+ });
44
+ commander_1.program.parse(process.argv);
@@ -0,0 +1,37 @@
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.dev = void 0;
7
+ const core_1 = require("@motiadev/core");
8
+ const generate_locked_data_1 = require("./generate-locked-data");
9
+ const path_1 = __importDefault(require("path"));
10
+ require('ts-node').register({
11
+ transpileOnly: true,
12
+ compilerOptions: { module: 'commonjs' },
13
+ });
14
+ const dev = async (port) => {
15
+ const lockedData = await (0, generate_locked_data_1.generateLockedData)(process.cwd());
16
+ const steps = [...lockedData.steps.active, ...lockedData.steps.dev];
17
+ const eventManager = (0, core_1.createEventManager)();
18
+ const state = (0, core_1.createStateAdapter)({
19
+ adapter: 'default',
20
+ filePath: path_1.default.join(process.cwd(), '.motia'),
21
+ });
22
+ await state.init();
23
+ const { app, server } = await (0, core_1.createServer)({ steps, state, flows: lockedData.flows, eventManager });
24
+ (0, core_1.createStepHandlers)(steps, eventManager, state);
25
+ server.listen(port);
26
+ console.log('🚀 Server ready and listening on port', port);
27
+ console.log(`🔗 Open http://localhost:${port}/ to open workbench 🛠️`);
28
+ const { applyMiddleware } = require('@motiadev/workbench/dist/middleware');
29
+ await applyMiddleware(app);
30
+ // 6) Gracefully shut down on SIGTERM
31
+ process.on('SIGTERM', async () => {
32
+ core_1.globalLogger.info('🛑 Shutting down...');
33
+ server.close();
34
+ process.exit(0);
35
+ });
36
+ };
37
+ exports.dev = dev;
@@ -0,0 +1,123 @@
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.generateLockedData = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const yaml_1 = __importDefault(require("yaml"));
11
+ const core_1 = require("@motiadev/core");
12
+ const version = `${(0, crypto_1.randomUUID)()}:${Math.floor(Date.now() / 1000)}`;
13
+ const baseFlowRegex = new RegExp(/flows\"?\s?.*\s*\[([^\]]+)\]/);
14
+ // Helper function to read config.yml
15
+ const readConfig = (configPath) => {
16
+ if (!fs_1.default.existsSync(configPath)) {
17
+ console.warn(`Config file not found at ${configPath}`);
18
+ return {};
19
+ }
20
+ const configContent = fs_1.default.readFileSync(configPath, 'utf-8');
21
+ return yaml_1.default.parse(configContent);
22
+ };
23
+ const extractStepConfig = (filePath) => {
24
+ const isRb = filePath.endsWith('.rb');
25
+ const isPython = filePath.endsWith('.py');
26
+ const isNode = filePath.endsWith('.js') || filePath.endsWith('.ts');
27
+ if (isRb) {
28
+ return (0, core_1.getRubyConfig)(filePath);
29
+ }
30
+ if (isPython) {
31
+ return (0, core_1.getPythonConfig)(filePath);
32
+ }
33
+ if (isNode) {
34
+ return (0, core_1.getNodeFileConfig)(filePath);
35
+ }
36
+ };
37
+ // Helper function to recursively collect flow data
38
+ const collectFlows = async (baseDir) => {
39
+ const folderItems = fs_1.default.readdirSync(baseDir, { withFileTypes: true });
40
+ let steps = [];
41
+ for (const item of folderItems) {
42
+ const itemPath = path_1.default.join(baseDir, item.name);
43
+ if (item.isDirectory()) {
44
+ steps = steps.concat(await collectFlows(itemPath));
45
+ }
46
+ else if (!!item.name.match(/\.step\.(ts|js|py|rb)$/)) {
47
+ const fileContent = fs_1.default.readFileSync(itemPath, 'utf-8');
48
+ const flowMatch = fileContent.match(baseFlowRegex);
49
+ const config = await extractStepConfig(itemPath);
50
+ if (!config) {
51
+ console.warn(`No config found in step ${itemPath}, step skipped`);
52
+ continue;
53
+ }
54
+ if (flowMatch) {
55
+ steps.push({
56
+ // TODO: when the lock file comes back, this needs to be updated to use a relative path
57
+ filePath: itemPath,
58
+ version,
59
+ config,
60
+ });
61
+ }
62
+ }
63
+ }
64
+ return steps;
65
+ };
66
+ const generateLockedData = async (projectDir) => {
67
+ const configPath = path_1.default.join(projectDir, 'config.yml');
68
+ // NOTE: right now for performance and simplicity let's enforce a folder, but we might want to remove this and scan the entire current directory
69
+ const stepsPath = path_1.default.join(projectDir, 'steps');
70
+ try {
71
+ const config = readConfig(configPath);
72
+ // Collect steps data from steps folder
73
+ const sourceSteps = await collectFlows(stepsPath);
74
+ const { flows, steps } = sourceSteps.reduce((acc, step) => {
75
+ const nextSteps = { ...acc.steps };
76
+ // NOTE: identifies a noop step
77
+ if (step.config.virtualEmits) {
78
+ nextSteps.dev.push(step);
79
+ }
80
+ else {
81
+ nextSteps.active.push(step);
82
+ }
83
+ const nextFlows = { ...acc.flows };
84
+ step.config.flows.forEach((flowName) => {
85
+ if (!nextFlows[flowName]) {
86
+ nextFlows[flowName] = {
87
+ name: flowName,
88
+ // TODO: how are we going to extract descriptions?
89
+ description: '',
90
+ steps: [],
91
+ };
92
+ }
93
+ nextFlows[flowName].steps.push(step);
94
+ });
95
+ return {
96
+ ...acc,
97
+ steps: nextSteps,
98
+ flows: nextFlows,
99
+ };
100
+ }, {
101
+ flows: {},
102
+ steps: {
103
+ active: [],
104
+ dev: [],
105
+ },
106
+ });
107
+ // Prepare the lock file data
108
+ const lockedData = {
109
+ baseDir: projectDir,
110
+ state: config?.state || {},
111
+ steps,
112
+ flows,
113
+ };
114
+ console.log('Project config loaded');
115
+ // TODO: for now this will return the locked data as an object, in the future we will write it to a lock file
116
+ return lockedData;
117
+ }
118
+ catch (error) {
119
+ console.error('Error generating locked data:', error);
120
+ throw Error('Failed to parse the project, generating locked data step failed');
121
+ }
122
+ };
123
+ exports.generateLockedData = generateLockedData;
package/package.json CHANGED
@@ -1,11 +1,24 @@
1
1
  {
2
2
  "name": "motia",
3
- "version": "0.0.1",
4
- "main": "index.js",
5
- "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "version": "0.0.2",
4
+ "bin": {
5
+ "motia": "dist/src/cli.js"
6
+ },
7
+ "dependencies": {
8
+ "dotenv": "^16.4.7",
9
+ "commander": "^13.0.0",
10
+ "ts-node": "^10.9.2",
11
+ "yaml": "^2.7.0",
12
+ "@motiadev/core": "0.0.10",
13
+ "@motiadev/workbench": "0.0.9"
7
14
  },
8
- "author": "",
9
- "license": "ISC",
10
- "description": ""
11
- }
15
+ "devDependencies": {
16
+ "@types/jest": "^29.5.14",
17
+ "jest": "^29.7.0",
18
+ "ts-jest": "^29.2.5",
19
+ "typescript": "^5.7.2"
20
+ },
21
+ "scripts": {
22
+ "build": "rm -rf dist && tsc"
23
+ }
24
+ }