motia 0.15.5-beta.174-720958 → 0.15.5-beta.174-096320
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/cli.mjs +9 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/cloud/config-utils.mjs +1 -1
- package/dist/cloud/config-utils.mjs.map +1 -1
- package/dist/cloud/new-deployment/build.mjs +11 -1
- package/dist/cloud/new-deployment/build.mjs.map +1 -1
- package/dist/create/index.mjs +28 -12
- package/dist/create/index.mjs.map +1 -1
- package/dist/cursor-rules/dot-files/.claude/agents/motia-developer.md +20 -11
- package/dist/cursor-rules/dot-files/.cursor/architecture/architecture.mdc +124 -28
- package/dist/cursor-rules/dot-files/.cursor/rules/motia/motia-config.mdc +66 -0
- package/dist/cursor-rules/dot-files/AGENTS.md +31 -16
- package/dist/cursor-rules/dot-files/CLAUDE.md +7 -2
- package/dist/cursor-rules/dot-files/opencode.json +1 -0
- package/dist/dev.mjs +8 -2
- package/dist/dev.mjs.map +1 -1
- package/dist/generate-locked-data.d.mts.map +1 -1
- package/dist/generate-locked-data.mjs +0 -2
- package/dist/generate-locked-data.mjs.map +1 -1
- package/dist/install.mjs +1 -1
- package/dist/plugins/install-plugin-dependencies.mjs +4 -3
- package/dist/plugins/install-plugin-dependencies.mjs.map +1 -1
- package/dist/start.mjs +8 -2
- package/dist/start.mjs.map +1 -1
- package/dist/utils/activate-python-env.mjs +9 -15
- package/dist/utils/activate-python-env.mjs.map +1 -1
- package/dist/utils/get-package-manager.mjs +42 -5
- package/dist/utils/get-package-manager.mjs.map +1 -1
- package/dist/utils/validate-python-environment.mjs +81 -0
- package/dist/utils/validate-python-environment.mjs.map +1 -0
- package/dist/watcher.mjs +1 -3
- package/dist/watcher.mjs.map +1 -1
- package/package.json +8 -8
package/dist/cli.mjs
CHANGED
|
@@ -85,11 +85,19 @@ generate.command("step").description("Create a new step with interactive prompts
|
|
|
85
85
|
return createStep({ stepFilePath: arg.dir });
|
|
86
86
|
}));
|
|
87
87
|
generate.command("openapi").description("Generate OpenAPI spec for your project").option("-t, --title <title>", "Title for the OpenAPI document. Defaults to project name").option("-v, --version <version>", "Version for the OpenAPI document. Defaults to 1.0.0", "1.0.0").option("-o, --output <output>", "Output file for the OpenAPI document. Defaults to openapi.json", "openapi.json").action(wrapAction(async (options) => {
|
|
88
|
-
const { generateLockedData } = await import("./generate-locked-data.mjs");
|
|
88
|
+
const { generateLockedData, getStepFiles, getStreamFiles } = await import("./generate-locked-data.mjs");
|
|
89
|
+
const { validatePythonEnvironment } = await import("./utils/validate-python-environment.mjs");
|
|
90
|
+
const { activatePythonVenv } = await import("./utils/activate-python-env.mjs");
|
|
89
91
|
const { generateOpenApi } = await import("./openapi/generate.mjs");
|
|
90
92
|
const { MemoryStreamAdapterManager } = await import("@motiadev/core");
|
|
91
93
|
const baseDir = process.cwd();
|
|
92
94
|
const appConfig = await loadMotiaConfig(baseDir);
|
|
95
|
+
const hasPythonFiles = [...getStepFiles(baseDir), ...getStreamFiles(baseDir)].some((file) => file.endsWith(".py"));
|
|
96
|
+
if (!(await validatePythonEnvironment({
|
|
97
|
+
baseDir,
|
|
98
|
+
hasPythonFiles
|
|
99
|
+
})).success) process.exit(1);
|
|
100
|
+
if (hasPythonFiles) activatePythonVenv({ baseDir });
|
|
93
101
|
const apiSteps = (await generateLockedData({
|
|
94
102
|
projectDir: baseDir,
|
|
95
103
|
streamAdapter: new MemoryStreamAdapterManager(),
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport 'dotenv/config'\n\nimport { program } from 'commander'\nimport { type CliContext, handler } from './cloud/config-utils'\nimport './cloud/index'\nimport { loadMotiaConfig } from './load-motia-config'\nimport { wrapAction } from './utils/analytics'\nimport { version } from './version'\n\nconst defaultPort = 3000\nconst defaultHost = '0.0.0.0'\n\nprogram\n .command('version')\n .description('Display detailed version information')\n .action(() => {\n console.log(`Motia CLI v${version}`)\n process.exit(0)\n })\n\nprogram\n .command('create [name]')\n .description('Create a new motia project')\n .option('-t, --template <template>', 'The template to use for your project')\n .option('-p, --plugin', 'Create a plugin project')\n .option('-i, --interactive', 'Use interactive prompts to create project') // it's default\n .option('--skip-redis', 'Skip Redis binary installation and use external Redis')\n .action((projectName, options) => {\n const mergedArgs = { ...options, name: projectName }\n return handler(async (arg: any, context: CliContext) => {\n const { createInteractive } = await import('./create/interactive')\n await createInteractive(\n {\n name: arg.name,\n template: arg.template,\n plugin: !!arg.plugin,\n skipRedis: !!arg.skipRedis,\n },\n context,\n )\n })(mergedArgs)\n })\n\nprogram\n .command('rules')\n .command('pull')\n .description('Install essential AI development guides (AGENTS.md, CLAUDE.md) and optional Cursor IDE rules')\n .option('-f, --force', 'Overwrite existing files')\n .action(\n handler(async (arg: any, context: CliContext) => {\n const { pullRules } = await import('./create/pull-rules')\n await pullRules({ force: arg.force, rootDir: process.cwd() }, context)\n }),\n )\n\nprogram\n .command('generate-types')\n .description('Generate types.d.ts file for your project')\n .action(\n wrapAction(async () => {\n const { generateTypes } = await import('./generate-types')\n await generateTypes(process.cwd())\n process.exit(0)\n }),\n )\n\nprogram\n .command('install')\n .description('Sets up Python virtual environment and install dependencies')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(\n wrapAction(async (options: any) => {\n const { install } = await import('./install')\n await install({ isVerbose: options.verbose })\n }),\n )\n\nprogram\n .command('dev')\n .description('Start the development server')\n .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)\n .option('-H, --host [host]', 'The host address for the server', `${defaultHost}`)\n .option('-v, --disable-verbose', 'Disable verbose logging')\n .option('-d, --debug', 'Enable debug logging')\n .option('-m, --mermaid', 'Enable mermaid diagram generation')\n .option('--motia-dir <path>', 'Path where .motia folder will be created')\n .action(\n wrapAction(async (arg: any) => {\n if (arg.debug) {\n console.log('🔍 Debug logging enabled')\n process.env.LOG_LEVEL = 'debug'\n }\n\n const port = arg.port ? parseInt(arg.port) : defaultPort\n const host = arg.host ? arg.host : defaultHost\n const { dev } = await import('./dev')\n await dev(port, host, arg.disableVerbose, arg.mermaid, arg.motiaDir)\n }),\n )\n\nprogram\n .command('start')\n .description('Start a server to run your Motia project')\n .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)\n .option('-H, --host [host]', 'The host address for the server', `${defaultHost}`)\n .option('-v, --disable-verbose', 'Disable verbose logging')\n .option('-d, --debug', 'Enable debug logging')\n .option('--motia-dir <path>', 'Path where .motia folder will be created')\n .action(\n wrapAction(async (arg: any) => {\n if (arg.debug) {\n console.log('🔍 Debug logging enabled')\n process.env.LOG_LEVEL = 'debug'\n }\n\n const port = arg.port ? parseInt(arg.port) : defaultPort\n const host = arg.host ? arg.host : defaultHost\n const { start } = await import('./start')\n await start(port, host, arg.disableVerbose, arg.motiaDir)\n }),\n )\n\nprogram\n .command('emit')\n .description('Emit an event to the Motia server')\n .requiredOption('--topic <topic>', 'Event topic/type to emit')\n .requiredOption('--message <message>', 'Event payload as JSON string')\n .option('-p, --port <number>', 'Port number (default: 3000)')\n .action(\n wrapAction(async (options: any) => {\n const port = options.port || 3000\n const url = `http://localhost:${port}/emit`\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n topic: options.topic,\n data: JSON.parse(options.message),\n }),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n\n const result = await response.json()\n console.log('Event emitted successfully:', result)\n }),\n )\n\nconst generate = program.command('generate').description('Generate motia resources')\n\ngenerate\n .command('step')\n .description('Create a new step with interactive prompts')\n .option('-d, --dir <step file path>', 'The path relative to the steps directory, used to create the step file')\n .action(\n wrapAction(async (arg: any) => {\n const { createStep } = await import('./create-step/index')\n return createStep({\n stepFilePath: arg.dir,\n })\n }),\n )\n\ngenerate\n .command('openapi')\n .description('Generate OpenAPI spec for your project')\n .option('-t, --title <title>', 'Title for the OpenAPI document. Defaults to project name')\n .option('-v, --version <version>', 'Version for the OpenAPI document. Defaults to 1.0.0', '1.0.0')\n .option('-o, --output <output>', 'Output file for the OpenAPI document. Defaults to openapi.json', 'openapi.json')\n .action(\n wrapAction(async (options: any) => {\n const { generateLockedData } = await import('./generate-locked-data')\n const { generateOpenApi } = await import('./openapi/generate')\n const { MemoryStreamAdapterManager } = await import('@motiadev/core')\n\n const baseDir = process.cwd()\n const appConfig = await loadMotiaConfig(baseDir)\n\n const lockedData = await generateLockedData({\n projectDir: baseDir,\n streamAdapter: new MemoryStreamAdapterManager(),\n streamAuth: appConfig.streamAuth,\n printerType: 'disabled',\n })\n const apiSteps = lockedData.apiSteps()\n\n generateOpenApi(process.cwd(), apiSteps, options.title, options.version, options.output)\n process.exit(0)\n }),\n )\n\nconst docker = program.command('docker').description('Motia docker commands')\n\ndocker\n .command('setup')\n .description('Setup a motia-docker for your project')\n .action(\n wrapAction(async () => {\n const { setup } = await import('./docker/setup')\n await setup()\n process.exit(0)\n }),\n )\n\ndocker\n .command('run')\n .description('Build and run your project in a docker container')\n .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)\n .option('-n, --project-name <project name>', 'The name for your project')\n .option('-s, --skip-build', 'Skip docker build')\n .action(\n wrapAction(async (arg: any) => {\n const { run } = await import('./docker/run')\n await run(arg.port, arg.projectName, arg.skipBuild)\n process.exit(0)\n }),\n )\n\ndocker\n .command('build')\n .description('Build your project in a docker container')\n .option('-n, --project-name <project name>', 'The name for your project')\n .action(\n wrapAction(async (arg: any) => {\n const { build } = await import('./docker/build')\n await build(arg.projectName)\n process.exit(0)\n }),\n )\n\nprogram.version(version, '-V, --version', 'Output the current version')\nprogram.parseAsync(process.argv).catch(() => {\n process.exit(1)\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport 'dotenv/config'\n\nimport { program } from 'commander'\nimport { type CliContext, handler } from './cloud/config-utils'\nimport './cloud/index'\nimport { loadMotiaConfig } from './load-motia-config'\nimport { wrapAction } from './utils/analytics'\nimport { version } from './version'\n\nconst defaultPort = 3000\nconst defaultHost = '0.0.0.0'\n\nprogram\n .command('version')\n .description('Display detailed version information')\n .action(() => {\n console.log(`Motia CLI v${version}`)\n process.exit(0)\n })\n\nprogram\n .command('create [name]')\n .description('Create a new motia project')\n .option('-t, --template <template>', 'The template to use for your project')\n .option('-p, --plugin', 'Create a plugin project')\n .option('-i, --interactive', 'Use interactive prompts to create project') // it's default\n .option('--skip-redis', 'Skip Redis binary installation and use external Redis')\n .action((projectName, options) => {\n const mergedArgs = { ...options, name: projectName }\n return handler(async (arg: any, context: CliContext) => {\n const { createInteractive } = await import('./create/interactive')\n await createInteractive(\n {\n name: arg.name,\n template: arg.template,\n plugin: !!arg.plugin,\n skipRedis: !!arg.skipRedis,\n },\n context,\n )\n })(mergedArgs)\n })\n\nprogram\n .command('rules')\n .command('pull')\n .description('Install essential AI development guides (AGENTS.md, CLAUDE.md) and optional Cursor IDE rules')\n .option('-f, --force', 'Overwrite existing files')\n .action(\n handler(async (arg: any, context: CliContext) => {\n const { pullRules } = await import('./create/pull-rules')\n await pullRules({ force: arg.force, rootDir: process.cwd() }, context)\n }),\n )\n\nprogram\n .command('generate-types')\n .description('Generate types.d.ts file for your project')\n .action(\n wrapAction(async () => {\n const { generateTypes } = await import('./generate-types')\n await generateTypes(process.cwd())\n process.exit(0)\n }),\n )\n\nprogram\n .command('install')\n .description('Sets up Python virtual environment and install dependencies')\n .option('-v, --verbose', 'Enable verbose logging')\n .action(\n wrapAction(async (options: any) => {\n const { install } = await import('./install')\n await install({ isVerbose: options.verbose })\n }),\n )\n\nprogram\n .command('dev')\n .description('Start the development server')\n .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)\n .option('-H, --host [host]', 'The host address for the server', `${defaultHost}`)\n .option('-v, --disable-verbose', 'Disable verbose logging')\n .option('-d, --debug', 'Enable debug logging')\n .option('-m, --mermaid', 'Enable mermaid diagram generation')\n .option('--motia-dir <path>', 'Path where .motia folder will be created')\n .action(\n wrapAction(async (arg: any) => {\n if (arg.debug) {\n console.log('🔍 Debug logging enabled')\n process.env.LOG_LEVEL = 'debug'\n }\n\n const port = arg.port ? parseInt(arg.port) : defaultPort\n const host = arg.host ? arg.host : defaultHost\n const { dev } = await import('./dev')\n await dev(port, host, arg.disableVerbose, arg.mermaid, arg.motiaDir)\n }),\n )\n\nprogram\n .command('start')\n .description('Start a server to run your Motia project')\n .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)\n .option('-H, --host [host]', 'The host address for the server', `${defaultHost}`)\n .option('-v, --disable-verbose', 'Disable verbose logging')\n .option('-d, --debug', 'Enable debug logging')\n .option('--motia-dir <path>', 'Path where .motia folder will be created')\n .action(\n wrapAction(async (arg: any) => {\n if (arg.debug) {\n console.log('🔍 Debug logging enabled')\n process.env.LOG_LEVEL = 'debug'\n }\n\n const port = arg.port ? parseInt(arg.port) : defaultPort\n const host = arg.host ? arg.host : defaultHost\n const { start } = await import('./start')\n await start(port, host, arg.disableVerbose, arg.motiaDir)\n }),\n )\n\nprogram\n .command('emit')\n .description('Emit an event to the Motia server')\n .requiredOption('--topic <topic>', 'Event topic/type to emit')\n .requiredOption('--message <message>', 'Event payload as JSON string')\n .option('-p, --port <number>', 'Port number (default: 3000)')\n .action(\n wrapAction(async (options: any) => {\n const port = options.port || 3000\n const url = `http://localhost:${port}/emit`\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n topic: options.topic,\n data: JSON.parse(options.message),\n }),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n\n const result = await response.json()\n console.log('Event emitted successfully:', result)\n }),\n )\n\nconst generate = program.command('generate').description('Generate motia resources')\n\ngenerate\n .command('step')\n .description('Create a new step with interactive prompts')\n .option('-d, --dir <step file path>', 'The path relative to the steps directory, used to create the step file')\n .action(\n wrapAction(async (arg: any) => {\n const { createStep } = await import('./create-step/index')\n return createStep({\n stepFilePath: arg.dir,\n })\n }),\n )\n\ngenerate\n .command('openapi')\n .description('Generate OpenAPI spec for your project')\n .option('-t, --title <title>', 'Title for the OpenAPI document. Defaults to project name')\n .option('-v, --version <version>', 'Version for the OpenAPI document. Defaults to 1.0.0', '1.0.0')\n .option('-o, --output <output>', 'Output file for the OpenAPI document. Defaults to openapi.json', 'openapi.json')\n .action(\n wrapAction(async (options: any) => {\n const { generateLockedData, getStepFiles, getStreamFiles } = await import('./generate-locked-data')\n const { validatePythonEnvironment } = await import('./utils/validate-python-environment')\n const { activatePythonVenv } = await import('./utils/activate-python-env')\n const { generateOpenApi } = await import('./openapi/generate')\n const { MemoryStreamAdapterManager } = await import('@motiadev/core')\n\n const baseDir = process.cwd()\n const appConfig = await loadMotiaConfig(baseDir)\n\n const stepFiles = [...getStepFiles(baseDir), ...getStreamFiles(baseDir)]\n const hasPythonFiles = stepFiles.some((file) => file.endsWith('.py'))\n\n const pythonValidation = await validatePythonEnvironment({ baseDir, hasPythonFiles })\n if (!pythonValidation.success) {\n process.exit(1)\n }\n\n if (hasPythonFiles) {\n activatePythonVenv({ baseDir })\n }\n\n const lockedData = await generateLockedData({\n projectDir: baseDir,\n streamAdapter: new MemoryStreamAdapterManager(),\n streamAuth: appConfig.streamAuth,\n printerType: 'disabled',\n })\n const apiSteps = lockedData.apiSteps()\n\n generateOpenApi(process.cwd(), apiSteps, options.title, options.version, options.output)\n process.exit(0)\n }),\n )\n\nconst docker = program.command('docker').description('Motia docker commands')\n\ndocker\n .command('setup')\n .description('Setup a motia-docker for your project')\n .action(\n wrapAction(async () => {\n const { setup } = await import('./docker/setup')\n await setup()\n process.exit(0)\n }),\n )\n\ndocker\n .command('run')\n .description('Build and run your project in a docker container')\n .option('-p, --port <port>', 'The port to run the server on', `${defaultPort}`)\n .option('-n, --project-name <project name>', 'The name for your project')\n .option('-s, --skip-build', 'Skip docker build')\n .action(\n wrapAction(async (arg: any) => {\n const { run } = await import('./docker/run')\n await run(arg.port, arg.projectName, arg.skipBuild)\n process.exit(0)\n }),\n )\n\ndocker\n .command('build')\n .description('Build your project in a docker container')\n .option('-n, --project-name <project name>', 'The name for your project')\n .action(\n wrapAction(async (arg: any) => {\n const { build } = await import('./docker/build')\n await build(arg.projectName)\n process.exit(0)\n }),\n )\n\nprogram.version(version, '-V, --version', 'Output the current version')\nprogram.parseAsync(process.argv).catch(() => {\n process.exit(1)\n})\n"],"mappings":"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"}
|
|
@@ -41,7 +41,7 @@ function handler(handler$1) {
|
|
|
41
41
|
logCliError(commandName, error);
|
|
42
42
|
await flush().promise.catch(() => {});
|
|
43
43
|
if (error instanceof Error) {
|
|
44
|
-
context.log("error", (message) => message.tag("failed").append(error.message));
|
|
44
|
+
error.message && context.log("error", (message) => message.tag("failed").append(error.message));
|
|
45
45
|
context.exit(1);
|
|
46
46
|
} else context.exitWithError("An error occurred", error);
|
|
47
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-utils.mjs","names":["commandParts: string[]","handler","error: any"],"sources":["../../src/cloud/config-utils.ts"],"sourcesContent":["import { flush } from '@amplitude/analytics-node'\nimport { logCliError } from '../utils/analytics'\nimport { CLIOutputManager, type Message } from './cli-output-manager'\n\nexport type { Message }\n\nconst getCommandName = (): string => {\n const args = process.argv.slice(2)\n const commandParts: string[] = []\n\n for (let i = 0; i < args.length && i < 3; i++) {\n const arg = args[i]\n if (!arg.startsWith('-') && !arg.startsWith('--')) {\n commandParts.push(arg)\n } else {\n break\n }\n }\n\n return commandParts.join(' ') || 'unknown'\n}\n\nexport class CliContext {\n private readonly output = new CLIOutputManager()\n\n log(id: string, callback: (message: Message) => void) {\n this.output.log(id, callback)\n }\n\n exitWithError(msg: string, error?: unknown): never {\n this.output.log('error', (message) => {\n message.tag('failed').append(msg)\n\n if (error) {\n message.box([error instanceof Error ? error.message : 'Unknown error'], 'red')\n }\n })\n process.exit(1)\n }\n\n exit(code: number): never {\n process.exit(code)\n }\n}\n\nexport type CliHandler = <TArgs extends Record<string, any>>(args: TArgs, context: CliContext) => Promise<void>\n\nexport function handler(handler: CliHandler): (args: Record<string, any>) => Promise<void> {\n return async (args: Record<string, unknown>) => {\n const context = new CliContext()\n const commandName = getCommandName()\n\n try {\n await handler(args, context)\n } catch (error: any) {\n logCliError(commandName, error)\n await flush().promise.catch(() => {\n // Silently fail\n })\n if (error instanceof Error) {\n context.log('error', (message) => message.tag('failed').append(error.message))\n context.exit(1)\n } else {\n context.exitWithError('An error occurred', error)\n }\n }\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,uBAA+B;CACnC,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;CAClC,MAAMA,eAAyB,EAAE;AAEjC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,UAAU,IAAI,GAAG,KAAK;EAC7C,MAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAI,WAAW,IAAI,IAAI,CAAC,IAAI,WAAW,KAAK,CAC/C,cAAa,KAAK,IAAI;MAEtB;;AAIJ,QAAO,aAAa,KAAK,IAAI,IAAI;;AAGnC,IAAa,aAAb,MAAwB;;gBACI,IAAI,kBAAkB;;CAEhD,IAAI,IAAY,UAAsC;AACpD,OAAK,OAAO,IAAI,IAAI,SAAS;;CAG/B,cAAc,KAAa,OAAwB;AACjD,OAAK,OAAO,IAAI,UAAU,YAAY;AACpC,WAAQ,IAAI,SAAS,CAAC,OAAO,IAAI;AAEjC,OAAI,MACF,SAAQ,IAAI,CAAC,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB,EAAE,MAAM;IAEhF;AACF,UAAQ,KAAK,EAAE;;CAGjB,KAAK,MAAqB;AACxB,UAAQ,KAAK,KAAK;;;AAMtB,SAAgB,QAAQ,WAAmE;AACzF,QAAO,OAAO,SAAkC;EAC9C,MAAM,UAAU,IAAI,YAAY;EAChC,MAAM,cAAc,gBAAgB;AAEpC,MAAI;AACF,SAAMC,UAAQ,MAAM,QAAQ;WACrBC,OAAY;AACnB,eAAY,aAAa,MAAM;AAC/B,SAAM,OAAO,CAAC,QAAQ,YAAY,GAEhC;AACF,OAAI,iBAAiB,OAAO;AAC1B,
|
|
1
|
+
{"version":3,"file":"config-utils.mjs","names":["commandParts: string[]","handler","error: any"],"sources":["../../src/cloud/config-utils.ts"],"sourcesContent":["import { flush } from '@amplitude/analytics-node'\nimport { logCliError } from '../utils/analytics'\nimport { CLIOutputManager, type Message } from './cli-output-manager'\n\nexport type { Message }\n\nconst getCommandName = (): string => {\n const args = process.argv.slice(2)\n const commandParts: string[] = []\n\n for (let i = 0; i < args.length && i < 3; i++) {\n const arg = args[i]\n if (!arg.startsWith('-') && !arg.startsWith('--')) {\n commandParts.push(arg)\n } else {\n break\n }\n }\n\n return commandParts.join(' ') || 'unknown'\n}\n\nexport class CliContext {\n private readonly output = new CLIOutputManager()\n\n log(id: string, callback: (message: Message) => void) {\n this.output.log(id, callback)\n }\n\n exitWithError(msg: string, error?: unknown): never {\n this.output.log('error', (message) => {\n message.tag('failed').append(msg)\n\n if (error) {\n message.box([error instanceof Error ? error.message : 'Unknown error'], 'red')\n }\n })\n process.exit(1)\n }\n\n exit(code: number): never {\n process.exit(code)\n }\n}\n\nexport type CliHandler = <TArgs extends Record<string, any>>(args: TArgs, context: CliContext) => Promise<void>\n\nexport function handler(handler: CliHandler): (args: Record<string, any>) => Promise<void> {\n return async (args: Record<string, unknown>) => {\n const context = new CliContext()\n const commandName = getCommandName()\n\n try {\n await handler(args, context)\n } catch (error: any) {\n logCliError(commandName, error)\n await flush().promise.catch(() => {\n // Silently fail\n })\n if (error instanceof Error) {\n error.message && context.log('error', (message) => message.tag('failed').append(error.message))\n context.exit(1)\n } else {\n context.exitWithError('An error occurred', error)\n }\n }\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,uBAA+B;CACnC,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;CAClC,MAAMA,eAAyB,EAAE;AAEjC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,UAAU,IAAI,GAAG,KAAK;EAC7C,MAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAI,WAAW,IAAI,IAAI,CAAC,IAAI,WAAW,KAAK,CAC/C,cAAa,KAAK,IAAI;MAEtB;;AAIJ,QAAO,aAAa,KAAK,IAAI,IAAI;;AAGnC,IAAa,aAAb,MAAwB;;gBACI,IAAI,kBAAkB;;CAEhD,IAAI,IAAY,UAAsC;AACpD,OAAK,OAAO,IAAI,IAAI,SAAS;;CAG/B,cAAc,KAAa,OAAwB;AACjD,OAAK,OAAO,IAAI,UAAU,YAAY;AACpC,WAAQ,IAAI,SAAS,CAAC,OAAO,IAAI;AAEjC,OAAI,MACF,SAAQ,IAAI,CAAC,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB,EAAE,MAAM;IAEhF;AACF,UAAQ,KAAK,EAAE;;CAGjB,KAAK,MAAqB;AACxB,UAAQ,KAAK,KAAK;;;AAMtB,SAAgB,QAAQ,WAAmE;AACzF,QAAO,OAAO,SAAkC;EAC9C,MAAM,UAAU,IAAI,YAAY;EAChC,MAAM,cAAc,gBAAgB;AAEpC,MAAI;AACF,SAAMC,UAAQ,MAAM,QAAQ;WACrBC,OAAY;AACnB,eAAY,aAAa,MAAM;AAC/B,SAAM,OAAO,CAAC,QAAQ,YAAY,GAEhC;AACF,OAAI,iBAAiB,OAAO;AAC1B,UAAM,WAAW,QAAQ,IAAI,UAAU,YAAY,QAAQ,IAAI,SAAS,CAAC,OAAO,MAAM,QAAQ,CAAC;AAC/F,YAAQ,KAAK,EAAE;SAEf,SAAQ,cAAc,qBAAqB,MAAM"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { BuildError, BuildErrorType } from "../../utils/errors/build.error.mjs";
|
|
2
2
|
import { collectFlows, getStepFiles, getStreamFiles } from "../../generate-locked-data.mjs";
|
|
3
|
+
import { activatePythonVenv } from "../../utils/activate-python-env.mjs";
|
|
4
|
+
import { validatePythonEnvironment } from "../../utils/validate-python-environment.mjs";
|
|
3
5
|
import { Builder } from "../build/builder.mjs";
|
|
4
6
|
import { distDir, projectDir, stepsConfigPath } from "./constants.mjs";
|
|
5
7
|
import { NodeBuilder } from "../build/builders/node/index.mjs";
|
|
@@ -23,7 +25,15 @@ const build = async (listener) => {
|
|
|
23
25
|
});
|
|
24
26
|
fs.mkdirSync(distDir, { recursive: true });
|
|
25
27
|
const lockedData = new LockedData(projectDir, new MemoryStreamAdapterManager(), new NoPrinter());
|
|
26
|
-
|
|
28
|
+
const hasPython = hasPythonSteps([...stepFiles, ...streamFiles]);
|
|
29
|
+
if (!(await validatePythonEnvironment({
|
|
30
|
+
baseDir: projectDir,
|
|
31
|
+
hasPythonFiles: hasPython
|
|
32
|
+
})).success) throw new BuildError(BuildErrorType.COMPILATION, void 0, "");
|
|
33
|
+
if (hasPython) {
|
|
34
|
+
activatePythonVenv({ baseDir: projectDir });
|
|
35
|
+
builder.registerBuilder("python", new PythonBuilder(builder, listener));
|
|
36
|
+
}
|
|
27
37
|
if ((await collectFlows(projectDir, lockedData).catch((err) => {
|
|
28
38
|
const finalMessage = `${err.filePath ? `Build error in ${err.filePath}` : "Build error"}\nPlease check the logs above for details`;
|
|
29
39
|
throw new BuildError(BuildErrorType.COMPILATION, err.filePath, finalMessage, err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.mjs","names":["stepsFile: StepsConfigFile"],"sources":["../../../src/cloud/new-deployment/build.ts"],"sourcesContent":["import { isApiStep, LockedData, MemoryStreamAdapterManager, NoPrinter } from '@motiadev/core'\nimport fs from 'fs'\nimport { collectFlows, getStepFiles, getStreamFiles } from '../../generate-locked-data'\nimport { BuildError, BuildErrorType } from '../../utils/errors/build.error'\nimport { Builder, type StepsConfigFile } from '../build/builder'\nimport { NodeBuilder } from '../build/builders/node/index'\nimport { PythonBuilder } from '../build/builders/python/index'\nimport { distDir, projectDir, stepsConfigPath } from './constants'\nimport type { BuildListener } from './listeners/listener.types'\n\nconst hasPythonSteps = (stepFiles: string[]) => {\n return stepFiles.some((file) => file.endsWith('.py'))\n}\n\nexport const build = async (listener: BuildListener): Promise<Builder> => {\n const builder = new Builder(projectDir, listener)\n const stepFiles = getStepFiles(projectDir)\n const streamFiles = getStreamFiles(projectDir)\n\n if (stepFiles.length === 0) {\n throw new Error('Project contains no steps, please add some steps before building')\n }\n\n // Register language-specific builders\n builder.registerBuilder('node', new NodeBuilder(builder, listener))\n\n fs.rmSync(distDir, { recursive: true, force: true })\n fs.mkdirSync(distDir, { recursive: true })\n\n const lockedData = new LockedData(projectDir, new MemoryStreamAdapterManager(), new NoPrinter())\n\n
|
|
1
|
+
{"version":3,"file":"build.mjs","names":["stepsFile: StepsConfigFile"],"sources":["../../../src/cloud/new-deployment/build.ts"],"sourcesContent":["import { isApiStep, LockedData, MemoryStreamAdapterManager, NoPrinter } from '@motiadev/core'\nimport fs from 'fs'\nimport { collectFlows, getStepFiles, getStreamFiles } from '../../generate-locked-data'\nimport { activatePythonVenv } from '../../utils/activate-python-env'\nimport { BuildError, BuildErrorType } from '../../utils/errors/build.error'\nimport { validatePythonEnvironment } from '../../utils/validate-python-environment'\nimport { Builder, type StepsConfigFile } from '../build/builder'\nimport { NodeBuilder } from '../build/builders/node/index'\nimport { PythonBuilder } from '../build/builders/python/index'\nimport { distDir, projectDir, stepsConfigPath } from './constants'\nimport type { BuildListener } from './listeners/listener.types'\n\nconst hasPythonSteps = (stepFiles: string[]) => {\n return stepFiles.some((file) => file.endsWith('.py'))\n}\n\nexport const build = async (listener: BuildListener): Promise<Builder> => {\n const builder = new Builder(projectDir, listener)\n const stepFiles = getStepFiles(projectDir)\n const streamFiles = getStreamFiles(projectDir)\n\n if (stepFiles.length === 0) {\n throw new Error('Project contains no steps, please add some steps before building')\n }\n\n // Register language-specific builders\n builder.registerBuilder('node', new NodeBuilder(builder, listener))\n\n fs.rmSync(distDir, { recursive: true, force: true })\n fs.mkdirSync(distDir, { recursive: true })\n\n const lockedData = new LockedData(projectDir, new MemoryStreamAdapterManager(), new NoPrinter())\n\n const hasPython = hasPythonSteps([...stepFiles, ...streamFiles])\n const pythonValidation = await validatePythonEnvironment({ baseDir: projectDir, hasPythonFiles: hasPython })\n if (!pythonValidation.success) {\n throw new BuildError(BuildErrorType.COMPILATION, undefined, '')\n }\n\n if (hasPython) {\n activatePythonVenv({ baseDir: projectDir })\n builder.registerBuilder('python', new PythonBuilder(builder, listener))\n }\n\n const invalidSteps = await collectFlows(projectDir, lockedData).catch((err) => {\n const errorMessage = err.filePath ? `Build error in ${err.filePath}` : 'Build error'\n\n const finalMessage = `${errorMessage}\\nPlease check the logs above for details`\n\n throw new BuildError(BuildErrorType.COMPILATION, err.filePath, finalMessage, err)\n })\n\n if (invalidSteps.length > 0) {\n throw new Error('Project contains invalid steps, please fix them before building')\n }\n\n await Promise.all(lockedData.activeSteps.map((step) => builder.buildStep(step)))\n await builder.buildApiSteps(lockedData.activeSteps.filter(isApiStep))\n\n const streams = lockedData.listStreams()\n\n for (const stream of streams) {\n if (stream.config.baseConfig.storageType === 'default') {\n builder.registerStateStream(stream)\n } else {\n listener.onWarning(stream.filePath, 'Custom streams are not supported yet in the cloud')\n }\n }\n\n const stepsFile: StepsConfigFile = {\n steps: builder.stepsConfig,\n streams: builder.streamsConfig,\n routers: builder.routersConfig,\n }\n fs.writeFileSync(stepsConfigPath, JSON.stringify(stepsFile, null, 2))\n\n return builder\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,kBAAkB,cAAwB;AAC9C,QAAO,UAAU,MAAM,SAAS,KAAK,SAAS,MAAM,CAAC;;AAGvD,MAAa,QAAQ,OAAO,aAA8C;CACxE,MAAM,UAAU,IAAI,QAAQ,YAAY,SAAS;CACjD,MAAM,YAAY,aAAa,WAAW;CAC1C,MAAM,cAAc,eAAe,WAAW;AAE9C,KAAI,UAAU,WAAW,EACvB,OAAM,IAAI,MAAM,mEAAmE;AAIrF,SAAQ,gBAAgB,QAAQ,IAAI,YAAY,SAAS,SAAS,CAAC;AAEnE,IAAG,OAAO,SAAS;EAAE,WAAW;EAAM,OAAO;EAAM,CAAC;AACpD,IAAG,UAAU,SAAS,EAAE,WAAW,MAAM,CAAC;CAE1C,MAAM,aAAa,IAAI,WAAW,YAAY,IAAI,4BAA4B,EAAE,IAAI,WAAW,CAAC;CAEhG,MAAM,YAAY,eAAe,CAAC,GAAG,WAAW,GAAG,YAAY,CAAC;AAEhE,KAAI,EADqB,MAAM,0BAA0B;EAAE,SAAS;EAAY,gBAAgB;EAAW,CAAC,EACtF,QACpB,OAAM,IAAI,WAAW,eAAe,aAAa,QAAW,GAAG;AAGjE,KAAI,WAAW;AACb,qBAAmB,EAAE,SAAS,YAAY,CAAC;AAC3C,UAAQ,gBAAgB,UAAU,IAAI,cAAc,SAAS,SAAS,CAAC;;AAWzE,MARqB,MAAM,aAAa,YAAY,WAAW,CAAC,OAAO,QAAQ;EAG7E,MAAM,eAAe,GAFA,IAAI,WAAW,kBAAkB,IAAI,aAAa,cAElC;AAErC,QAAM,IAAI,WAAW,eAAe,aAAa,IAAI,UAAU,cAAc,IAAI;GACjF,EAEe,SAAS,EACxB,OAAM,IAAI,MAAM,kEAAkE;AAGpF,OAAM,QAAQ,IAAI,WAAW,YAAY,KAAK,SAAS,QAAQ,UAAU,KAAK,CAAC,CAAC;AAChF,OAAM,QAAQ,cAAc,WAAW,YAAY,OAAO,UAAU,CAAC;CAErE,MAAM,UAAU,WAAW,aAAa;AAExC,MAAK,MAAM,UAAU,QACnB,KAAI,OAAO,OAAO,WAAW,gBAAgB,UAC3C,SAAQ,oBAAoB,OAAO;KAEnC,UAAS,UAAU,OAAO,UAAU,oDAAoD;CAI5F,MAAMA,YAA6B;EACjC,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,SAAS,QAAQ;EAClB;AACD,IAAG,cAAc,iBAAiB,KAAK,UAAU,WAAW,MAAM,EAAE,CAAC;AAErE,QAAO"}
|
package/dist/create/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { executeCommand } from "../utils/execute-command.mjs";
|
|
2
1
|
import { version } from "../version.mjs";
|
|
2
|
+
import { executeCommand } from "../utils/execute-command.mjs";
|
|
3
|
+
import { checkIfDirectoryExists, checkIfFileExists } from "./utils.mjs";
|
|
4
|
+
import { getPackageManager, getPackageManagerFromEnv } from "../utils/get-package-manager.mjs";
|
|
3
5
|
import { generateTypes } from "../generate-types.mjs";
|
|
4
6
|
import { pythonInstall } from "../install.mjs";
|
|
5
7
|
import { pluginDependencies } from "../plugins/plugin-dependencies.mjs";
|
|
6
|
-
import { checkIfDirectoryExists, checkIfFileExists } from "./utils.mjs";
|
|
7
|
-
import { getPackageManager } from "../utils/get-package-manager.mjs";
|
|
8
8
|
import { pullRules } from "./pull-rules.mjs";
|
|
9
9
|
import { setupTemplate } from "./setup-template.mjs";
|
|
10
10
|
import fs from "fs";
|
|
@@ -19,7 +19,8 @@ const installRequiredDependencies = async (packageManager, rootDir, context) =>
|
|
|
19
19
|
const installCommand = {
|
|
20
20
|
npm: "npm install --save",
|
|
21
21
|
yarn: "yarn add",
|
|
22
|
-
pnpm: "pnpm add"
|
|
22
|
+
pnpm: "pnpm add",
|
|
23
|
+
bun: "bun add"
|
|
23
24
|
}[packageManager];
|
|
24
25
|
const dependencies = [
|
|
25
26
|
`motia@${version}`,
|
|
@@ -40,13 +41,12 @@ const installRequiredDependencies = async (packageManager, rootDir, context) =>
|
|
|
40
41
|
console.error("❌ Failed to install dependencies:", error);
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
|
-
const preparePackageManager = async (rootDir, context) => {
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} else context.log("package-manager-using-default", (message) => message.tag("info").append("Using default package manager").append(packageManager, "gray"));
|
|
44
|
+
const preparePackageManager = async (rootDir, context, detectFromParent = false) => {
|
|
45
|
+
const detectionDir = detectFromParent ? process.cwd() : rootDir;
|
|
46
|
+
const envPackageManager = getPackageManagerFromEnv();
|
|
47
|
+
const packageManager = getPackageManager(detectionDir);
|
|
48
|
+
if (!envPackageManager && packageManager === "npm" && !checkIfFileExists(detectionDir, "package-lock.json")) context.log("package-manager-using-default", (message) => message.tag("info").append("Using default package manager").append(packageManager, "gray"));
|
|
49
|
+
else context.log("package-manager-detected", (message) => message.tag("info").append("Detected package manager").append(packageManager, "gray"));
|
|
50
50
|
return packageManager;
|
|
51
51
|
};
|
|
52
52
|
const installNodeDependencies = async (rootDir, context) => {
|
|
@@ -162,7 +162,23 @@ const create = async ({ projectName, template, cursorEnabled, context, skipRedis
|
|
|
162
162
|
packageManager = await installNodeDependencies(rootDir, context);
|
|
163
163
|
if (template.includes("python") || template.includes("multilang")) await pythonInstall({ baseDir: rootDir });
|
|
164
164
|
await generateTypes(rootDir);
|
|
165
|
-
} else
|
|
165
|
+
} else {
|
|
166
|
+
packageManager = await preparePackageManager(rootDir, context, true);
|
|
167
|
+
context.log("installing-plugin-dependencies", (message) => message.tag("info").append("Installing plugin dependencies..."));
|
|
168
|
+
const installCommand = {
|
|
169
|
+
npm: "npm install",
|
|
170
|
+
yarn: "yarn",
|
|
171
|
+
pnpm: "pnpm install",
|
|
172
|
+
bun: "bun install"
|
|
173
|
+
}[packageManager];
|
|
174
|
+
try {
|
|
175
|
+
await executeCommand(installCommand, rootDir);
|
|
176
|
+
context.log("plugin-dependencies-installed", (message) => message.tag("success").append("Plugin dependencies installed"));
|
|
177
|
+
} catch (error) {
|
|
178
|
+
context.log("failed-to-install-plugin-dependencies", (message) => message.tag("failed").append("Failed to install plugin dependencies"));
|
|
179
|
+
console.error(error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
166
182
|
const projectDirName = path.basename(rootDir);
|
|
167
183
|
const devCommand = `${packageManager} run dev`;
|
|
168
184
|
const port = 3e3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["packageManager: string"],"sources":["../../src/create/index.ts"],"sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport pc from 'picocolors'\nimport { fileURLToPath } from 'url'\nimport type { CliContext, Message } from '../cloud/config-utils'\nimport { generateTypes } from '../generate-types'\nimport { pythonInstall } from '../install'\nimport { pluginDependencies } from '../plugins/plugin-dependencies'\nimport { executeCommand } from '../utils/execute-command'\nimport { getPackageManager } from '../utils/get-package-manager'\nimport { version } from '../version'\nimport { pullRules } from './pull-rules'\nimport { setupTemplate } from './setup-template'\nimport { checkIfDirectoryExists, checkIfFileExists } from './utils'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\nconst installRequiredDependencies = async (packageManager: string, rootDir: string, context: CliContext) => {\n context.log('installing-dependencies', (message: Message) => message.tag('info').append('Installing dependencies...'))\n\n const installCommand = {\n npm: 'npm install --save',\n yarn: 'yarn add',\n pnpm: 'pnpm add',\n }[packageManager]\n\n const dependencies = [\n `motia@${version}`,\n 'zod@4.1.12',\n `@motiadev/adapter-bullmq-events@${version}`,\n ...pluginDependencies.map((dep: string) => `${dep}@${version}`),\n ].join(' ')\n\n const devDependencies = ['ts-node@10.9.2', 'typescript@5.7.3', '@types/react@19.1.1'].join(' ')\n\n try {\n await executeCommand(`${installCommand} ${dependencies}`, rootDir)\n await executeCommand(`${installCommand} -D ${devDependencies}`, rootDir)\n\n context.log('dependencies-installed', (message: Message) => message.tag('success').append('Dependencies installed'))\n } catch (error) {\n console.error('❌ Failed to install dependencies:', error)\n }\n}\n\nconst preparePackageManager = async (rootDir: string, context: CliContext) => {\n let packageManager = 'npm'\n const detectedPackageManager = getPackageManager(rootDir)\n\n if (detectedPackageManager !== 'unknown') {\n context.log('package-manager-detected', (message: Message) =>\n message.tag('info').append('Detected package manager').append(detectedPackageManager, 'gray'),\n )\n packageManager = detectedPackageManager\n } else {\n context.log('package-manager-using-default', (message: Message) =>\n message.tag('info').append('Using default package manager').append(packageManager, 'gray'),\n )\n }\n\n return packageManager\n}\n\nconst installNodeDependencies = async (rootDir: string, context: CliContext) => {\n const packageManager = await preparePackageManager(rootDir, context)\n\n await installRequiredDependencies(packageManager, rootDir, context).catch((error: unknown) => {\n context.log('failed-to-install-dependencies', (message: Message) =>\n message.tag('failed').append('Failed to install dependencies'),\n )\n console.error(error)\n })\n\n return packageManager\n}\n\ntype Args = {\n projectName: string\n template: string\n cursorEnabled: boolean\n context: CliContext\n skipTutorialTemplates?: boolean\n skipRedis?: boolean\n}\n\nexport const create = async ({\n projectName,\n template,\n cursorEnabled,\n context,\n skipRedis = false,\n}: Args): Promise<void> => {\n console.log(\n '\\n\\n' +\n `\n _____ ______ ______ ______\n /'\\\\_/\\`\\\\/\\\\ __\\`\\\\/\\\\__ _\\\\/\\\\__ _\\\\ /\\\\ _ \\\\\n /\\\\ \\\\ \\\\ \\\\/\\\\ \\\\/_/\\\\ \\\\/\\\\/_/\\\\ \\\\/ \\\\ \\\\ \\\\L\\\\ \\\\\n \\\\ \\\\ \\\\__\\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ __ \\\\\n \\\\ \\\\ \\\\_/\\\\ \\\\ \\\\ \\\\_\\\\ \\\\ \\\\ \\\\ \\\\ \\\\_\\\\ \\\\__\\\\ \\\\ \\\\/\\\\ \\\\\n \\\\ \\\\_\\\\\\\\ \\\\_\\\\ \\\\_____\\\\ \\\\ \\\\_\\\\ /\\\\_____\\\\\\\\ \\\\_\\\\ \\\\_\\\\\n \\\\/_/ \\\\/_/\\\\/_____/ \\\\/_/ \\\\/_____/ \\\\/_/\\\\/_/\n ` +\n '\\n\\n',\n )\n\n const isCurrentDir = projectName === '.' || projectName === './' || projectName === '.\\\\'\n const rootDir = isCurrentDir ? process.cwd() : path.join(process.cwd(), projectName)\n const isPluginTemplate = template === 'plugin'\n\n process.env.REDISMS_DISABLE_POSTINSTALL = '1'\n if (!isCurrentDir && !checkIfDirectoryExists(rootDir)) {\n fs.mkdirSync(path.join(rootDir))\n context.log('directory-created', (message: Message) =>\n message.tag('success').append('Directory created ').append(projectName, 'gray'),\n )\n } else {\n context.log('directory-using', (message: Message) => message.tag('info').append('Using current directory'))\n }\n\n // Plugin template handles package.json differently (via template)\n if (!isPluginTemplate && !checkIfFileExists(rootDir, 'package.json')) {\n const finalProjectName =\n !projectName || projectName === '.' || projectName === './' || projectName === '.\\\\'\n ? path.basename(process.cwd())\n : projectName.trim()\n\n const packageJsonContent = {\n name: finalProjectName,\n description: '',\n type: 'module',\n scripts: {\n postinstall: 'motia install',\n dev: 'motia dev',\n start: 'motia start',\n 'generate-types': 'motia generate-types',\n build: 'motia build',\n clean: 'rm -rf dist node_modules python_modules .motia .mermaid',\n //'generate:config': 'motia get-config --output ./', TODO: doesnt work at the moment\n },\n keywords: ['motia'],\n }\n\n fs.writeFileSync(path.join(rootDir, 'package.json'), JSON.stringify(packageJsonContent, null, 2))\n\n context.log('package-json-created', (message: Message) =>\n message.tag('success').append('File').append('package.json', 'cyan').append('has been created.'),\n )\n } else if (!isPluginTemplate) {\n const packageJsonPath = path.join(rootDir, 'package.json')\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))\n\n if (!packageJson.scripts) {\n packageJson.scripts = {}\n }\n\n if (!packageJson.scripts.dev) {\n packageJson.scripts.dev = 'motia dev'\n } else {\n packageJson.scripts.olddev = packageJson.scripts.dev\n packageJson.scripts.dev = 'motia dev'\n context.log('dev-command-already-exists', (message: Message) =>\n message.tag('warning').append('dev command already exists in package.json'),\n )\n }\n\n fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))\n context.log('dev-command-updated', (message: Message) =>\n message\n .tag('success')\n .append('Updated')\n .append('dev', 'gray')\n .append('command to')\n .append('package.json', 'gray'),\n )\n }\n\n // Plugin template handles tsconfig.json via template\n if (!isPluginTemplate && !checkIfFileExists(rootDir, 'tsconfig.json')) {\n const tsconfigContent = {\n compilerOptions: {\n target: 'ES2020',\n module: 'ESNext',\n moduleResolution: 'bundler',\n allowImportingTsExtensions: true,\n noEmit: true,\n esModuleInterop: true,\n strict: true,\n skipLibCheck: true,\n forceConsistentCasingInFileNames: true,\n resolveJsonModule: true,\n allowJs: true,\n outDir: 'dist',\n rootDir: '.',\n baseUrl: '.',\n jsx: 'react-jsx',\n },\n include: ['**/*.ts', 'motia.config.ts', '**/*.tsx', 'types.d.ts', '**/*.jsx'],\n exclude: ['node_modules', 'dist', 'tests'],\n }\n\n fs.writeFileSync(path.join(rootDir, 'tsconfig.json'), JSON.stringify(tsconfigContent, null, 2))\n context.log('tsconfig-json-created', (message: Message) =>\n message.tag('success').append('File').append('tsconfig.json', 'cyan').append('has been created.'),\n )\n }\n\n // Plugin template handles .gitignore via template\n if (!isPluginTemplate && !checkIfFileExists(rootDir, '.gitignore')) {\n const gitignoreContent = [\n 'node_modules',\n 'python_modules',\n '.venv',\n 'venv',\n '.motia',\n '.mermaid',\n 'dist',\n '*.pyc',\n ].join('\\n')\n\n fs.writeFileSync(path.join(rootDir, '.gitignore'), gitignoreContent)\n context.log('gitignore-created', (message: Message) =>\n message.tag('success').append('File').append('.gitignore', 'cyan').append('has been created.'),\n )\n }\n\n // Skip cursor rules for plugin template\n if (!isPluginTemplate && cursorEnabled) {\n await pullRules({ force: true, rootDir }, context)\n }\n\n if (template) {\n await setupTemplate(template, rootDir, context)\n }\n\n if (!isPluginTemplate && skipRedis) {\n const motiaConfigPath = path.join(rootDir, 'motia.config.ts')\n\n const templatePath = path.join(__dirname, 'templates/motia.config.external-redis.ts.txt')\n const templateContent = fs.readFileSync(templatePath, 'utf-8')\n fs.writeFileSync(motiaConfigPath, templateContent)\n context.log('motia-config-created', (message: Message) =>\n message.tag('success').append('File').append('motia.config.ts', 'cyan').append('has been created.'),\n )\n }\n\n let packageManager: string\n if (!isPluginTemplate) {\n packageManager = await installNodeDependencies(rootDir, context)\n\n if (template.includes('python') || template.includes('multilang')) {\n await pythonInstall({ baseDir: rootDir })\n }\n\n await generateTypes(rootDir)\n } else {\n // For plugin template, just detect the package manager\n packageManager = await preparePackageManager(rootDir, context)\n }\n\n const projectDirName = path.basename(rootDir)\n const devCommand = `${packageManager} run dev`\n const port = 3000\n const cdCommand = isCurrentDir ? '' : `${pc.cyan(`cd ${projectDirName}`)}\\n `\n\n context.log('success-blank', (message) => message.text(''))\n context.log('success-header', (message) =>\n message.text(`${pc.green('✨')} ${pc.bold('All set! Your project is ready to go.')}`),\n )\n context.log('success-blank-2', (message) => message.text(''))\n context.log('success-get-started', (message) => message.text('Get started:'))\n context.log('success-blank-3', (message) => message.text(''))\n context.log('success-commands', (message) => message.text(` ${cdCommand}${pc.cyan(devCommand)}`))\n context.log('success-blank-4', (message) => message.text(''))\n context.log('success-open', (message) => message.text(`Then open ${pc.cyan(`http://localhost:${port}`)}`))\n context.log('success-blank-5', (message: Message) => message.text(''))\n context.log('success-docs', (message) => message.text(`Docs: ${pc.cyan('https://www.motia.dev/docs')}`))\n context.log('success-blank-6', (message) => message.text(''))\n if (skipRedis) {\n context.log('redis-skip-warning', (message: Message) =>\n message\n .tag('warning')\n .append(\n '⚠️ You skipped Redis binary installation. Make sure to provide a Redis connection before running Motia.',\n ),\n )\n context.log('success-blank-7', (message) => message.text(''))\n }\n context.log('success-signoff', (message) => message.text('Happy coding! 🚀'))\n context.log('success-blank-8', (message) => message.text(''))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["packageManager: string"],"sources":["../../src/create/index.ts"],"sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport pc from 'picocolors'\nimport { fileURLToPath } from 'url'\nimport type { CliContext, Message } from '../cloud/config-utils'\nimport { generateTypes } from '../generate-types'\nimport { pythonInstall } from '../install'\nimport { pluginDependencies } from '../plugins/plugin-dependencies'\nimport { executeCommand } from '../utils/execute-command'\nimport { getPackageManager, getPackageManagerFromEnv } from '../utils/get-package-manager'\nimport { version } from '../version'\nimport { pullRules } from './pull-rules'\nimport { setupTemplate } from './setup-template'\nimport { checkIfDirectoryExists, checkIfFileExists } from './utils'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\n\nconst installRequiredDependencies = async (packageManager: string, rootDir: string, context: CliContext) => {\n context.log('installing-dependencies', (message: Message) => message.tag('info').append('Installing dependencies...'))\n\n const installCommand = {\n npm: 'npm install --save',\n yarn: 'yarn add',\n pnpm: 'pnpm add',\n bun: 'bun add',\n }[packageManager]\n\n const dependencies = [\n `motia@${version}`,\n 'zod@4.1.12',\n `@motiadev/adapter-bullmq-events@${version}`,\n ...pluginDependencies.map((dep: string) => `${dep}@${version}`),\n ].join(' ')\n\n const devDependencies = ['ts-node@10.9.2', 'typescript@5.7.3', '@types/react@19.1.1'].join(' ')\n\n try {\n await executeCommand(`${installCommand} ${dependencies}`, rootDir)\n await executeCommand(`${installCommand} -D ${devDependencies}`, rootDir)\n\n context.log('dependencies-installed', (message: Message) => message.tag('success').append('Dependencies installed'))\n } catch (error) {\n console.error('❌ Failed to install dependencies:', error)\n }\n}\n\nconst preparePackageManager = async (rootDir: string, context: CliContext, detectFromParent = false) => {\n const detectionDir = detectFromParent ? process.cwd() : rootDir\n const envPackageManager = getPackageManagerFromEnv()\n const packageManager = getPackageManager(detectionDir)\n\n const isFallback =\n !envPackageManager && packageManager === 'npm' && !checkIfFileExists(detectionDir, 'package-lock.json')\n\n if (isFallback) {\n context.log('package-manager-using-default', (message: Message) =>\n message.tag('info').append('Using default package manager').append(packageManager, 'gray'),\n )\n } else {\n context.log('package-manager-detected', (message: Message) =>\n message.tag('info').append('Detected package manager').append(packageManager, 'gray'),\n )\n }\n\n return packageManager\n}\n\nconst installNodeDependencies = async (rootDir: string, context: CliContext) => {\n const packageManager = await preparePackageManager(rootDir, context)\n\n await installRequiredDependencies(packageManager, rootDir, context).catch((error: unknown) => {\n context.log('failed-to-install-dependencies', (message: Message) =>\n message.tag('failed').append('Failed to install dependencies'),\n )\n console.error(error)\n })\n\n return packageManager\n}\n\ntype Args = {\n projectName: string\n template: string\n cursorEnabled: boolean\n context: CliContext\n skipTutorialTemplates?: boolean\n skipRedis?: boolean\n}\n\nexport const create = async ({\n projectName,\n template,\n cursorEnabled,\n context,\n skipRedis = false,\n}: Args): Promise<void> => {\n console.log(\n '\\n\\n' +\n `\n _____ ______ ______ ______\n /'\\\\_/\\`\\\\/\\\\ __\\`\\\\/\\\\__ _\\\\/\\\\__ _\\\\ /\\\\ _ \\\\\n /\\\\ \\\\ \\\\ \\\\/\\\\ \\\\/_/\\\\ \\\\/\\\\/_/\\\\ \\\\/ \\\\ \\\\ \\\\L\\\\ \\\\\n \\\\ \\\\ \\\\__\\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\ __ \\\\\n \\\\ \\\\ \\\\_/\\\\ \\\\ \\\\ \\\\_\\\\ \\\\ \\\\ \\\\ \\\\ \\\\_\\\\ \\\\__\\\\ \\\\ \\\\/\\\\ \\\\\n \\\\ \\\\_\\\\\\\\ \\\\_\\\\ \\\\_____\\\\ \\\\ \\\\_\\\\ /\\\\_____\\\\\\\\ \\\\_\\\\ \\\\_\\\\\n \\\\/_/ \\\\/_/\\\\/_____/ \\\\/_/ \\\\/_____/ \\\\/_/\\\\/_/\n ` +\n '\\n\\n',\n )\n\n const isCurrentDir = projectName === '.' || projectName === './' || projectName === '.\\\\'\n const rootDir = isCurrentDir ? process.cwd() : path.join(process.cwd(), projectName)\n const isPluginTemplate = template === 'plugin'\n\n process.env.REDISMS_DISABLE_POSTINSTALL = '1'\n if (!isCurrentDir && !checkIfDirectoryExists(rootDir)) {\n fs.mkdirSync(path.join(rootDir))\n context.log('directory-created', (message: Message) =>\n message.tag('success').append('Directory created ').append(projectName, 'gray'),\n )\n } else {\n context.log('directory-using', (message: Message) => message.tag('info').append('Using current directory'))\n }\n\n // Plugin template handles package.json differently (via template)\n if (!isPluginTemplate && !checkIfFileExists(rootDir, 'package.json')) {\n const finalProjectName =\n !projectName || projectName === '.' || projectName === './' || projectName === '.\\\\'\n ? path.basename(process.cwd())\n : projectName.trim()\n\n const packageJsonContent = {\n name: finalProjectName,\n description: '',\n type: 'module',\n scripts: {\n postinstall: 'motia install',\n dev: 'motia dev',\n start: 'motia start',\n 'generate-types': 'motia generate-types',\n build: 'motia build',\n clean: 'rm -rf dist node_modules python_modules .motia .mermaid',\n //'generate:config': 'motia get-config --output ./', TODO: doesnt work at the moment\n },\n keywords: ['motia'],\n }\n\n fs.writeFileSync(path.join(rootDir, 'package.json'), JSON.stringify(packageJsonContent, null, 2))\n\n context.log('package-json-created', (message: Message) =>\n message.tag('success').append('File').append('package.json', 'cyan').append('has been created.'),\n )\n } else if (!isPluginTemplate) {\n const packageJsonPath = path.join(rootDir, 'package.json')\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))\n\n if (!packageJson.scripts) {\n packageJson.scripts = {}\n }\n\n if (!packageJson.scripts.dev) {\n packageJson.scripts.dev = 'motia dev'\n } else {\n packageJson.scripts.olddev = packageJson.scripts.dev\n packageJson.scripts.dev = 'motia dev'\n context.log('dev-command-already-exists', (message: Message) =>\n message.tag('warning').append('dev command already exists in package.json'),\n )\n }\n\n fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))\n context.log('dev-command-updated', (message: Message) =>\n message\n .tag('success')\n .append('Updated')\n .append('dev', 'gray')\n .append('command to')\n .append('package.json', 'gray'),\n )\n }\n\n // Plugin template handles tsconfig.json via template\n if (!isPluginTemplate && !checkIfFileExists(rootDir, 'tsconfig.json')) {\n const tsconfigContent = {\n compilerOptions: {\n target: 'ES2020',\n module: 'ESNext',\n moduleResolution: 'bundler',\n allowImportingTsExtensions: true,\n noEmit: true,\n esModuleInterop: true,\n strict: true,\n skipLibCheck: true,\n forceConsistentCasingInFileNames: true,\n resolveJsonModule: true,\n allowJs: true,\n outDir: 'dist',\n rootDir: '.',\n baseUrl: '.',\n jsx: 'react-jsx',\n },\n include: ['**/*.ts', 'motia.config.ts', '**/*.tsx', 'types.d.ts', '**/*.jsx'],\n exclude: ['node_modules', 'dist', 'tests'],\n }\n\n fs.writeFileSync(path.join(rootDir, 'tsconfig.json'), JSON.stringify(tsconfigContent, null, 2))\n context.log('tsconfig-json-created', (message: Message) =>\n message.tag('success').append('File').append('tsconfig.json', 'cyan').append('has been created.'),\n )\n }\n\n // Plugin template handles .gitignore via template\n if (!isPluginTemplate && !checkIfFileExists(rootDir, '.gitignore')) {\n const gitignoreContent = [\n 'node_modules',\n 'python_modules',\n '.venv',\n 'venv',\n '.motia',\n '.mermaid',\n 'dist',\n '*.pyc',\n ].join('\\n')\n\n fs.writeFileSync(path.join(rootDir, '.gitignore'), gitignoreContent)\n context.log('gitignore-created', (message: Message) =>\n message.tag('success').append('File').append('.gitignore', 'cyan').append('has been created.'),\n )\n }\n\n // Skip cursor rules for plugin template\n if (!isPluginTemplate && cursorEnabled) {\n await pullRules({ force: true, rootDir }, context)\n }\n\n if (template) {\n await setupTemplate(template, rootDir, context)\n }\n\n if (!isPluginTemplate && skipRedis) {\n const motiaConfigPath = path.join(rootDir, 'motia.config.ts')\n\n const templatePath = path.join(__dirname, 'templates/motia.config.external-redis.ts.txt')\n const templateContent = fs.readFileSync(templatePath, 'utf-8')\n fs.writeFileSync(motiaConfigPath, templateContent)\n context.log('motia-config-created', (message: Message) =>\n message.tag('success').append('File').append('motia.config.ts', 'cyan').append('has been created.'),\n )\n }\n\n let packageManager: string\n if (!isPluginTemplate) {\n packageManager = await installNodeDependencies(rootDir, context)\n\n if (template.includes('python') || template.includes('multilang')) {\n await pythonInstall({ baseDir: rootDir })\n }\n\n await generateTypes(rootDir)\n } else {\n packageManager = await preparePackageManager(rootDir, context, true)\n\n context.log('installing-plugin-dependencies', (message: Message) =>\n message.tag('info').append('Installing plugin dependencies...'),\n )\n\n const installCommand = {\n npm: 'npm install',\n yarn: 'yarn',\n pnpm: 'pnpm install',\n bun: 'bun install',\n }[packageManager]\n\n try {\n await executeCommand(installCommand!, rootDir)\n context.log('plugin-dependencies-installed', (message: Message) =>\n message.tag('success').append('Plugin dependencies installed'),\n )\n } catch (error) {\n context.log('failed-to-install-plugin-dependencies', (message: Message) =>\n message.tag('failed').append('Failed to install plugin dependencies'),\n )\n console.error(error)\n }\n }\n\n const projectDirName = path.basename(rootDir)\n const devCommand = `${packageManager} run dev`\n const port = 3000\n const cdCommand = isCurrentDir ? '' : `${pc.cyan(`cd ${projectDirName}`)}\\n `\n\n context.log('success-blank', (message) => message.text(''))\n context.log('success-header', (message) =>\n message.text(`${pc.green('✨')} ${pc.bold('All set! Your project is ready to go.')}`),\n )\n context.log('success-blank-2', (message) => message.text(''))\n context.log('success-get-started', (message) => message.text('Get started:'))\n context.log('success-blank-3', (message) => message.text(''))\n context.log('success-commands', (message) => message.text(` ${cdCommand}${pc.cyan(devCommand)}`))\n context.log('success-blank-4', (message) => message.text(''))\n context.log('success-open', (message) => message.text(`Then open ${pc.cyan(`http://localhost:${port}`)}`))\n context.log('success-blank-5', (message: Message) => message.text(''))\n context.log('success-docs', (message) => message.text(`Docs: ${pc.cyan('https://www.motia.dev/docs')}`))\n context.log('success-blank-6', (message) => message.text(''))\n if (skipRedis) {\n context.log('redis-skip-warning', (message: Message) =>\n message\n .tag('warning')\n .append(\n '⚠️ You skipped Redis binary installation. Make sure to provide a Redis connection before running Motia.',\n ),\n )\n context.log('success-blank-7', (message) => message.text(''))\n }\n context.log('success-signoff', (message) => message.text('Happy coding! 🚀'))\n context.log('success-blank-8', (message) => message.text(''))\n}\n"],"mappings":"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"}
|
|
@@ -11,56 +11,64 @@ You are an expert Motia developer with comprehensive knowledge of all Motia patt
|
|
|
11
11
|
|
|
12
12
|
Before writing ANY Motia code, you MUST read the relevant cursor rules from `.cursor/rules/`:
|
|
13
13
|
|
|
14
|
+
### Configuration Guide (in `.cursor/rules/motia/`)
|
|
15
|
+
|
|
16
|
+
1. **`motia-config.mdc`** - Project configuration
|
|
17
|
+
- Package.json requirements (`"type": "module"`)
|
|
18
|
+
- Plugin naming conventions and setup
|
|
19
|
+
- Adapter configuration, Redis setup
|
|
20
|
+
- Stream authentication patterns
|
|
21
|
+
|
|
14
22
|
### Step Type Guides (in `.cursor/rules/motia/`)
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
2. **`api-steps.mdc`** - HTTP endpoints
|
|
17
25
|
- Creating API Steps with TypeScript, JavaScript, or Python
|
|
18
26
|
- Request/response schemas, validation, middleware
|
|
19
27
|
- When to emit events vs process directly
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
3. **`event-steps.mdc`** - Background tasks
|
|
22
30
|
- Creating Event Steps with TypeScript, JavaScript, or Python
|
|
23
31
|
- Topic subscription, event chaining, retry mechanisms
|
|
24
32
|
- Asynchronous workflow patterns
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
4. **`cron-steps.mdc`** - Scheduled tasks
|
|
27
35
|
- Creating Cron Steps with TypeScript, JavaScript, or Python
|
|
28
36
|
- Cron expression syntax, idempotent patterns
|
|
29
37
|
- When to emit events from scheduled jobs
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
5. **`state-management.mdc`** - State/cache management
|
|
32
40
|
- Using state across steps with TypeScript, JavaScript, or Python
|
|
33
41
|
- When to use state vs database
|
|
34
42
|
- TTL configuration, caching strategies
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
6. **`middlewares.mdc`** - Request/response middleware
|
|
37
45
|
- Creating middleware with TypeScript, JavaScript, or Python
|
|
38
46
|
- Authentication, validation, error handling
|
|
39
47
|
- Middleware composition patterns
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
7. **`realtime-streaming.mdc`** - Real-time data
|
|
42
50
|
- Server-Sent Events (SSE) patterns
|
|
43
51
|
- WebSocket support
|
|
44
52
|
- Stream configuration and usage
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
8. **`virtual-steps.mdc`** - Visual flow connections
|
|
47
55
|
- Creating NOOP steps for Workbench
|
|
48
56
|
- Virtual emits/subscribes for documentation
|
|
49
57
|
- Workflow visualization
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
9. **`ui-steps.mdc`** - Custom Workbench components
|
|
52
60
|
- Creating custom visual components (TypeScript/React)
|
|
53
61
|
- EventNode, ApiNode, CronNode components
|
|
54
62
|
- Styling with Tailwind
|
|
55
63
|
|
|
56
64
|
### Architecture Guides (in `.cursor/architecture/`)
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
10. **`architecture.mdc`** - Project structure
|
|
59
67
|
- File organization, naming conventions
|
|
60
68
|
- Domain-Driven Design patterns
|
|
61
69
|
- Services, repositories, utilities structure
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
11. **`error-handling.mdc`** - Error handling
|
|
64
72
|
- Custom error classes
|
|
65
73
|
- Middleware error handling
|
|
66
74
|
- ZodError/Pydantic validation errors
|
|
@@ -78,6 +86,7 @@ Before writing ANY Motia code, you MUST read the relevant cursor rules from `.cu
|
|
|
78
86
|
## Key Principles
|
|
79
87
|
|
|
80
88
|
- **All guides have TypeScript, JavaScript, and Python examples**
|
|
89
|
+
- **Steps can live in `/src` or `/steps`** - Motia discovers both (use `/src` for modern structure)
|
|
81
90
|
- **Always export `config` and `handler`**
|
|
82
91
|
- **List all emits in config before using them**
|
|
83
92
|
- **Follow naming conventions**: `*.step.ts` (TS), `*.step.js` (JS), `*_step.py` (Python)
|
|
@@ -92,4 +101,4 @@ If you're unsure about any Motia pattern:
|
|
|
92
101
|
|
|
93
102
|
---
|
|
94
103
|
|
|
95
|
-
Remember: The
|
|
104
|
+
Remember: The 11 cursor rules in `.cursor/rules/` are your source of truth. Always read them first.
|
|
@@ -8,13 +8,61 @@ alwaysApply: true
|
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
This guide covers the architecture
|
|
11
|
+
This guide covers the architecture and best practices for structuring Motia projects.
|
|
12
|
+
|
|
13
|
+
**Key Takeaway**: Motia automatically discovers steps from anywhere in your project. Modern projects use `/src` for a familiar structure that works seamlessly with Domain-Driven Design.
|
|
12
14
|
|
|
13
15
|
## File Structure
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
Motia automatically discovers step files from your project. You can organize steps in either:
|
|
18
|
+
|
|
19
|
+
- **`/src` folder** (recommended) - Familiar pattern for most developers
|
|
20
|
+
- **`/steps` folder** - Traditional Motia pattern
|
|
21
|
+
- Both folders simultaneously
|
|
22
|
+
|
|
23
|
+
### Recommended Structure (using `/src`)
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
project/
|
|
27
|
+
├── src/
|
|
28
|
+
│ ├── api/ # API endpoints
|
|
29
|
+
│ │ ├── users.step.ts
|
|
30
|
+
│ │ └── orders.step.ts
|
|
31
|
+
│ ├── events/ # Event handlers
|
|
32
|
+
│ │ ├── order-processing.step.ts
|
|
33
|
+
│ │ └── notifications.step.ts
|
|
34
|
+
│ ├── cron/ # Scheduled tasks
|
|
35
|
+
│ │ └── cleanup.step.ts
|
|
36
|
+
│ ├── services/ # Business logic
|
|
37
|
+
│ ├── repositories/ # Data access
|
|
38
|
+
│ └── utils/ # Utilities
|
|
39
|
+
└── motia.config.ts
|
|
40
|
+
```
|
|
16
41
|
|
|
17
|
-
|
|
42
|
+
### Alternative Structure (using `/steps`)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
project/
|
|
46
|
+
├── steps/
|
|
47
|
+
│ ├── api/
|
|
48
|
+
│ │ └── users.step.ts
|
|
49
|
+
│ ├── events/
|
|
50
|
+
│ │ └── order-processing.step.ts
|
|
51
|
+
│ └── cron/
|
|
52
|
+
│ └── cleanup.step.ts
|
|
53
|
+
├── src/
|
|
54
|
+
│ ├── services/
|
|
55
|
+
│ └── utils/
|
|
56
|
+
└── motia.config.ts
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Create subfolders within your chosen directory to organize related steps into logical groups (domains, features, or flows).
|
|
60
|
+
|
|
61
|
+
**Why `/src` is recommended:**
|
|
62
|
+
- Familiar to developers from other frameworks (Next.js, NestJS, etc.)
|
|
63
|
+
- Natural co-location with services, repositories, and utilities
|
|
64
|
+
- Works seamlessly with Domain-Driven Design patterns
|
|
65
|
+
- Cleaner project root with fewer top-level folders
|
|
18
66
|
|
|
19
67
|
## Step Naming Conventions
|
|
20
68
|
|
|
@@ -40,49 +88,97 @@ Underneath the `steps/` folder, create subfolders for Flows. Flows are used to g
|
|
|
40
88
|
|
|
41
89
|
## Defining Middlewares
|
|
42
90
|
|
|
43
|
-
Middleware is a powerful feature in Motia
|
|
44
|
-
|
|
91
|
+
Middleware is a powerful feature in Motia for common validation, error handling, and shared logic.
|
|
92
|
+
|
|
93
|
+
### Middleware Organization
|
|
45
94
|
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
95
|
+
Store middlewares in a dedicated folder:
|
|
96
|
+
- `/middlewares` at project root (recommended)
|
|
97
|
+
- `/src/middlewares` if using `/src` structure
|
|
98
|
+
|
|
99
|
+
### Best Practices
|
|
100
|
+
|
|
101
|
+
- **One responsibility per middleware** - Follow SOLID principles
|
|
102
|
+
- **Descriptive naming** - Use names like `auth.middleware.ts`, `validation.middleware.ts`
|
|
103
|
+
- **Handle errors gracefully** - Use core middleware for ZodError (see [Error Handling Guide](./error-handling.mdc))
|
|
104
|
+
- **Avoid infrastructure concerns** - Rate limiting and CORS are handled by infrastructure, not middleware
|
|
51
105
|
|
|
52
106
|
## Domain Driven Design
|
|
53
107
|
|
|
54
|
-
|
|
108
|
+
Motia encourages Domain-Driven Design (DDD) principles for maintainable, scalable applications.
|
|
55
109
|
|
|
56
|
-
|
|
57
|
-
- Create `/src/repositories` folder to store your repositories, this is where it holds data access logic.
|
|
58
|
-
- Create `/src/utils` folder to store your utility functions.
|
|
59
|
-
- Models and DTOs are not quite necessary, we can rely on zod to create the models and DTOs from the steps.
|
|
60
|
-
- Controller layer is the Steps, it should have mostly logic around validation and calling services.
|
|
61
|
-
- Avoid having Service methods with just a call to the Repository, it should have some logic around it, if it doesn't have, then Steps can have access to repositories directly.
|
|
110
|
+
### Folder Structure for DDD
|
|
62
111
|
|
|
63
|
-
|
|
112
|
+
When using `/src` for steps (recommended), your structure naturally supports DDD:
|
|
64
113
|
|
|
65
|
-
|
|
114
|
+
```
|
|
115
|
+
src/
|
|
116
|
+
├── api/ # API Steps (Controllers)
|
|
117
|
+
├── events/ # Event Steps (Controllers)
|
|
118
|
+
├── cron/ # Cron Steps (Controllers)
|
|
119
|
+
├── services/ # Business logic layer
|
|
120
|
+
├── repositories/ # Data access layer
|
|
121
|
+
├── utils/ # Utility functions
|
|
122
|
+
└── types/ # Shared types (optional)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Layer Responsibilities
|
|
126
|
+
|
|
127
|
+
- **Steps (Controller Layer)**: Handle validation, call services, emit events
|
|
128
|
+
- **Services**: Contain business logic, orchestrate repositories
|
|
129
|
+
- **Repositories**: Direct data access (database, external APIs)
|
|
130
|
+
- **Utils**: Pure utility functions with no side effects
|
|
66
131
|
|
|
67
|
-
|
|
68
|
-
- Create a file inside the folder called `index.ts`.
|
|
69
|
-
- Inside `index.ts`, export a constant with the name of the service, with the methods as properties.
|
|
70
|
-
- Methods should be defined as separate files, use export named functions.
|
|
71
|
-
- Use the service in the Steps.
|
|
132
|
+
### Best Practices
|
|
72
133
|
|
|
73
|
-
|
|
134
|
+
- Models and DTOs are not necessary - use Zod schemas from step configs
|
|
135
|
+
- Steps should focus on validation and calling services
|
|
136
|
+
- Avoid service methods that only call repositories - Steps can access repositories directly
|
|
137
|
+
- Keep business logic in services, not in steps
|
|
74
138
|
|
|
139
|
+
### Services
|
|
140
|
+
|
|
141
|
+
Services contain your business logic and should be organized by domain.
|
|
142
|
+
|
|
143
|
+
**Structure:**
|
|
144
|
+
```
|
|
145
|
+
src/
|
|
146
|
+
├── services/
|
|
147
|
+
│ ├── auth/
|
|
148
|
+
│ │ ├── index.ts # Export service
|
|
149
|
+
│ │ ├── login.ts # Login method
|
|
150
|
+
│ │ └── register.ts # Register method
|
|
151
|
+
│ └── orders/
|
|
152
|
+
│ ├── index.ts
|
|
153
|
+
│ └── create-order.ts
|
|
154
|
+
└── api/
|
|
155
|
+
└── auth.step.ts # Uses authService
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Service Definition (`/src/services/auth/index.ts`):**
|
|
75
159
|
```typescript
|
|
76
160
|
/**
|
|
77
|
-
* Business logic
|
|
161
|
+
* Business logic methods imported from separate files
|
|
78
162
|
*/
|
|
79
163
|
import { login } from './login'
|
|
164
|
+
import { register } from './register'
|
|
80
165
|
|
|
81
166
|
/**
|
|
82
|
-
*
|
|
167
|
+
* Export service with methods as properties
|
|
83
168
|
*/
|
|
84
169
|
export const authService = {
|
|
85
|
-
login
|
|
170
|
+
login,
|
|
171
|
+
register
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Usage in Step (`/src/api/auth.step.ts`):**
|
|
176
|
+
```typescript
|
|
177
|
+
import { authService } from '../services/auth'
|
|
178
|
+
|
|
179
|
+
export const handler = async (req, ctx) => {
|
|
180
|
+
const user = await authService.login(req.body.email, req.body.password)
|
|
181
|
+
return { status: 200, body: { user } }
|
|
86
182
|
}
|
|
87
183
|
```
|
|
88
184
|
|