motia 0.0.19 → 0.0.21
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/src/dev-watchers.js +61 -0
- package/dist/src/dev.js +9 -6
- package/dist/src/generate-locked-data.js +16 -75
- package/dist/src/watcher.js +94 -0
- package/package.json +4 -3
|
@@ -0,0 +1,61 @@
|
|
|
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.createDevWatchers = void 0;
|
|
7
|
+
const core_1 = require("@motiadev/core");
|
|
8
|
+
const watcher_1 = require("./watcher");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const createDevWatchers = (lockedData, server, eventHandler, cronManager) => {
|
|
11
|
+
const stepDir = path_1.default.join(process.cwd(), 'steps');
|
|
12
|
+
const watcher = new watcher_1.Watcher(stepDir, lockedData);
|
|
13
|
+
lockedData.on('flow-created', (flowName) => {
|
|
14
|
+
server.socketServer.emit('flow-created', flowName);
|
|
15
|
+
});
|
|
16
|
+
lockedData.on('flow-removed', (flowName) => {
|
|
17
|
+
server.socketServer.emit('flow-removed', flowName);
|
|
18
|
+
});
|
|
19
|
+
lockedData.on('flow-updated', (flowName) => {
|
|
20
|
+
server.socketServer.emit('flow-updated', flowName);
|
|
21
|
+
});
|
|
22
|
+
watcher.onStepChange((oldStep, newStep) => {
|
|
23
|
+
if ((0, core_1.isApiStep)(oldStep))
|
|
24
|
+
server.removeRoute(oldStep);
|
|
25
|
+
if ((0, core_1.isCronStep)(oldStep))
|
|
26
|
+
cronManager.removeCronJob(oldStep);
|
|
27
|
+
if ((0, core_1.isEventStep)(oldStep))
|
|
28
|
+
eventHandler.removeHandler(oldStep);
|
|
29
|
+
const isUpdated = lockedData.updateStep(oldStep, newStep);
|
|
30
|
+
if (isUpdated) {
|
|
31
|
+
if ((0, core_1.isCronStep)(newStep))
|
|
32
|
+
cronManager.createCronJob(newStep);
|
|
33
|
+
if ((0, core_1.isEventStep)(newStep))
|
|
34
|
+
eventHandler.createHandler(newStep);
|
|
35
|
+
if ((0, core_1.isApiStep)(newStep))
|
|
36
|
+
server.addRoute(newStep);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
watcher.onStepCreate((step) => {
|
|
40
|
+
const isCreated = lockedData.createStep(step);
|
|
41
|
+
if (isCreated) {
|
|
42
|
+
if ((0, core_1.isApiStep)(step))
|
|
43
|
+
server.addRoute(step);
|
|
44
|
+
if ((0, core_1.isEventStep)(step))
|
|
45
|
+
eventHandler.createHandler(step);
|
|
46
|
+
if ((0, core_1.isCronStep)(step))
|
|
47
|
+
cronManager.createCronJob(step);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
watcher.onStepDelete((step) => {
|
|
51
|
+
if ((0, core_1.isApiStep)(step))
|
|
52
|
+
server.removeRoute(step);
|
|
53
|
+
if ((0, core_1.isEventStep)(step))
|
|
54
|
+
eventHandler.removeHandler(step);
|
|
55
|
+
if ((0, core_1.isCronStep)(step))
|
|
56
|
+
cronManager.removeCronJob(step);
|
|
57
|
+
lockedData.deleteStep(step);
|
|
58
|
+
});
|
|
59
|
+
return watcher;
|
|
60
|
+
};
|
|
61
|
+
exports.createDevWatchers = createDevWatchers;
|
package/dist/src/dev.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.dev = void 0;
|
|
|
7
7
|
const core_1 = require("@motiadev/core");
|
|
8
8
|
const generate_locked_data_1 = require("./generate-locked-data");
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const dev_watchers_1 = require("./dev-watchers");
|
|
10
11
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
12
|
require('ts-node').register({
|
|
12
13
|
transpileOnly: true,
|
|
@@ -14,25 +15,27 @@ require('ts-node').register({
|
|
|
14
15
|
});
|
|
15
16
|
const dev = async (port) => {
|
|
16
17
|
const lockedData = await (0, generate_locked_data_1.generateLockedData)(process.cwd());
|
|
17
|
-
const steps = [...lockedData.steps.active, ...lockedData.steps.dev];
|
|
18
18
|
const eventManager = (0, core_1.createEventManager)();
|
|
19
19
|
const state = (0, core_1.createStateAdapter)({
|
|
20
20
|
adapter: 'default',
|
|
21
21
|
filePath: path_1.default.join(process.cwd(), '.motia'),
|
|
22
22
|
});
|
|
23
23
|
await state.init();
|
|
24
|
-
const
|
|
25
|
-
(0, core_1.createStepHandlers)(
|
|
26
|
-
|
|
24
|
+
const motiaServer = await (0, core_1.createServer)(lockedData, eventManager, state);
|
|
25
|
+
const motiaEventManager = (0, core_1.createStepHandlers)(lockedData, eventManager, state);
|
|
26
|
+
const watcher = (0, dev_watchers_1.createDevWatchers)(lockedData, motiaServer, motiaEventManager, motiaServer.cronManager);
|
|
27
|
+
watcher.init();
|
|
28
|
+
motiaServer.server.listen(port);
|
|
27
29
|
console.log('🚀 Server ready and listening on port', port);
|
|
28
30
|
console.log(`🔗 Open http://localhost:${port}/ to open workbench 🛠️`);
|
|
29
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
32
|
const { applyMiddleware } = require('@motiadev/workbench/dist/middleware');
|
|
31
|
-
await applyMiddleware(app);
|
|
33
|
+
await applyMiddleware(motiaServer.app);
|
|
32
34
|
// 6) Gracefully shut down on SIGTERM
|
|
33
35
|
process.on('SIGTERM', async () => {
|
|
34
36
|
core_1.globalLogger.info('🛑 Shutting down...');
|
|
35
|
-
server.close();
|
|
37
|
+
motiaServer.server.close();
|
|
38
|
+
await watcher.stop();
|
|
36
39
|
process.exit(0);
|
|
37
40
|
});
|
|
38
41
|
};
|
|
@@ -3,12 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateLockedData = void 0;
|
|
6
|
+
exports.generateLockedData = exports.readConfig = void 0;
|
|
7
|
+
const core_1 = require("@motiadev/core");
|
|
7
8
|
const crypto_1 = require("crypto");
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
10
11
|
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
-
const core_1 = require("@motiadev/core");
|
|
12
12
|
const version = `${(0, crypto_1.randomUUID)()}:${Math.floor(Date.now() / 1000)}`;
|
|
13
13
|
const baseFlowRegex = new RegExp(/flows"?\s?.*\s*\[([^\]]+)\]/);
|
|
14
14
|
// Helper function to read config.yml
|
|
@@ -21,99 +21,40 @@ const readConfig = (configPath) => {
|
|
|
21
21
|
const configContent = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
22
22
|
return yaml_1.default.parse(configContent);
|
|
23
23
|
};
|
|
24
|
-
|
|
25
|
-
const isRb = filePath.endsWith('.rb');
|
|
26
|
-
const isPython = filePath.endsWith('.py');
|
|
27
|
-
const isNode = filePath.endsWith('.js') || filePath.endsWith('.ts');
|
|
28
|
-
if (isRb) {
|
|
29
|
-
return (0, core_1.getRubyConfig)(filePath);
|
|
30
|
-
}
|
|
31
|
-
if (isPython) {
|
|
32
|
-
return (0, core_1.getPythonConfig)(filePath);
|
|
33
|
-
}
|
|
34
|
-
if (isNode) {
|
|
35
|
-
return (0, core_1.getNodeFileConfig)(filePath);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
24
|
+
exports.readConfig = readConfig;
|
|
38
25
|
// Helper function to recursively collect flow data
|
|
39
26
|
const collectFlows = async (baseDir) => {
|
|
40
27
|
const folderItems = fs_1.default.readdirSync(baseDir, { withFileTypes: true });
|
|
41
28
|
let steps = [];
|
|
42
29
|
for (const item of folderItems) {
|
|
43
|
-
const
|
|
30
|
+
const filePath = path_1.default.join(baseDir, item.name);
|
|
44
31
|
if (item.isDirectory()) {
|
|
45
|
-
steps = steps.concat(await collectFlows(
|
|
32
|
+
steps = steps.concat(await collectFlows(filePath));
|
|
46
33
|
}
|
|
47
34
|
else if (item.name.match(/\.step\.(ts|js|py|rb)$/)) {
|
|
48
|
-
const fileContent = fs_1.default.readFileSync(
|
|
35
|
+
const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
49
36
|
const flowMatch = fileContent.match(baseFlowRegex);
|
|
50
|
-
const config = await
|
|
37
|
+
const config = await (0, core_1.getStepConfig)(filePath);
|
|
51
38
|
if (!config) {
|
|
52
|
-
console.warn(`No config found in step ${
|
|
39
|
+
console.warn(`No config found in step ${filePath}, step skipped`);
|
|
53
40
|
continue;
|
|
54
41
|
}
|
|
55
42
|
if (flowMatch) {
|
|
56
|
-
steps.push({
|
|
57
|
-
// TODO: when the lock file comes back, this needs to be updated to use a relative path
|
|
58
|
-
filePath: itemPath,
|
|
59
|
-
version,
|
|
60
|
-
config,
|
|
61
|
-
});
|
|
43
|
+
steps.push({ filePath, version, config });
|
|
62
44
|
}
|
|
63
45
|
}
|
|
64
46
|
}
|
|
65
47
|
return steps;
|
|
66
48
|
};
|
|
67
49
|
const generateLockedData = async (projectDir) => {
|
|
68
|
-
const configPath = path_1.default.join(projectDir, 'config.yml');
|
|
69
|
-
// 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
|
|
70
|
-
const stepsPath = path_1.default.join(projectDir, 'steps');
|
|
71
50
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
nextSteps.dev.push(step);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
nextSteps.active.push(step);
|
|
83
|
-
}
|
|
84
|
-
const nextFlows = { ...acc.flows };
|
|
85
|
-
step.config.flows.forEach((flowName) => {
|
|
86
|
-
if (!nextFlows[flowName]) {
|
|
87
|
-
nextFlows[flowName] = {
|
|
88
|
-
name: flowName,
|
|
89
|
-
// TODO: how are we going to extract descriptions?
|
|
90
|
-
description: '',
|
|
91
|
-
steps: [],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
nextFlows[flowName].steps.push(step);
|
|
95
|
-
});
|
|
96
|
-
return {
|
|
97
|
-
...acc,
|
|
98
|
-
steps: nextSteps,
|
|
99
|
-
flows: nextFlows,
|
|
100
|
-
};
|
|
101
|
-
}, {
|
|
102
|
-
flows: {},
|
|
103
|
-
steps: {
|
|
104
|
-
active: [],
|
|
105
|
-
dev: [],
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
// Prepare the lock file data
|
|
109
|
-
const lockedData = {
|
|
110
|
-
baseDir: projectDir,
|
|
111
|
-
state: config?.state || {},
|
|
112
|
-
steps,
|
|
113
|
-
flows,
|
|
114
|
-
};
|
|
115
|
-
console.log('Project config loaded');
|
|
116
|
-
// TODO: for now this will return the locked data as an object, in the future we will write it to a lock file
|
|
51
|
+
/*
|
|
52
|
+
* NOTE: right now for performance and simplicity let's enforce a folder,
|
|
53
|
+
* but we might want to remove this and scan the entire current directory
|
|
54
|
+
*/
|
|
55
|
+
const sourceSteps = await collectFlows(path_1.default.join(projectDir, 'steps'));
|
|
56
|
+
const lockedData = new core_1.LockedData(projectDir);
|
|
57
|
+
sourceSteps.forEach((step) => lockedData.createStep(step));
|
|
117
58
|
return lockedData;
|
|
118
59
|
}
|
|
119
60
|
catch (error) {
|
|
@@ -0,0 +1,94 @@
|
|
|
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.Watcher = void 0;
|
|
7
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const core_1 = require("@motiadev/core");
|
|
10
|
+
class Watcher {
|
|
11
|
+
constructor(dir, lockedData) {
|
|
12
|
+
this.dir = dir;
|
|
13
|
+
this.lockedData = lockedData;
|
|
14
|
+
}
|
|
15
|
+
onStepChange(handler) {
|
|
16
|
+
this.stepChangeHandler = handler;
|
|
17
|
+
}
|
|
18
|
+
onStepCreate(handler) {
|
|
19
|
+
this.stepCreateHandler = handler;
|
|
20
|
+
}
|
|
21
|
+
onStepDelete(handler) {
|
|
22
|
+
this.stepDeleteHandler = handler;
|
|
23
|
+
}
|
|
24
|
+
findStep(path) {
|
|
25
|
+
return (this.lockedData.activeSteps.find((step) => step.filePath === path) ||
|
|
26
|
+
this.lockedData.devSteps.find((step) => step.filePath === path));
|
|
27
|
+
}
|
|
28
|
+
async onFileAdd(path) {
|
|
29
|
+
if (!this.stepCreateHandler) {
|
|
30
|
+
console.warn(`No step create handler, step skipped`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const config = await (0, core_1.getStepConfig)(path);
|
|
34
|
+
if (!config) {
|
|
35
|
+
console.warn(`No config found in step ${path}, step skipped`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const version = `${(0, crypto_1.randomUUID)()}:${Math.floor(Date.now() / 1000)}`;
|
|
39
|
+
const step = { filePath: path, version, config };
|
|
40
|
+
this.stepCreateHandler?.(step);
|
|
41
|
+
}
|
|
42
|
+
async onFileChange(path) {
|
|
43
|
+
if (!this.stepChangeHandler) {
|
|
44
|
+
console.warn(`No step change handler, step skipped`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const config = await (0, core_1.getStepConfig)(path);
|
|
48
|
+
const step = this.findStep(path);
|
|
49
|
+
if (!step && !config) {
|
|
50
|
+
console.warn(`Step ${path} not found, step skipped`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// didn't have a step, but now we have a config
|
|
54
|
+
if (!step && config) {
|
|
55
|
+
const version = `${(0, crypto_1.randomUUID)()}:${Math.floor(Date.now() / 1000)}`;
|
|
56
|
+
const step = { filePath: path, version, config };
|
|
57
|
+
this.stepCreateHandler?.(step);
|
|
58
|
+
}
|
|
59
|
+
// had a step, and now we have a config
|
|
60
|
+
if (step && config) {
|
|
61
|
+
const newStep = { ...step, config };
|
|
62
|
+
this.stepChangeHandler?.(step, newStep);
|
|
63
|
+
}
|
|
64
|
+
// had a step, but no config
|
|
65
|
+
if (step && !config) {
|
|
66
|
+
this.stepDeleteHandler?.(step);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async onFileDelete(path) {
|
|
70
|
+
if (!this.stepDeleteHandler) {
|
|
71
|
+
console.warn(`No step delete handler, step skipped`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const step = this.findStep(path);
|
|
75
|
+
if (!step) {
|
|
76
|
+
console.warn(`Step ${path} not found, step skipped`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.stepDeleteHandler?.(step);
|
|
80
|
+
}
|
|
81
|
+
init() {
|
|
82
|
+
this.watcher = chokidar_1.default
|
|
83
|
+
.watch(this.dir, { persistent: true, ignoreInitial: true })
|
|
84
|
+
.on('add', (path) => this.onFileAdd(path))
|
|
85
|
+
.on('change', (path) => this.onFileChange(path))
|
|
86
|
+
.on('unlink', (path) => this.onFileDelete(path));
|
|
87
|
+
}
|
|
88
|
+
async stop() {
|
|
89
|
+
if (this.watcher) {
|
|
90
|
+
await this.watcher.close();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.Watcher = Watcher;
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "motia",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"motia": "dist/src/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
+
"chokidar": "^4.0.3",
|
|
9
10
|
"commander": "^13.0.0",
|
|
10
11
|
"dotenv": "^16.4.7",
|
|
11
12
|
"figlet": "^1.8.0",
|
|
12
13
|
"ts-node": "^10.9.2",
|
|
13
14
|
"yaml": "^2.7.0",
|
|
14
|
-
"@motiadev/core": "0.0.
|
|
15
|
-
"@motiadev/workbench": "0.0.
|
|
15
|
+
"@motiadev/core": "0.0.21",
|
|
16
|
+
"@motiadev/workbench": "0.0.21"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@types/figlet": "^1.7.0",
|