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.
Files changed (33) hide show
  1. package/dist/cli.mjs +9 -1
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/cloud/config-utils.mjs +1 -1
  4. package/dist/cloud/config-utils.mjs.map +1 -1
  5. package/dist/cloud/new-deployment/build.mjs +11 -1
  6. package/dist/cloud/new-deployment/build.mjs.map +1 -1
  7. package/dist/create/index.mjs +28 -12
  8. package/dist/create/index.mjs.map +1 -1
  9. package/dist/cursor-rules/dot-files/.claude/agents/motia-developer.md +20 -11
  10. package/dist/cursor-rules/dot-files/.cursor/architecture/architecture.mdc +124 -28
  11. package/dist/cursor-rules/dot-files/.cursor/rules/motia/motia-config.mdc +66 -0
  12. package/dist/cursor-rules/dot-files/AGENTS.md +31 -16
  13. package/dist/cursor-rules/dot-files/CLAUDE.md +7 -2
  14. package/dist/cursor-rules/dot-files/opencode.json +1 -0
  15. package/dist/dev.mjs +8 -2
  16. package/dist/dev.mjs.map +1 -1
  17. package/dist/generate-locked-data.d.mts.map +1 -1
  18. package/dist/generate-locked-data.mjs +0 -2
  19. package/dist/generate-locked-data.mjs.map +1 -1
  20. package/dist/install.mjs +1 -1
  21. package/dist/plugins/install-plugin-dependencies.mjs +4 -3
  22. package/dist/plugins/install-plugin-dependencies.mjs.map +1 -1
  23. package/dist/start.mjs +8 -2
  24. package/dist/start.mjs.map +1 -1
  25. package/dist/utils/activate-python-env.mjs +9 -15
  26. package/dist/utils/activate-python-env.mjs.map +1 -1
  27. package/dist/utils/get-package-manager.mjs +42 -5
  28. package/dist/utils/get-package-manager.mjs.map +1 -1
  29. package/dist/utils/validate-python-environment.mjs +81 -0
  30. package/dist/utils/validate-python-environment.mjs.map +1 -0
  31. package/dist/watcher.mjs +1 -3
  32. package/dist/watcher.mjs.map +1 -1
  33. 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,YAAQ,IAAI,UAAU,YAAY,QAAQ,IAAI,SAAS,CAAC,OAAO,MAAM,QAAQ,CAAC;AAC9E,YAAQ,KAAK,EAAE;SAEf,SAAQ,cAAc,qBAAqB,MAAM"}
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
- if (hasPythonSteps([...stepFiles, ...streamFiles])) builder.registerBuilder("python", new PythonBuilder(builder, listener));
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 if (hasPythonSteps([...stepFiles, ...streamFiles])) {\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":";;;;;;;;;;AAUA,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;AAEhG,KAAI,eAAe,CAAC,GAAG,WAAW,GAAG,YAAY,CAAC,CAChD,SAAQ,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"}
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"}
@@ -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
- let packageManager = "npm";
45
- const detectedPackageManager = getPackageManager(rootDir);
46
- if (detectedPackageManager !== "unknown") {
47
- context.log("package-manager-detected", (message) => message.tag("info").append("Detected package manager").append(detectedPackageManager, "gray"));
48
- packageManager = detectedPackageManager;
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 packageManager = await preparePackageManager(rootDir, context);
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
- 1. **`api-steps.mdc`** - HTTP endpoints
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
- 2. **`event-steps.mdc`** - Background tasks
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
- 3. **`cron-steps.mdc`** - Scheduled tasks
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
- 4. **`state-management.mdc`** - State/cache management
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
- 5. **`middlewares.mdc`** - Request/response middleware
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
- 6. **`realtime-streaming.mdc`** - Real-time data
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
- 7. **`virtual-steps.mdc`** - Visual flow connections
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
- 8. **`ui-steps.mdc`** - Custom Workbench components
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
- 9. **`architecture.mdc`** - Project structure
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
- 10. **`error-handling.mdc`** - Error handling
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 10 cursor rules in `.cursor/rules/` are your source of truth. Always read them first.
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 of a Motia project.
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
- All step files should be underneath the `steps/` folder.
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
- Underneath the `steps/` folder, create subfolders for Flows. Flows are used to group steps together.
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 to help adding common validation, error
44
- handling and other common logic to your steps.
91
+ Middleware is a powerful feature in Motia for common validation, error handling, and shared logic.
92
+
93
+ ### Middleware Organization
45
94
 
46
- - Make sure to add all the middlewares in a single folder, called `middlewares/`.
47
- - Create a comprehensive file name for the middleware, like `auth.middleware.ts`.
48
- - Follow SOLID principles with separation of concerns in middlewares, create a middleware for each responsibility.
49
- - Use core middleware to handle ZodError gracefully (see [Error Handling Guide](./error-handling.mdc))
50
- - Rate limiting and CORS are not needed to be handled in middleware since they're an infrastructure concern.
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
- Make sure you follow Domain Driven Design principles in your project.
108
+ Motia encourages Domain-Driven Design (DDD) principles for maintainable, scalable applications.
55
109
 
56
- - Create `/src/services` folder to store your services, this is where it holds business logic.
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
- ### Services
112
+ When using `/src` for steps (recommended), your structure naturally supports DDD:
64
113
 
65
- Defining services can be done in the following way:
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
- - Create a folder underneath `/src/services/` folder, like `/src/services/auth/`.
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
- #### Example
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 for authentication defined in a separate file in the same folder.
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
- * Constant with the name of the service, with the methods as properties.
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