motia 0.0.1 → 0.0.3
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 +51 -0
- package/dist/src/cli.js +44 -0
- package/dist/src/dev.js +37 -0
- package/dist/src/generate-locked-data.js +123 -0
- package/package.json +22 -8
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.
|
package/dist/src/cli.js
ADDED
|
@@ -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);
|
package/dist/src/dev.js
ADDED
|
@@ -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,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "motia",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"bin": {
|
|
6
|
+
"motia": "dist/src/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"dotenv": "^16.4.7",
|
|
10
|
+
"commander": "^13.0.0",
|
|
11
|
+
"ts-node": "^10.9.2",
|
|
12
|
+
"yaml": "^2.7.0",
|
|
13
|
+
"@motiadev/core": "0.0.10",
|
|
14
|
+
"@motiadev/workbench": "0.0.9"
|
|
7
15
|
},
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/jest": "^29.5.14",
|
|
18
|
+
"jest": "^29.7.0",
|
|
19
|
+
"ts-jest": "^29.2.5",
|
|
20
|
+
"typescript": "^5.7.2"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rm -rf dist && tsc"
|
|
24
|
+
}
|
|
25
|
+
}
|