motia 0.5.12-beta.121 → 0.6.0-beta.122
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/cli.js +13 -0
- package/dist/cjs/create/index.d.ts +1 -0
- package/dist/cjs/create/index.js +12 -38
- package/dist/cjs/create/interactive.js +1 -1
- package/dist/cjs/create/setup-template.d.ts +2 -0
- package/dist/cjs/create/setup-template.js +13 -0
- package/dist/cjs/create/templates/generate.d.ts +1 -1
- package/dist/cjs/create/templates/generate.js +28 -5
- package/dist/cjs/create/templates/generate.ts +33 -6
- package/dist/cjs/create/templates/index.js +1 -1
- package/dist/cjs/create/templates/index.ts +1 -1
- package/dist/cjs/create/templates/python/motia-workbench.json +14 -16
- package/dist/cjs/create/templates/python/requirements.txt +2 -0
- package/dist/cjs/create/templates/python/steps/api_step.py-features.json.txt +69 -0
- package/dist/cjs/create/templates/python/steps/api_step.py.txt +52 -0
- package/dist/cjs/create/templates/python/steps/notification_step.py.txt +40 -0
- package/dist/cjs/create/templates/python/steps/process_food_order_step.py-features.json.txt +68 -0
- package/dist/cjs/create/templates/python/steps/process_food_order_step.py.txt +50 -0
- package/dist/cjs/create/templates/python/steps/services/__init__.py.txt +0 -0
- package/dist/cjs/create/templates/python/steps/services/pet_store.py.txt +37 -0
- package/dist/cjs/create/templates/python/steps/services/types.py.txt +19 -0
- package/dist/cjs/create/templates/python/steps/state_audit_cron_step.py-features.json.txt +26 -0
- package/dist/cjs/create/templates/python/steps/state_audit_cron_step.py.txt +39 -0
- package/dist/cjs/create/templates/python/tutorial.tsx.txt +647 -0
- package/dist/cjs/create/templates/typescript/motia-workbench.json +28 -0
- package/dist/cjs/create/templates/typescript/services/pet-store.ts.txt +29 -0
- package/dist/cjs/create/templates/typescript/services/types.ts.txt +21 -0
- package/dist/cjs/create/templates/typescript/steps/api.step.ts-features.json.txt +67 -0
- package/dist/cjs/create/templates/typescript/steps/api.step.ts.txt +50 -0
- package/dist/cjs/create/templates/typescript/steps/notification.step.ts.txt +35 -0
- package/dist/cjs/create/templates/typescript/steps/process-food-order.step.ts-features.json.txt +67 -0
- package/dist/cjs/create/templates/typescript/steps/process-food-order.step.ts.txt +44 -0
- package/dist/cjs/create/templates/typescript/steps/state-audit-cron.step.ts-features.json.txt +26 -0
- package/dist/cjs/create/templates/typescript/steps/state-audit-cron.step.ts.txt +51 -0
- package/dist/cjs/create/templates/typescript/tutorial.tsx.txt +647 -0
- package/dist/cjs/create/utils.d.ts +2 -0
- package/dist/cjs/create/utils.js +21 -0
- package/dist/cjs/docker/utils/print-intro.js +0 -1
- package/dist/esm/cli.js +10 -0
- package/dist/esm/create/index.d.ts +1 -0
- package/dist/esm/create/index.js +4 -30
- package/dist/esm/create/interactive.js +1 -1
- package/dist/esm/create/setup-template.d.ts +2 -0
- package/dist/esm/create/setup-template.js +9 -0
- package/dist/esm/create/templates/generate.d.ts +1 -1
- package/dist/esm/create/templates/generate.js +29 -6
- package/dist/esm/create/templates/generate.ts +33 -6
- package/dist/esm/create/templates/index.js +1 -1
- package/dist/esm/create/templates/index.ts +1 -1
- package/dist/esm/create/templates/python/motia-workbench.json +14 -16
- package/dist/esm/create/templates/python/requirements.txt +2 -0
- package/dist/esm/create/templates/python/steps/api_step.py-features.json.txt +69 -0
- package/dist/esm/create/templates/python/steps/api_step.py.txt +52 -0
- package/dist/esm/create/templates/python/steps/notification_step.py.txt +40 -0
- package/dist/esm/create/templates/python/steps/process_food_order_step.py-features.json.txt +68 -0
- package/dist/esm/create/templates/python/steps/process_food_order_step.py.txt +50 -0
- package/dist/esm/create/templates/python/steps/services/__init__.py.txt +0 -0
- package/dist/esm/create/templates/python/steps/services/pet_store.py.txt +37 -0
- package/dist/esm/create/templates/python/steps/services/types.py.txt +19 -0
- package/dist/esm/create/templates/python/steps/state_audit_cron_step.py-features.json.txt +26 -0
- package/dist/esm/create/templates/python/steps/state_audit_cron_step.py.txt +39 -0
- package/dist/esm/create/templates/python/tutorial.tsx.txt +647 -0
- package/dist/esm/create/templates/typescript/motia-workbench.json +28 -0
- package/dist/esm/create/templates/typescript/services/pet-store.ts.txt +29 -0
- package/dist/esm/create/templates/typescript/services/types.ts.txt +21 -0
- package/dist/esm/create/templates/typescript/steps/api.step.ts-features.json.txt +67 -0
- package/dist/esm/create/templates/typescript/steps/api.step.ts.txt +50 -0
- package/dist/esm/create/templates/typescript/steps/notification.step.ts.txt +35 -0
- package/dist/esm/create/templates/typescript/steps/process-food-order.step.ts-features.json.txt +67 -0
- package/dist/esm/create/templates/typescript/steps/process-food-order.step.ts.txt +44 -0
- package/dist/esm/create/templates/typescript/steps/state-audit-cron.step.ts-features.json.txt +26 -0
- package/dist/esm/create/templates/typescript/steps/state-audit-cron.step.ts.txt +51 -0
- package/dist/esm/create/templates/typescript/tutorial.tsx.txt +647 -0
- package/dist/esm/create/utils.d.ts +2 -0
- package/dist/esm/create/utils.js +13 -0
- package/dist/esm/docker/utils/print-intro.js +0 -1
- package/dist/types/create/index.d.ts +1 -0
- package/dist/types/create/setup-template.d.ts +2 -0
- package/dist/types/create/templates/generate.d.ts +1 -1
- package/dist/types/create/utils.d.ts +2 -0
- package/package.json +4 -4
- package/dist/cjs/create/templates/default/motia-workbench.json +0 -29
- package/dist/cjs/create/templates/default/steps/00-noop.step.ts.txt +0 -33
- package/dist/cjs/create/templates/default/steps/00-noop.step.tsx.txt +0 -18
- package/dist/cjs/create/templates/default/steps/01-api.step.ts.txt +0 -70
- package/dist/cjs/create/templates/default/steps/02-test-state.step.ts.txt +0 -53
- package/dist/cjs/create/templates/default/steps/03-check-state-change.step.ts.txt +0 -54
- package/dist/cjs/create/templates/python/steps/00_noop_step.py.txt +0 -24
- package/dist/cjs/create/templates/python/steps/00_noop_step.tsx.txt +0 -18
- package/dist/cjs/create/templates/python/steps/01_api_step.py.txt +0 -53
- package/dist/cjs/create/templates/python/steps/02_test_state_step.py.txt +0 -38
- package/dist/cjs/create/templates/python/steps/03_check_state_change_step.py.txt +0 -43
- package/dist/esm/create/templates/default/motia-workbench.json +0 -29
- package/dist/esm/create/templates/default/steps/00-noop.step.ts.txt +0 -33
- package/dist/esm/create/templates/default/steps/00-noop.step.tsx.txt +0 -18
- package/dist/esm/create/templates/default/steps/01-api.step.ts.txt +0 -70
- package/dist/esm/create/templates/default/steps/02-test-state.step.ts.txt +0 -53
- package/dist/esm/create/templates/default/steps/03-check-state-change.step.ts.txt +0 -54
- package/dist/esm/create/templates/python/steps/00_noop_step.py.txt +0 -24
- package/dist/esm/create/templates/python/steps/00_noop_step.tsx.txt +0 -18
- package/dist/esm/create/templates/python/steps/01_api_step.py.txt +0 -53
- package/dist/esm/create/templates/python/steps/02_test_state_step.py.txt +0 -38
- package/dist/esm/create/templates/python/steps/03_check_state_change_step.py.txt +0 -43
package/dist/cjs/cli.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
5
8
|
const commander_1 = require("commander");
|
|
6
9
|
require("./cloud");
|
|
7
10
|
const version_1 = require("./version");
|
|
8
11
|
const config_utils_1 = require("./cloud/config-utils");
|
|
12
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
13
|
const defaultPort = 3000;
|
|
10
14
|
const defaultHost = '0.0.0.0';
|
|
11
15
|
require('dotenv/config');
|
|
@@ -28,14 +32,23 @@ commander_1.program
|
|
|
28
32
|
.option('-c, --cursor', 'Copy .cursor folder from template')
|
|
29
33
|
.option('-i, --interactive', 'Use interactive prompts to create project')
|
|
30
34
|
.option('-y, --skip-confirmation', 'Skip confirmation prompt')
|
|
35
|
+
.option('-d, --skip-tutorial', 'Skip the motia tutorial', false)
|
|
31
36
|
.action((0, config_utils_1.handler)(async (arg, context) => {
|
|
32
37
|
if (arg.name || arg.template || arg.cursor) {
|
|
33
38
|
const { create } = require('./create');
|
|
39
|
+
const disableTutorial = await inquirer_1.default.prompt({
|
|
40
|
+
type: 'confirm',
|
|
41
|
+
name: 'disableTutorial',
|
|
42
|
+
message: 'Do you wish to disable the motia tutorial?',
|
|
43
|
+
default: arg.skipTutorial,
|
|
44
|
+
when: () => arg.skipTutorial === false,
|
|
45
|
+
});
|
|
34
46
|
await create({
|
|
35
47
|
projectName: arg.name ?? '.',
|
|
36
48
|
template: arg.template ?? 'default',
|
|
37
49
|
cursorEnabled: arg.cursor,
|
|
38
50
|
context,
|
|
51
|
+
skipTutorialTemplates: disableTutorial.disableTutorial,
|
|
39
52
|
});
|
|
40
53
|
}
|
|
41
54
|
else {
|
package/dist/cjs/create/index.js
CHANGED
|
@@ -6,35 +6,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.create = void 0;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const templates_1 = require("./templates");
|
|
10
9
|
const execute_command_1 = require("../utils/execute-command");
|
|
11
10
|
const install_1 = require("../install");
|
|
12
11
|
const generate_types_1 = require("../generate-types");
|
|
13
12
|
const version_1 = require("../version");
|
|
13
|
+
const setup_template_1 = require("./setup-template");
|
|
14
|
+
const utils_1 = require("./utils");
|
|
14
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
16
|
require('ts-node').register({
|
|
16
17
|
transpileOnly: true,
|
|
17
18
|
compilerOptions: { module: 'commonjs' },
|
|
18
19
|
});
|
|
19
|
-
const checkIfFileExists = (dir, fileName) => {
|
|
20
|
-
return fs_1.default.existsSync(path_1.default.join(dir, fileName));
|
|
21
|
-
};
|
|
22
|
-
const checkIfDirectoryExists = (dir) => {
|
|
23
|
-
try {
|
|
24
|
-
return fs_1.default.statSync(dir).isDirectory();
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
20
|
const getPackageManager = (dir) => {
|
|
31
|
-
if (checkIfFileExists(dir, 'yarn.lock')) {
|
|
21
|
+
if ((0, utils_1.checkIfFileExists)(dir, 'yarn.lock')) {
|
|
32
22
|
return 'yarn';
|
|
33
23
|
}
|
|
34
|
-
else if (checkIfFileExists(dir, 'pnpm-lock.yaml')) {
|
|
24
|
+
else if ((0, utils_1.checkIfFileExists)(dir, 'pnpm-lock.yaml')) {
|
|
35
25
|
return 'pnpm';
|
|
36
26
|
}
|
|
37
|
-
else if (checkIfFileExists(dir, 'package-lock.json')) {
|
|
27
|
+
else if ((0, utils_1.checkIfFileExists)(dir, 'package-lock.json')) {
|
|
38
28
|
return 'npm';
|
|
39
29
|
}
|
|
40
30
|
else {
|
|
@@ -97,14 +87,14 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
|
|
|
97
87
|
'\n\n');
|
|
98
88
|
const isCurrentDir = projectName === '.' || projectName === './' || projectName === '.\\';
|
|
99
89
|
const rootDir = isCurrentDir ? process.cwd() : path_1.default.join(process.cwd(), projectName);
|
|
100
|
-
if (!isCurrentDir && !checkIfDirectoryExists(rootDir)) {
|
|
90
|
+
if (!isCurrentDir && !(0, utils_1.checkIfDirectoryExists)(rootDir)) {
|
|
101
91
|
fs_1.default.mkdirSync(path_1.default.join(rootDir));
|
|
102
92
|
context.log('directory-created', (message) => message.tag('success').append('Directory created ').append(projectName, 'gray'));
|
|
103
93
|
}
|
|
104
94
|
else {
|
|
105
95
|
context.log('directory-using', (message) => message.tag('info').append('Using current directory'));
|
|
106
96
|
}
|
|
107
|
-
if (!checkIfFileExists(rootDir, 'package.json')) {
|
|
97
|
+
if (!(0, utils_1.checkIfFileExists)(rootDir, 'package.json')) {
|
|
108
98
|
const packageJsonContent = {
|
|
109
99
|
name: projectName,
|
|
110
100
|
description: '',
|
|
@@ -144,7 +134,7 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
|
|
|
144
134
|
.append('command to')
|
|
145
135
|
.append('package.json', 'gray'));
|
|
146
136
|
}
|
|
147
|
-
if (!checkIfFileExists(rootDir, 'tsconfig.json')) {
|
|
137
|
+
if (!(0, utils_1.checkIfFileExists)(rootDir, 'tsconfig.json')) {
|
|
148
138
|
const tsconfigContent = {
|
|
149
139
|
compilerOptions: {
|
|
150
140
|
target: 'ES2020',
|
|
@@ -167,7 +157,7 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
|
|
|
167
157
|
fs_1.default.writeFileSync(path_1.default.join(rootDir, 'tsconfig.json'), JSON.stringify(tsconfigContent, null, 2));
|
|
168
158
|
context.log('tsconfig-json-created', (message) => message.tag('success').append('File').append('tsconfig.json', 'cyan').append('has been created.'));
|
|
169
159
|
}
|
|
170
|
-
if (!checkIfFileExists(rootDir, '.gitignore')) {
|
|
160
|
+
if (!(0, utils_1.checkIfFileExists)(rootDir, '.gitignore')) {
|
|
171
161
|
const gitignoreContent = [
|
|
172
162
|
'node_modules',
|
|
173
163
|
'python_modules',
|
|
@@ -183,31 +173,15 @@ const create = async ({ projectName, template, cursorEnabled, context }) => {
|
|
|
183
173
|
}
|
|
184
174
|
const cursorTemplateDir = path_1.default.join(__dirname, '../../dot-files/.cursor');
|
|
185
175
|
const cursorTargetDir = path_1.default.join(rootDir, '.cursor');
|
|
186
|
-
if (cursorEnabled && !checkIfDirectoryExists(cursorTargetDir)) {
|
|
176
|
+
if (cursorEnabled && !(0, utils_1.checkIfDirectoryExists)(cursorTargetDir)) {
|
|
187
177
|
fs_1.default.cpSync(cursorTemplateDir, cursorTargetDir, { recursive: true });
|
|
188
178
|
context.log('cursor-folder-created', (message) => message.tag('success').append('Folder').append('.cursor', 'cyan').append('has been created.'));
|
|
189
179
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
fs_1.default.mkdirSync(stepsDir);
|
|
193
|
-
context.log('steps-directory-created', (message) => message.tag('success').append('Folder').append('steps', 'cyan').append('has been created.'));
|
|
194
|
-
}
|
|
195
|
-
if (!template || !(template in templates_1.templates)) {
|
|
196
|
-
context.log('template-not-found', (message) => message.tag('failed').append(`Template ${template} not found, please use one of the following:`));
|
|
197
|
-
context.log('available-templates', (message) => message.tag('info').append(`Available templates: \n\n ${Object.keys(templates_1.templates).join('\n')}`));
|
|
198
|
-
return;
|
|
180
|
+
if (template) {
|
|
181
|
+
await (0, setup_template_1.setupTemplate)(template, rootDir, context);
|
|
199
182
|
}
|
|
200
|
-
await templates_1.templates[template](rootDir, context);
|
|
201
183
|
const packageManager = await installNodeDependencies(rootDir, context);
|
|
202
184
|
if (template === 'python') {
|
|
203
|
-
if (!checkIfFileExists(rootDir, 'requirements.txt')) {
|
|
204
|
-
const requirementsContent = [
|
|
205
|
-
// TODO: motia PyPi package
|
|
206
|
-
// Add other Python dependencies as needed
|
|
207
|
-
].join('\n');
|
|
208
|
-
fs_1.default.writeFileSync(path_1.default.join(rootDir, 'requirements.txt'), requirementsContent);
|
|
209
|
-
context.log('requirements-txt-created', (message) => message.tag('success').append('File').append('requirements.txt', 'gray').append('has been created.'));
|
|
210
|
-
}
|
|
211
185
|
await (0, install_1.pythonInstall)({ baseDir: rootDir });
|
|
212
186
|
}
|
|
213
187
|
await (0, generate_types_1.generateTypes)(rootDir);
|
|
@@ -8,7 +8,7 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
8
8
|
const colors_1 = __importDefault(require("colors"));
|
|
9
9
|
const index_1 = require("./index");
|
|
10
10
|
const choices = {
|
|
11
|
-
|
|
11
|
+
typescript: 'Base (TypeScript)',
|
|
12
12
|
python: 'Base (Python)',
|
|
13
13
|
};
|
|
14
14
|
const createInteractive = async (_args, context) => {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupTemplate = void 0;
|
|
4
|
+
const templates_1 = require("./templates");
|
|
5
|
+
const setupTemplate = async (template, rootDir, context) => {
|
|
6
|
+
if (!template || !(template in templates_1.templates)) {
|
|
7
|
+
context.log('template-not-found', (message) => message.tag('failed').append(`Template ${template} not found, please use one of the following:`));
|
|
8
|
+
context.log('available-templates', (message) => message.tag('info').append(`Available templates: \n\n ${Object.keys(templates_1.templates).join('\n')}`));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
await templates_1.templates[template](rootDir, context);
|
|
12
|
+
};
|
|
13
|
+
exports.setupTemplate = setupTemplate;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { CliContext } from '../../cloud/config-utils';
|
|
2
2
|
export type Generator = (rootDir: string, context: CliContext) => Promise<void>;
|
|
3
|
-
export declare const generateTemplateSteps: (
|
|
3
|
+
export declare const generateTemplateSteps: (templateFolder: string) => Generator;
|
|
@@ -37,20 +37,43 @@ exports.generateTemplateSteps = void 0;
|
|
|
37
37
|
const fs_1 = require("fs");
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const glob_1 = require("glob");
|
|
40
|
-
const generateTemplateSteps = (
|
|
40
|
+
const generateTemplateSteps = (templateFolder) => {
|
|
41
41
|
return async (rootDir, context) => {
|
|
42
|
-
const templatePath = path.join(__dirname,
|
|
42
|
+
const templatePath = path.join(__dirname, templateFolder);
|
|
43
43
|
const files = (0, glob_1.globSync)('**/*', { absolute: false, cwd: templatePath });
|
|
44
44
|
try {
|
|
45
45
|
for (const fileName of files) {
|
|
46
46
|
const filePath = path.join(templatePath, fileName);
|
|
47
|
+
const targetFilePath = path.join(rootDir, fileName);
|
|
48
|
+
const targetDir = path.dirname(targetFilePath);
|
|
49
|
+
try {
|
|
50
|
+
// Check if it's a directory in the template
|
|
51
|
+
(0, fs_1.statSync)(targetDir);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
(0, fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
55
|
+
}
|
|
47
56
|
if ((0, fs_1.statSync)(filePath).isDirectory()) {
|
|
48
|
-
|
|
57
|
+
const folderPath = path.basename(filePath);
|
|
58
|
+
(0, fs_1.mkdirSync)(path.join(rootDir, folderPath), { recursive: true });
|
|
49
59
|
continue;
|
|
50
60
|
}
|
|
51
|
-
const sanitizedFileName = fileName.replace('.txt', '');
|
|
61
|
+
const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '');
|
|
62
|
+
const isWorkbenchConfig = fileName.match('motia-workbench.json');
|
|
52
63
|
const generateFilePath = path.join(rootDir, sanitizedFileName);
|
|
53
|
-
|
|
64
|
+
let content = await fs_1.promises.readFile(filePath, 'utf8');
|
|
65
|
+
// Make sure statSync doesn't break the execution if the file doesn't exist
|
|
66
|
+
try {
|
|
67
|
+
if (isWorkbenchConfig && (0, fs_1.statSync)(generateFilePath).isFile()) {
|
|
68
|
+
const existingWorkbenchConfig = await fs_1.promises.readFile(generateFilePath, 'utf8');
|
|
69
|
+
const workbenchContent = JSON.parse(content);
|
|
70
|
+
content = JSON.stringify([...JSON.parse(existingWorkbenchConfig), ...workbenchContent], null, 2);
|
|
71
|
+
context.log('workbench-config-updated', (message) => message.tag('success').append('Workbench config').append('has been updated.'));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
void 0;
|
|
76
|
+
}
|
|
54
77
|
await fs_1.promises.writeFile(generateFilePath, content, 'utf8');
|
|
55
78
|
context.log(sanitizedFileName, (message) => {
|
|
56
79
|
message.tag('success').append('File').append(sanitizedFileName, 'cyan').append('has been created.');
|
|
@@ -1,27 +1,54 @@
|
|
|
1
|
-
import { promises as fs, statSync } from 'fs'
|
|
1
|
+
import { promises as fs, statSync, mkdirSync } from 'fs'
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import { globSync } from 'glob'
|
|
4
4
|
import { CliContext } from '../../cloud/config-utils'
|
|
5
5
|
|
|
6
6
|
export type Generator = (rootDir: string, context: CliContext) => Promise<void>
|
|
7
7
|
|
|
8
|
-
export const generateTemplateSteps = (
|
|
8
|
+
export const generateTemplateSteps = (templateFolder: string): Generator => {
|
|
9
9
|
return async (rootDir: string, context: CliContext): Promise<void> => {
|
|
10
|
-
const templatePath = path.join(__dirname,
|
|
10
|
+
const templatePath = path.join(__dirname, templateFolder)
|
|
11
11
|
const files = globSync('**/*', { absolute: false, cwd: templatePath })
|
|
12
12
|
|
|
13
13
|
try {
|
|
14
14
|
for (const fileName of files) {
|
|
15
15
|
const filePath = path.join(templatePath, fileName)
|
|
16
|
+
const targetFilePath = path.join(rootDir, fileName)
|
|
17
|
+
const targetDir = path.dirname(targetFilePath)
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Check if it's a directory in the template
|
|
21
|
+
statSync(targetDir)
|
|
22
|
+
} catch {
|
|
23
|
+
mkdirSync(targetDir, { recursive: true })
|
|
24
|
+
}
|
|
16
25
|
|
|
17
26
|
if (statSync(filePath).isDirectory()) {
|
|
18
|
-
|
|
27
|
+
const folderPath = path.basename(filePath)
|
|
28
|
+
mkdirSync(path.join(rootDir, folderPath), { recursive: true })
|
|
19
29
|
continue
|
|
20
30
|
}
|
|
21
31
|
|
|
22
|
-
const sanitizedFileName = fileName.replace('.txt', '')
|
|
32
|
+
const sanitizedFileName = fileName === 'requirements.txt' ? fileName : fileName.replace('.txt', '')
|
|
33
|
+
const isWorkbenchConfig = fileName.match('motia-workbench.json')
|
|
23
34
|
const generateFilePath = path.join(rootDir, sanitizedFileName)
|
|
24
|
-
|
|
35
|
+
let content = await fs.readFile(filePath, 'utf8')
|
|
36
|
+
|
|
37
|
+
// Make sure statSync doesn't break the execution if the file doesn't exist
|
|
38
|
+
try {
|
|
39
|
+
if (isWorkbenchConfig && statSync(generateFilePath).isFile()) {
|
|
40
|
+
const existingWorkbenchConfig = await fs.readFile(generateFilePath, 'utf8')
|
|
41
|
+
const workbenchContent = JSON.parse(content)
|
|
42
|
+
|
|
43
|
+
content = JSON.stringify([...JSON.parse(existingWorkbenchConfig), ...workbenchContent], null, 2)
|
|
44
|
+
|
|
45
|
+
context.log('workbench-config-updated', (message) =>
|
|
46
|
+
message.tag('success').append('Workbench config').append('has been updated.'),
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
void 0
|
|
51
|
+
}
|
|
25
52
|
|
|
26
53
|
await fs.writeFile(generateFilePath, content, 'utf8')
|
|
27
54
|
context.log(sanitizedFileName, (message) => {
|
|
@@ -3,6 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.templates = void 0;
|
|
4
4
|
const generate_1 = require("./generate");
|
|
5
5
|
exports.templates = {
|
|
6
|
-
|
|
6
|
+
typescript: (0, generate_1.generateTemplateSteps)('typescript'),
|
|
7
7
|
python: (0, generate_1.generateTemplateSteps)('python'),
|
|
8
8
|
};
|
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
|
-
"id": "
|
|
3
|
+
"id": "python-tutorial",
|
|
4
4
|
"config": {
|
|
5
|
-
"steps/
|
|
6
|
-
"x": -
|
|
7
|
-
"y":
|
|
5
|
+
"steps/state_audit_cron_step.py": {
|
|
6
|
+
"x": -38,
|
|
7
|
+
"y": 683,
|
|
8
8
|
"sourceHandlePosition": "right"
|
|
9
9
|
},
|
|
10
|
-
"steps/
|
|
11
|
-
"x":
|
|
12
|
-
"y":
|
|
13
|
-
"sourceHandlePosition": "right",
|
|
10
|
+
"steps/process_food_order_step.py": {
|
|
11
|
+
"x": 384,
|
|
12
|
+
"y": 476,
|
|
14
13
|
"targetHandlePosition": "left"
|
|
15
14
|
},
|
|
16
|
-
"steps/
|
|
17
|
-
"x":
|
|
18
|
-
"y":
|
|
19
|
-
"sourceHandlePosition": "bottom",
|
|
15
|
+
"steps/notification_step.py": {
|
|
16
|
+
"x": 601,
|
|
17
|
+
"y": 724,
|
|
20
18
|
"targetHandlePosition": "left"
|
|
21
19
|
},
|
|
22
|
-
"steps/
|
|
23
|
-
"x":
|
|
24
|
-
"y":
|
|
25
|
-
"
|
|
20
|
+
"steps/api_step.py": {
|
|
21
|
+
"x": 15,
|
|
22
|
+
"y": 461,
|
|
23
|
+
"sourceHandlePosition": "right"
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
26
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "step-configuration",
|
|
4
|
+
"title": "Step Configuration",
|
|
5
|
+
"description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
|
|
6
|
+
"lines": [
|
|
7
|
+
"6-30"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "api-configuration",
|
|
12
|
+
"title": "API Step",
|
|
13
|
+
"description": "Definition of an API endpoint",
|
|
14
|
+
"lines": [
|
|
15
|
+
"23-24"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "request-body",
|
|
20
|
+
"title": "Request body",
|
|
21
|
+
"description": "Definition of the expected request body. Motia will automatically generate types based on this schema.",
|
|
22
|
+
"lines": [
|
|
23
|
+
"6-16",
|
|
24
|
+
"25"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "response-payload",
|
|
29
|
+
"title": "Response Payload",
|
|
30
|
+
"description": "Definition of the expected response payload, Motia will generate the types automatically based on this schema. This is also important to create the Open API spec later.",
|
|
31
|
+
"lines": [
|
|
32
|
+
"4",
|
|
33
|
+
"26-28"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "event-driven-architecture",
|
|
38
|
+
"title": "Emits",
|
|
39
|
+
"description": "We can define the events that this step will emit, this is how we can trigger other Motia Steps.",
|
|
40
|
+
"lines": [
|
|
41
|
+
"29",
|
|
42
|
+
"42-50"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "handler",
|
|
47
|
+
"title": "Handler",
|
|
48
|
+
"description": "The handler is the function that will be executed when the step is triggered. This one receives the request body and emits events.",
|
|
49
|
+
"lines": [
|
|
50
|
+
"32-52"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "logger",
|
|
55
|
+
"title": "Logger",
|
|
56
|
+
"description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function. We encourage you to use it instead of console.log. It will automatically be tied to the trace id of the request.",
|
|
57
|
+
"lines": [
|
|
58
|
+
"34"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "http-response",
|
|
63
|
+
"title": "HTTP Response",
|
|
64
|
+
"description": "The handler can return a response to the client. This is how we can return a response to the client. It must comply with the responseSchema defined in the step configuration.",
|
|
65
|
+
"lines": [
|
|
66
|
+
"52"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from .services.pet_store import pet_store_service
|
|
4
|
+
from .services.types import Pet
|
|
5
|
+
|
|
6
|
+
class PetRequest(BaseModel):
|
|
7
|
+
name: str
|
|
8
|
+
photo_url: str
|
|
9
|
+
|
|
10
|
+
class FoodOrder(BaseModel):
|
|
11
|
+
id: str
|
|
12
|
+
quantity: int
|
|
13
|
+
|
|
14
|
+
class RequestBody(BaseModel):
|
|
15
|
+
pet: PetRequest
|
|
16
|
+
food_order: Optional[FoodOrder] = None
|
|
17
|
+
|
|
18
|
+
config = {
|
|
19
|
+
"type": "api",
|
|
20
|
+
"name": "PythonApiTrigger",
|
|
21
|
+
"description": "basic-tutorial api trigger",
|
|
22
|
+
"flows": ["python-tutorial"],
|
|
23
|
+
"method": "POST",
|
|
24
|
+
"path": "/python-basic-tutorial",
|
|
25
|
+
"bodySchema": RequestBody.model_json_schema(),
|
|
26
|
+
"responseSchema": {
|
|
27
|
+
200: Pet.model_json_schema(),
|
|
28
|
+
},
|
|
29
|
+
"emits": ["python-process-food-order"],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async def handler(req, context):
|
|
33
|
+
body = req.get("body", {})
|
|
34
|
+
context.logger.info("Step 01 – Processing API Step", {"body": body})
|
|
35
|
+
|
|
36
|
+
pet = body.get("pet", {})
|
|
37
|
+
food_order = body.get("food_order", {})
|
|
38
|
+
|
|
39
|
+
new_pet_record = await pet_store_service.create_pet(pet)
|
|
40
|
+
|
|
41
|
+
if food_order:
|
|
42
|
+
await context.emit({
|
|
43
|
+
"topic": "python-process-food-order",
|
|
44
|
+
"data": {
|
|
45
|
+
"id": food_order.get("id"),
|
|
46
|
+
"quantity": food_order.get("quantity"),
|
|
47
|
+
"email": "test@test.com", # sample email
|
|
48
|
+
"pet_id": new_pet_record.get("id"),
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return {"status": 200, "body": new_pet_record}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
class InputSchema(BaseModel):
|
|
6
|
+
template_id: str
|
|
7
|
+
email: str
|
|
8
|
+
template_data: Dict[str, Any]
|
|
9
|
+
|
|
10
|
+
config = {
|
|
11
|
+
"type": "event",
|
|
12
|
+
"name": "PythonNotification",
|
|
13
|
+
"description": "Checks a state change",
|
|
14
|
+
"flows": ["python-tutorial"],
|
|
15
|
+
"subscribes": ["python-notification"],
|
|
16
|
+
"emits": [],
|
|
17
|
+
"input": InputSchema.model_json_schema(),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async def handler(input_data, context):
|
|
21
|
+
email = input_data.get("email")
|
|
22
|
+
template_id = input_data.get("template_id")
|
|
23
|
+
template_data = input_data.get("template_data")
|
|
24
|
+
|
|
25
|
+
redacted_email = re.sub(r'(?<=.{2}).(?=.*@)', '*', email)
|
|
26
|
+
|
|
27
|
+
context.logger.info("Processing Notification", {
|
|
28
|
+
"template_id": template_id,
|
|
29
|
+
"template_data": template_data,
|
|
30
|
+
"email": redacted_email,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
# This represents a call to some sort of
|
|
34
|
+
# notification service to indicate that a
|
|
35
|
+
# new order has been placed
|
|
36
|
+
context.logger.info("New notification sent", {
|
|
37
|
+
"template_id": template_id,
|
|
38
|
+
"email": redacted_email,
|
|
39
|
+
"template_data": template_data,
|
|
40
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "step-configuration",
|
|
4
|
+
"title": "Step Configuration",
|
|
5
|
+
"description": "All steps should have a defined configuration, this is how you define the step's behavior and how it will be triggered.",
|
|
6
|
+
"lines": [
|
|
7
|
+
"5-19"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "event-configuration",
|
|
12
|
+
"title": "Event Step",
|
|
13
|
+
"description": "Definition of an event step that subscribes to specific topics",
|
|
14
|
+
"lines": [
|
|
15
|
+
"12",
|
|
16
|
+
"15-16"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "input-schema",
|
|
21
|
+
"title": "Input Schema",
|
|
22
|
+
"description": "Definition of the expected input data structure from the subscribed topic. Motia will automatically generate types based on this schema.",
|
|
23
|
+
"lines": [
|
|
24
|
+
"5-9",
|
|
25
|
+
"17"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "event-emits",
|
|
30
|
+
"title": "Emits",
|
|
31
|
+
"description": "We can define the events that this step will emit, triggering other Motia Steps.",
|
|
32
|
+
"lines": [
|
|
33
|
+
"17"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "handler",
|
|
38
|
+
"title": "Handler",
|
|
39
|
+
"description": "The handler is the function that will be executed when the step receives an event from its subscribed topic. It processes the input data and can emit new events.",
|
|
40
|
+
"lines": [
|
|
41
|
+
"21-50"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "state",
|
|
46
|
+
"title": "State Management",
|
|
47
|
+
"description": "The handler demonstrates state management by storing order data that can be accessed by other steps.",
|
|
48
|
+
"lines": [
|
|
49
|
+
"35"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "event-emission",
|
|
54
|
+
"title": "Event Emission",
|
|
55
|
+
"description": "After processing the order, the handler emits a new event to notify other steps about the new order.",
|
|
56
|
+
"lines": [
|
|
57
|
+
"37-50"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "logger",
|
|
62
|
+
"title": "Logger",
|
|
63
|
+
"description": "The logger is a utility that allows you to log messages to the console. It is available in the handler function and automatically ties to the trace id of the request.",
|
|
64
|
+
"lines": [
|
|
65
|
+
"22"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from .services.pet_store import pet_store_service
|
|
4
|
+
|
|
5
|
+
class InputSchema(BaseModel):
|
|
6
|
+
id: str
|
|
7
|
+
email: str
|
|
8
|
+
quantity: int
|
|
9
|
+
pet_id: int
|
|
10
|
+
|
|
11
|
+
config = {
|
|
12
|
+
"type": "event",
|
|
13
|
+
"name": "PythonProcessFoodOrder",
|
|
14
|
+
"description": "basic-tutorial event step, demonstrates how to consume an event from a topic and persist data in state",
|
|
15
|
+
"flows": ["python-tutorial"],
|
|
16
|
+
"subscribes": ["python-process-food-order"],
|
|
17
|
+
"emits": ["python-notification"],
|
|
18
|
+
"input": InputSchema.model_json_schema(),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async def handler(input_data, context):
|
|
22
|
+
context.logger.info("Step 02 – Process food order", {"input": input_data})
|
|
23
|
+
|
|
24
|
+
order = await pet_store_service.create_order({
|
|
25
|
+
"id": input_data.get("id"),
|
|
26
|
+
"quantity": input_data.get("quantity"),
|
|
27
|
+
"pet_id": input_data.get("pet_id"),
|
|
28
|
+
"email": input_data.get("email"),
|
|
29
|
+
"ship_date": datetime.now().isoformat(),
|
|
30
|
+
"status": "placed",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
context.logger.info("Order created", {"order": order})
|
|
34
|
+
|
|
35
|
+
await context.state.set("orders_python", order.get("id"), order)
|
|
36
|
+
|
|
37
|
+
await context.emit({
|
|
38
|
+
"topic": "python-notification",
|
|
39
|
+
"data": {
|
|
40
|
+
"email": input_data["email"],
|
|
41
|
+
"template_id": "new-order",
|
|
42
|
+
"template_data": {
|
|
43
|
+
"status": order.get("status"),
|
|
44
|
+
"ship_date": order.get("shipDate"),
|
|
45
|
+
"id": order.get("id"),
|
|
46
|
+
"pet_id": order.get("petId"),
|
|
47
|
+
"quantity": order.get("quantity"),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
})
|
|
File without changes
|