@synergenius/flow-weaver 0.13.2 → 0.14.0

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 (57) hide show
  1. package/README.md +41 -2
  2. package/dist/api/validate.js +8 -2
  3. package/dist/ast/types.d.ts +120 -0
  4. package/dist/chevrotain-parser/node-parser.d.ts +4 -0
  5. package/dist/chevrotain-parser/node-parser.js +41 -1
  6. package/dist/chevrotain-parser/port-parser.d.ts +1 -0
  7. package/dist/chevrotain-parser/port-parser.js +22 -2
  8. package/dist/chevrotain-parser/tokens.d.ts +3 -0
  9. package/dist/chevrotain-parser/tokens.js +15 -0
  10. package/dist/cli/commands/export.js +25 -38
  11. package/dist/cli/flow-weaver.mjs +63703 -54297
  12. package/dist/cli/templates/index.js +9 -0
  13. package/dist/cli/templates/workflows/cicd-docker.d.ts +9 -0
  14. package/dist/cli/templates/workflows/cicd-docker.js +110 -0
  15. package/dist/cli/templates/workflows/cicd-matrix.d.ts +9 -0
  16. package/dist/cli/templates/workflows/cicd-matrix.js +112 -0
  17. package/dist/cli/templates/workflows/cicd-multi-env.d.ts +9 -0
  18. package/dist/cli/templates/workflows/cicd-multi-env.js +118 -0
  19. package/dist/cli/templates/workflows/cicd-test-deploy.d.ts +11 -0
  20. package/dist/cli/templates/workflows/cicd-test-deploy.js +149 -0
  21. package/dist/constants.js +7 -0
  22. package/dist/deployment/index.d.ts +14 -7
  23. package/dist/deployment/index.js +29 -17
  24. package/dist/deployment/targets/base.d.ts +27 -2
  25. package/dist/deployment/targets/base.js +38 -6
  26. package/dist/deployment/targets/cicd-base.d.ts +111 -0
  27. package/dist/deployment/targets/cicd-base.js +357 -0
  28. package/dist/deployment/targets/cloudflare.d.ts +6 -0
  29. package/dist/deployment/targets/cloudflare.js +3 -0
  30. package/dist/deployment/targets/github-actions.d.ts +54 -0
  31. package/dist/deployment/targets/github-actions.js +366 -0
  32. package/dist/deployment/targets/gitlab-ci.d.ts +65 -0
  33. package/dist/deployment/targets/gitlab-ci.js +374 -0
  34. package/dist/deployment/targets/inngest.d.ts +25 -0
  35. package/dist/deployment/targets/inngest.js +10 -1
  36. package/dist/deployment/targets/lambda.d.ts +17 -0
  37. package/dist/deployment/targets/lambda.js +5 -0
  38. package/dist/deployment/targets/vercel.d.ts +16 -0
  39. package/dist/deployment/targets/vercel.js +5 -0
  40. package/dist/diagram/geometry.js +13 -5
  41. package/dist/export/index.d.ts +13 -9
  42. package/dist/export/index.js +129 -997
  43. package/dist/generated-version.d.ts +1 -1
  44. package/dist/generated-version.js +1 -1
  45. package/dist/jsdoc-parser.d.ts +130 -0
  46. package/dist/jsdoc-parser.js +408 -4
  47. package/dist/marketplace/index.d.ts +1 -1
  48. package/dist/marketplace/types.d.ts +13 -0
  49. package/dist/marketplace/validator.js +21 -2
  50. package/dist/mcp/tools-export.js +56 -14
  51. package/dist/parser.js +28 -1
  52. package/dist/validation/cicd-detection.d.ts +33 -0
  53. package/dist/validation/cicd-detection.js +76 -0
  54. package/dist/validation/cicd-rules.d.ts +62 -0
  55. package/dist/validation/cicd-rules.js +284 -0
  56. package/docs/reference/scaffold.md +4 -0
  57. package/package.json +4 -3
@@ -5,22 +5,39 @@
5
5
  import * as path from 'path';
6
6
  import * as fs from 'fs';
7
7
  import * as os from 'os';
8
- import { fileURLToPath } from 'url';
9
- // ESM-compatible __dirname
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
- import { getGeneratedBranding } from '../generated-branding.js';
13
8
  import { compileWorkflow } from '../api/compile.js';
14
9
  import { AnnotationParser } from '../parser.js';
15
- import { LAMBDA_TEMPLATE, VERCEL_TEMPLATE, CLOUDFLARE_TEMPLATE, INNGEST_TEMPLATE, SAM_TEMPLATE, } from './templates.js';
16
- import { generateInngestFunction } from '../generator/inngest.js';
17
10
  /**
18
- * Export multiple workflows as a single service
11
+ * Export a workflow for deployment.
12
+ *
13
+ * Delegates to the target registry — every target is a plugin discovered
14
+ * from installed marketplace packs. The legacy template system has been
15
+ * replaced by target classes that generate their own handler + config.
19
16
  */
20
- export async function exportMultiWorkflow(options) {
17
+ export async function exportWorkflow(options) {
18
+ const { createTargetRegistry } = await import('../deployment/index.js');
19
+ const registry = await createTargetRegistry(process.cwd());
20
+ const target = registry.get(options.target);
21
+ if (!target) {
22
+ const available = registry.getNames();
23
+ throw new Error(available.length === 0
24
+ ? `No export targets installed. Install a target pack (e.g. npm install flowweaver-pack-${options.target})`
25
+ : `Unknown target "${options.target}". Installed: ${available.join(', ')}`);
26
+ }
21
27
  const inputPath = path.resolve(options.input);
22
28
  const outputDir = path.resolve(options.output);
23
29
  const isDryRun = options.dryRun ?? false;
30
+ // Multi-workflow mode
31
+ if (options.multi) {
32
+ return exportMultiWorkflowViaRegistry(target, inputPath, outputDir, isDryRun, options);
33
+ }
34
+ // Single-workflow mode
35
+ return exportSingleWorkflowViaRegistry(target, inputPath, outputDir, isDryRun, options);
36
+ }
37
+ /**
38
+ * Single-workflow export through the target registry.
39
+ */
40
+ async function exportSingleWorkflowViaRegistry(target, inputPath, outputDir, isDryRun, options) {
24
41
  // Validate input file exists
25
42
  if (!fs.existsSync(inputPath)) {
26
43
  throw new Error(`Input file not found: ${inputPath}`);
@@ -31,738 +48,76 @@ export async function exportMultiWorkflow(options) {
31
48
  if (parseResult.workflows.length === 0) {
32
49
  throw new Error(`No workflows found in ${inputPath}`);
33
50
  }
34
- // Filter workflows if specific ones are requested
35
- let selectedWorkflows = parseResult.workflows;
36
- if (options.workflows && options.workflows.length > 0) {
37
- selectedWorkflows = parseResult.workflows.filter((w) => options.workflows.includes(w.name) || options.workflows.includes(w.functionName));
38
- if (selectedWorkflows.length === 0) {
39
- const available = parseResult.workflows.map((w) => w.name).join(', ');
40
- throw new Error(`None of the requested workflows found. Available: ${available}`);
41
- }
51
+ // Select workflow
52
+ const workflow = options.workflow
53
+ ? parseResult.workflows.find((w) => w.name === options.workflow || w.functionName === options.workflow)
54
+ : parseResult.workflows[0];
55
+ if (!workflow) {
56
+ const available = parseResult.workflows.map((w) => w.name).join(', ');
57
+ throw new Error(`Workflow "${options.workflow}" not found. Available: ${available}`);
42
58
  }
43
- // For dry-run, use temp directory for compilation
44
- const workDir = isDryRun
45
- ? path.join(os.tmpdir(), `fw-export-multi-dryrun-${Date.now()}`)
46
- : outputDir;
47
- // Create working directory
48
- fs.mkdirSync(workDir, { recursive: true });
49
- fs.mkdirSync(path.join(workDir, 'workflows'), { recursive: true });
50
- fs.mkdirSync(path.join(workDir, 'runtime'), { recursive: true });
51
- const compiledWorkflows = [];
52
- try {
53
- // Compile each workflow
54
- for (const workflow of selectedWorkflows) {
55
- const workflowOutputPath = path.join(workDir, 'workflows', `${workflow.name}.ts`);
56
- // Copy source file
57
- fs.copyFileSync(inputPath, workflowOutputPath);
58
- // Compile in-place
59
- await compileWorkflow(workflowOutputPath, {
60
- write: true,
61
- inPlace: true,
62
- parse: { workflowName: workflow.functionName },
63
- generate: { production: options.production ?? true },
64
- });
65
- const compiledCode = fs.readFileSync(workflowOutputPath, 'utf8');
66
- compiledWorkflows.push({
67
- name: workflow.name,
68
- functionName: workflow.functionName,
69
- description: workflow.description,
70
- code: compiledCode,
71
- });
72
- }
73
- // Copy runtime files
74
- const runtimeDir = path.join(workDir, 'runtime');
75
- const libraryRuntimeDir = path.resolve(__dirname, '../runtime');
76
- // Copy function registry and builtin functions
77
- const runtimeFiles = ['function-registry.ts', 'parameter-resolver.ts', 'builtin-functions.ts'];
78
- for (const file of runtimeFiles) {
79
- const srcPath = path.join(libraryRuntimeDir, file);
80
- const destPath = path.join(runtimeDir, file);
81
- if (fs.existsSync(srcPath)) {
82
- fs.copyFileSync(srcPath, destPath);
83
- }
59
+ // Generate artifacts via target
60
+ const artifacts = await target.generate({
61
+ sourceFile: inputPath,
62
+ workflowName: workflow.functionName,
63
+ displayName: workflow.name,
64
+ outputDir,
65
+ description: workflow.description,
66
+ production: options.production ?? true,
67
+ includeDocs: options.includeDocs,
68
+ targetOptions: {
69
+ ...(options.durableSteps && { durableSteps: true }),
70
+ },
71
+ });
72
+ // If the target's handler references a workflow import, compile and include it
73
+ const needsCompiledWorkflow = artifacts.files.some((f) => f.type === 'handler' && f.content.includes("from './workflow"));
74
+ let compiledContent;
75
+ if (needsCompiledWorkflow) {
76
+ const workDir = isDryRun
77
+ ? path.join(os.tmpdir(), `fw-export-dryrun-${Date.now()}`)
78
+ : outputDir;
79
+ fs.mkdirSync(workDir, { recursive: true });
80
+ try {
81
+ const compiledPath = await compileToOutput(inputPath, workflow.functionName, workDir, options.production);
82
+ compiledContent = fs.readFileSync(compiledPath, 'utf8');
84
83
  }
85
- // Generate handler and config based on target
86
- const result = generateMultiHandler(options.target, compiledWorkflows, workDir, options);
87
- // Write files if not dry-run
88
- if (!isDryRun) {
89
- for (const file of result.files) {
90
- const dirPath = path.dirname(file.path);
91
- if (!fs.existsSync(dirPath)) {
92
- fs.mkdirSync(dirPath, { recursive: true });
84
+ finally {
85
+ if (isDryRun) {
86
+ try {
87
+ fs.rmSync(workDir, { recursive: true, force: true });
93
88
  }
94
- fs.writeFileSync(file.path, file.content);
95
- }
96
- }
97
- return {
98
- target: options.target,
99
- files: result.files,
100
- workflow: result.serviceName,
101
- workflows: compiledWorkflows.map((w) => w.name),
102
- openApiSpec: result.openApiSpec,
103
- };
104
- }
105
- finally {
106
- // Clean up temp directory in dry-run mode
107
- if (isDryRun) {
108
- try {
109
- fs.rmSync(workDir, { recursive: true, force: true });
110
- }
111
- catch {
112
- // Ignore cleanup errors
89
+ catch { /* ignore */ }
113
90
  }
114
91
  }
115
92
  }
116
- }
117
- /**
118
- * Generate multi-workflow handler and config files
119
- */
120
- function generateMultiHandler(target, workflows, outputDir, options) {
121
- const files = [];
122
- const serviceName = path.basename(options.input, path.extname(options.input)) + '-service';
123
- // Generate workflow imports and entries
124
- const workflowImports = workflows
125
- .map((w) => `import { ${w.functionName} } from './workflows/${w.name}.js';`)
126
- .join('\n');
127
- const workflowEntries = workflows.map((w) => ` '${w.name}': ${w.functionName},`).join('\n');
128
- // Generate OpenAPI spec
129
- const openApiSpec = generateMultiWorkflowOpenAPI(workflows, serviceName);
130
- // Generate based on target
131
- switch (target) {
132
- case 'lambda':
133
- files.push({
134
- path: path.join(outputDir, 'handler.ts'),
135
- content: generateLambdaMultiHandler(workflowImports, workflowEntries, serviceName),
136
- });
137
- files.push({
138
- path: path.join(outputDir, 'openapi.ts'),
139
- content: `// Generated OpenAPI specification\nexport const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};\n`,
140
- });
141
- files.push({
142
- path: path.join(outputDir, 'template.yaml'),
143
- content: generateLambdaSAMMultiTemplate(serviceName, workflows.length),
144
- });
145
- files.push({
146
- path: path.join(outputDir, 'package.json'),
147
- content: JSON.stringify({
148
- name: `fw-${serviceName}`,
149
- version: '1.0.0',
150
- type: 'module',
151
- main: 'handler.js',
152
- scripts: {
153
- build: 'tsc',
154
- deploy: 'sam build && sam deploy --guided',
155
- },
156
- devDependencies: {
157
- '@types/aws-lambda': '^8.10.0',
158
- typescript: '^5.0.0',
159
- },
160
- }, null, 2),
161
- });
162
- files.push({
163
- path: path.join(outputDir, 'tsconfig.json'),
164
- content: JSON.stringify({
165
- compilerOptions: {
166
- target: 'ES2022',
167
- module: 'NodeNext',
168
- moduleResolution: 'NodeNext',
169
- outDir: './dist',
170
- strict: true,
171
- esModuleInterop: true,
172
- skipLibCheck: true,
173
- },
174
- include: ['*.ts', 'workflows/*.ts', 'runtime/*.ts'],
175
- }, null, 2),
176
- });
177
- break;
178
- case 'vercel':
179
- files.push({
180
- path: path.join(outputDir, 'api', '[workflow].ts'),
181
- content: generateVercelMultiHandler(workflowImports, workflowEntries, serviceName),
182
- });
183
- files.push({
184
- path: path.join(outputDir, 'openapi.ts'),
185
- content: `// Generated OpenAPI specification\nexport const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};\n`,
186
- });
187
- files.push({
188
- path: path.join(outputDir, 'vercel.json'),
189
- content: JSON.stringify({
190
- functions: {
191
- 'api/[workflow].ts': {
192
- memory: 1024,
193
- maxDuration: 60,
194
- },
195
- },
196
- }, null, 2),
197
- });
198
- break;
199
- case 'cloudflare':
200
- files.push({
201
- path: path.join(outputDir, 'index.ts'),
202
- content: generateCloudflareMultiHandler(workflowImports, workflowEntries, serviceName),
203
- });
204
- files.push({
205
- path: path.join(outputDir, 'openapi.ts'),
206
- content: `// Generated OpenAPI specification\nexport const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};\n`,
207
- });
208
- files.push({
209
- path: path.join(outputDir, 'wrangler.toml'),
210
- content: `name = "${serviceName}"\nmain = "dist/index.js"\ncompatibility_date = "2024-01-01"\n\n[build]\ncommand = "npm run build"\n`,
211
- });
212
- files.push({
213
- path: path.join(outputDir, 'package.json'),
214
- content: JSON.stringify({
215
- name: `fw-${serviceName}`,
216
- version: '1.0.0',
217
- type: 'module',
218
- scripts: {
219
- build: 'tsc',
220
- deploy: 'wrangler deploy',
221
- dev: 'wrangler dev',
222
- },
223
- devDependencies: {
224
- '@cloudflare/workers-types': '^4.0.0',
225
- typescript: '^5.0.0',
226
- wrangler: '^3.0.0',
227
- },
228
- }, null, 2),
229
- });
230
- files.push({
231
- path: path.join(outputDir, 'tsconfig.json'),
232
- content: JSON.stringify({
233
- compilerOptions: {
234
- target: 'ES2022',
235
- module: 'ESNext',
236
- moduleResolution: 'Bundler',
237
- outDir: './dist',
238
- strict: true,
239
- skipLibCheck: true,
240
- types: ['@cloudflare/workers-types'],
241
- },
242
- include: ['*.ts', 'workflows/*.ts', 'runtime/*.ts'],
243
- }, null, 2),
244
- });
245
- break;
246
- case 'inngest':
247
- files.push({
248
- path: path.join(outputDir, 'handler.ts'),
249
- content: generateInngestMultiHandler(workflowImports, workflowEntries, serviceName, workflows),
250
- });
251
- files.push({
252
- path: path.join(outputDir, 'openapi.ts'),
253
- content: `// Generated OpenAPI specification\nexport const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};\n`,
254
- });
255
- files.push({
256
- path: path.join(outputDir, 'package.json'),
257
- content: JSON.stringify({
258
- name: `fw-${serviceName}`,
259
- version: '1.0.0',
260
- type: 'module',
261
- main: 'handler.js',
262
- scripts: {
263
- build: 'tsc',
264
- dev: 'npx inngest-cli@latest dev & npx tsx handler.ts',
265
- serve: 'npx tsx handler.ts',
266
- },
267
- dependencies: {
268
- inngest: '^3.0.0',
269
- express: '^4.18.0',
270
- },
271
- devDependencies: {
272
- '@types/express': '^4.17.0',
273
- typescript: '^5.0.0',
274
- },
275
- }, null, 2),
276
- });
277
- files.push({
278
- path: path.join(outputDir, 'tsconfig.json'),
279
- content: JSON.stringify({
280
- compilerOptions: {
281
- target: 'ES2022',
282
- module: 'NodeNext',
283
- moduleResolution: 'NodeNext',
284
- outDir: './dist',
285
- strict: true,
286
- esModuleInterop: true,
287
- skipLibCheck: true,
288
- },
289
- include: ['*.ts', 'workflows/*.ts', 'runtime/*.ts'],
290
- }, null, 2),
291
- });
292
- break;
93
+ // Collect all output files
94
+ const files = artifacts.files.map((f) => ({
95
+ path: path.join(outputDir, f.relativePath),
96
+ content: f.content,
97
+ }));
98
+ if (compiledContent) {
99
+ const workflowOutputPath = path.join(outputDir, 'workflow.ts');
100
+ files.push({ path: workflowOutputPath, content: compiledContent });
293
101
  }
294
- return { files, serviceName, openApiSpec };
295
- }
296
- /**
297
- * Generate consolidated OpenAPI spec for multiple workflows
298
- */
299
- function generateMultiWorkflowOpenAPI(workflows, serviceName) {
300
- const paths = {};
301
- for (const workflow of workflows) {
302
- paths[`/api/${workflow.name}`] = {
303
- post: {
304
- operationId: `execute_${workflow.functionName}`,
305
- summary: `Execute ${workflow.name} workflow`,
306
- description: workflow.description || `Execute the ${workflow.name} workflow`,
307
- tags: ['workflows'],
308
- requestBody: {
309
- description: 'Workflow input parameters',
310
- required: true,
311
- content: {
312
- 'application/json': {
313
- schema: { type: 'object', additionalProperties: true },
314
- },
315
- },
316
- },
317
- responses: {
318
- '200': {
319
- description: 'Successful workflow execution',
320
- content: {
321
- 'application/json': {
322
- schema: {
323
- type: 'object',
324
- properties: {
325
- success: { type: 'boolean' },
326
- result: { type: 'object' },
327
- executionTime: { type: 'number' },
328
- requestId: { type: 'string' },
329
- },
330
- },
331
- },
332
- },
333
- },
334
- '404': { description: 'Workflow not found' },
335
- '500': { description: 'Execution error' },
336
- },
337
- },
338
- };
102
+ // Write files if not dry-run
103
+ if (!isDryRun) {
104
+ for (const file of files) {
105
+ const dirPath = path.dirname(file.path);
106
+ fs.mkdirSync(dirPath, { recursive: true });
107
+ fs.writeFileSync(file.path, file.content, 'utf-8');
108
+ }
339
109
  }
340
- // Add functions endpoint
341
- paths['/api/functions'] = {
342
- get: {
343
- operationId: 'list_functions',
344
- summary: 'List available functions',
345
- description: 'Returns all registered functions that can be used as parameters',
346
- tags: ['functions'],
347
- responses: {
348
- '200': {
349
- description: 'List of registered functions',
350
- content: { 'application/json': { schema: { type: 'array' } } },
351
- },
352
- },
353
- },
354
- };
355
- return {
356
- openapi: '3.0.3',
357
- info: {
358
- title: `${serviceName} API`,
359
- version: '1.0.0',
360
- description: `Multi-workflow service with ${workflows.length} workflows`,
361
- },
362
- paths,
363
- tags: [
364
- { name: 'workflows', description: 'Workflow execution endpoints' },
365
- { name: 'functions', description: 'Function registry endpoints' },
366
- ],
367
- };
368
- }
369
- /**
370
- * Generate Lambda multi-workflow handler
371
- */
372
- function generateLambdaMultiHandler(imports, entries, serviceName) {
373
- return `${getGeneratedBranding().header('export --target lambda --multi')}
374
- import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2, Context } from 'aws-lambda';
375
- ${imports}
376
- import { functionRegistry } from './runtime/function-registry.js';
377
- import './runtime/builtin-functions.js';
378
- import { openApiSpec } from './openapi.js';
379
-
380
- const workflows: Record<string, (execute: boolean, params: unknown) => Promise<unknown>> = {
381
- ${entries}
382
- };
383
-
384
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
385
- <html lang="en">
386
- <head>
387
- <meta charset="UTF-8">
388
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
389
- <title>${serviceName} API Documentation</title>
390
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
391
- </head>
392
- <body>
393
- <div id="swagger-ui"></div>
394
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
395
- <script>
396
- SwaggerUIBundle({
397
- url: './openapi.json',
398
- dom_id: '#swagger-ui',
399
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
400
- layout: 'BaseLayout'
401
- });
402
- </script>
403
- </body>
404
- </html>\`;
405
-
406
- export const handler = async (
407
- event: APIGatewayProxyEventV2,
408
- context: Context
409
- ): Promise<APIGatewayProxyResultV2> => {
410
- context.callbackWaitsForEmptyEventLoop = false;
411
- const path = event.rawPath || event.requestContext?.http?.path || '/';
412
- const method = event.requestContext?.http?.method || 'GET';
413
-
414
- if (path === '/api/openapi.json' && method === 'GET') {
415
- return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(openApiSpec) };
416
- }
417
-
418
- if (path === '/api/docs' && method === 'GET') {
419
- return { statusCode: 200, headers: { 'Content-Type': 'text/html' }, body: SWAGGER_UI_HTML };
420
- }
421
-
422
- if (path === '/api/functions' && method === 'GET') {
423
- const category = event.queryStringParameters?.category;
424
- const functions = functionRegistry.list(category as any);
425
- return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(functions) };
426
- }
427
-
428
- const workflowMatch = path.match(/^\\/api\\/([^\\/]+)$/);
429
- if (!workflowMatch) {
430
- return { statusCode: 404, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Not found' }) };
431
- }
432
-
433
- const workflowName = workflowMatch[1];
434
- const workflow = workflows[workflowName];
435
-
436
- if (!workflow) {
437
- return { statusCode: 404, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: \`Workflow '\${workflowName}' not found\`, availableWorkflows: Object.keys(workflows) }) };
438
- }
439
-
440
- if (method !== 'POST') {
441
- return { statusCode: 405, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Method not allowed' }) };
442
- }
443
-
444
- try {
445
- const body = typeof event.body === 'string' ? JSON.parse(event.body || '{}') : event.body || {};
446
- const startTime = Date.now();
447
- const result = await workflow(true, body);
448
-
449
- return {
450
- statusCode: 200,
451
- headers: { 'Content-Type': 'application/json', 'X-Execution-Time': \`\${Date.now() - startTime}ms\`, 'X-Request-Id': context.awsRequestId },
452
- body: JSON.stringify({ success: true, result, executionTime: Date.now() - startTime, requestId: context.awsRequestId }),
453
- };
454
- } catch (error) {
455
110
  return {
456
- statusCode: 500,
457
- headers: { 'Content-Type': 'application/json' },
458
- body: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error), requestId: context.awsRequestId }),
111
+ target: options.target,
112
+ files,
113
+ workflow: workflow.name,
114
+ description: workflow.description,
459
115
  };
460
- }
461
- };
462
- `;
463
- }
464
- /**
465
- * Generate Lambda SAM template for multi-workflow
466
- */
467
- function generateLambdaSAMMultiTemplate(serviceName, workflowCount) {
468
- return `AWSTemplateFormatVersion: '2010-09-09'
469
- Transform: AWS::Serverless-2016-10-31
470
- Description: Flow Weaver multi-workflow service - ${serviceName}
471
-
472
- Globals:
473
- Function:
474
- Timeout: 30
475
- Runtime: nodejs20.x
476
- MemorySize: 256
477
-
478
- Resources:
479
- WorkflowFunction:
480
- Type: AWS::Serverless::Function
481
- Properties:
482
- Handler: handler.handler
483
- CodeUri: .
484
- Description: Multi-workflow service with ${workflowCount} workflows
485
- Events:
486
- ApiProxy:
487
- Type: HttpApi
488
- Properties:
489
- Path: /api/{proxy+}
490
- Method: ANY
491
-
492
- Outputs:
493
- ApiEndpoint:
494
- Description: API base URL
495
- Value: !Sub "https://\${ServerlessHttpApi}.execute-api.\${AWS::Region}.amazonaws.com/api"
496
- `;
497
- }
498
- /**
499
- * Generate Vercel multi-workflow handler
500
- */
501
- function generateVercelMultiHandler(imports, entries, serviceName) {
502
- const adjustedImports = imports.replace(/\.\//g, '../');
503
- return `${getGeneratedBranding().header('export --target vercel --multi')}
504
- import type { VercelRequest, VercelResponse } from '@vercel/node';
505
- ${adjustedImports}
506
- import { functionRegistry } from '../runtime/function-registry.js';
507
- import '../runtime/builtin-functions.js';
508
- import { openApiSpec } from '../openapi.js';
509
-
510
- export const config = { runtime: 'nodejs20.x', maxDuration: 60 };
511
-
512
- const workflows: Record<string, (execute: boolean, params: unknown) => Promise<unknown>> = {
513
- ${entries}
514
- };
515
-
516
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
517
- <html lang="en">
518
- <head>
519
- <meta charset="UTF-8">
520
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
521
- <title>${serviceName} API Documentation</title>
522
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
523
- </head>
524
- <body>
525
- <div id="swagger-ui"></div>
526
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
527
- <script>
528
- SwaggerUIBundle({
529
- url: '/api/openapi.json',
530
- dom_id: '#swagger-ui',
531
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
532
- layout: 'BaseLayout'
533
- });
534
- </script>
535
- </body>
536
- </html>\`;
537
-
538
- export default async function handler(req: VercelRequest, res: VercelResponse) {
539
- const { workflow: workflowName } = req.query;
540
- const requestId = req.headers['x-vercel-id'] as string || crypto.randomUUID();
541
-
542
- if (workflowName === 'openapi.json' && req.method === 'GET') {
543
- return res.status(200).json(openApiSpec);
544
- }
545
-
546
- if (workflowName === 'docs' && req.method === 'GET') {
547
- res.setHeader('Content-Type', 'text/html');
548
- return res.status(200).send(SWAGGER_UI_HTML);
549
- }
550
-
551
- if (workflowName === 'functions' && req.method === 'GET') {
552
- const category = req.query.category as string | undefined;
553
- const functions = functionRegistry.list(category as any);
554
- return res.status(200).json(functions);
555
- }
556
-
557
- const workflow = typeof workflowName === 'string' ? workflows[workflowName] : undefined;
558
-
559
- if (!workflow) {
560
- return res.status(404).json({ error: \`Workflow '\${workflowName}' not found\`, availableWorkflows: Object.keys(workflows) });
561
- }
562
-
563
- if (req.method !== 'POST') {
564
- return res.status(405).json({ error: 'Method not allowed' });
565
- }
566
-
567
- try {
568
- const params = req.body || {};
569
- const startTime = Date.now();
570
- const result = await workflow(true, params);
571
-
572
- return res.setHeader('X-Request-Id', requestId).setHeader('X-Execution-Time', \`\${Date.now() - startTime}ms\`).status(200).json({ success: true, result, executionTime: Date.now() - startTime, requestId });
573
- } catch (error) {
574
- return res.setHeader('X-Request-Id', requestId).status(500).json({ success: false, error: { code: 'EXECUTION_ERROR', message: error instanceof Error ? error.message : String(error) }, requestId });
575
- }
576
- }
577
- `;
578
116
  }
579
117
  /**
580
- * Generate Cloudflare multi-workflow handler
118
+ * Multi-workflow export through the target registry.
581
119
  */
582
- function generateCloudflareMultiHandler(imports, entries, serviceName) {
583
- return `${getGeneratedBranding().header('export --target cloudflare --multi')}
584
- ${imports}
585
- import { functionRegistry } from './runtime/function-registry.js';
586
- import './runtime/builtin-functions.js';
587
- import { openApiSpec } from './openapi.js';
588
-
589
- interface Env {}
590
-
591
- const workflows: Record<string, (execute: boolean, params: unknown) => Promise<unknown>> = {
592
- ${entries}
593
- };
594
-
595
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
596
- <html lang="en">
597
- <head>
598
- <meta charset="UTF-8">
599
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
600
- <title>${serviceName} API Documentation</title>
601
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
602
- </head>
603
- <body>
604
- <div id="swagger-ui"></div>
605
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
606
- <script>
607
- SwaggerUIBundle({
608
- url: '/api/openapi.json',
609
- dom_id: '#swagger-ui',
610
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
611
- layout: 'BaseLayout'
612
- });
613
- </script>
614
- </body>
615
- </html>\`;
616
-
617
- export default {
618
- async fetch(request: Request, env: Env): Promise<Response> {
619
- const url = new URL(request.url);
620
- const path = url.pathname;
621
- const method = request.method;
622
-
623
- if (path === '/api/openapi.json' && method === 'GET') {
624
- return new Response(JSON.stringify(openApiSpec), { status: 200, headers: { 'Content-Type': 'application/json' } });
625
- }
626
-
627
- if (path === '/api/docs' && method === 'GET') {
628
- return new Response(SWAGGER_UI_HTML, { status: 200, headers: { 'Content-Type': 'text/html' } });
629
- }
630
-
631
- if (path === '/api/functions' && method === 'GET') {
632
- const category = url.searchParams.get('category');
633
- const functions = functionRegistry.list(category as any);
634
- return new Response(JSON.stringify(functions), { status: 200, headers: { 'Content-Type': 'application/json' } });
635
- }
636
-
637
- const workflowMatch = path.match(/^\\/api\\/([^\\/]+)$/);
638
- if (!workflowMatch) {
639
- return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });
640
- }
641
-
642
- const workflowName = workflowMatch[1];
643
- const workflow = workflows[workflowName];
644
-
645
- if (!workflow) {
646
- return new Response(JSON.stringify({ error: \`Workflow '\${workflowName}' not found\`, availableWorkflows: Object.keys(workflows) }), { status: 404, headers: { 'Content-Type': 'application/json' } });
647
- }
648
-
649
- if (method !== 'POST') {
650
- return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405, headers: { 'Content-Type': 'application/json' } });
651
- }
652
-
653
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
654
-
655
- try {
656
- const body = await request.json() as Record<string, unknown>;
657
- const startTime = performance.now();
658
- const result = await workflow(true, body);
659
-
660
- return new Response(JSON.stringify({ success: true, result, executionTime: Math.round(performance.now() - startTime), requestId }), {
661
- status: 200,
662
- headers: { 'Content-Type': 'application/json', 'X-Request-Id': requestId, 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\` },
663
- });
664
- } catch (error) {
665
- return new Response(JSON.stringify({ success: false, error: { code: 'EXECUTION_ERROR', message: error instanceof Error ? error.message : String(error) }, requestId }), {
666
- status: 500,
667
- headers: { 'Content-Type': 'application/json', 'X-Request-Id': requestId },
668
- });
669
- }
670
- },
671
- } satisfies ExportedHandler<Env>;
672
- `;
673
- }
674
- /**
675
- * Generate Inngest multi-workflow handler
676
- */
677
- function generateInngestMultiHandler(imports, entries, serviceName, workflows) {
678
- const serviceId = serviceName
679
- .replace(/([a-z])([A-Z])/g, '$1-$2')
680
- .replace(/[^a-zA-Z0-9-]/g, '-')
681
- .replace(/-+/g, '-')
682
- .toLowerCase();
683
- const functionDefs = workflows.map((w) => {
684
- const fnId = w.name
685
- .replace(/([a-z])([A-Z])/g, '$1-$2')
686
- .replace(/[^a-zA-Z0-9-]/g, '-')
687
- .replace(/-+/g, '-')
688
- .toLowerCase();
689
- const fnVar = `fn_${w.functionName.replace(/[^a-zA-Z0-9_$]/g, '_')}`;
690
- return `const ${fnVar} = inngest.createFunction(
691
- { id: '${fnId}', name: '${w.name}' },
692
- { event: 'fw/${fnId}.execute' },
693
- async ({ event, step }) => {
694
- const params = event.data ?? {};
695
- const result = await step.run('execute-workflow', async () => {
696
- return ${w.functionName}(true, params);
697
- });
698
- return { success: true, result };
699
- }
700
- );`;
701
- }).join('\n\n');
702
- const functionList = workflows
703
- .map((w) => `fn_${w.functionName.replace(/[^a-zA-Z0-9_$]/g, '_')}`)
704
- .join(', ');
705
- return `${getGeneratedBranding().header('export --target inngest --multi')}
706
- import { Inngest } from 'inngest';
707
- import { serve } from 'inngest/express';
708
- import express from 'express';
709
- ${imports}
710
- import { functionRegistry } from './runtime/function-registry.js';
711
- import './runtime/builtin-functions.js';
712
- import { openApiSpec } from './openapi.js';
713
-
714
- const inngest = new Inngest({ id: '${serviceId}' });
715
-
716
- ${functionDefs}
717
-
718
- const functions = [${functionList}];
719
-
720
- type WorkflowHandler = (execute: boolean, params: unknown) => Promise<unknown>;
721
- const directHandlers: Record<string, WorkflowHandler> = {
722
- ${entries}
723
- };
724
-
725
- const app = express();
726
- app.use(express.json());
727
-
728
- app.use('/api/inngest', serve({ client: inngest, functions }));
729
-
730
- app.get('/api/openapi.json', (_req, res) => { res.json(openApiSpec); });
731
-
732
- app.get('/api/functions', (req, res) => {
733
- const category = req.query.category as string | undefined;
734
- res.json(functionRegistry.list(category as any));
735
- });
736
-
737
- app.post('/api/invoke/:workflowName', async (req, res) => {
738
- const workflow = directHandlers[req.params.workflowName];
739
- if (!workflow) {
740
- return res.status(404).json({ error: \`Workflow '\${req.params.workflowName}' not found\`, availableWorkflows: Object.keys(directHandlers) });
741
- }
742
- try {
743
- const startTime = Date.now();
744
- const result = await workflow(true, req.body || {});
745
- res.json({ success: true, result, executionTime: Date.now() - startTime });
746
- } catch (error) {
747
- res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) });
748
- }
749
- });
750
-
751
- export const handler = app;
752
- export default app;
753
- `;
754
- }
755
- /**
756
- * Export a workflow for serverless deployment
757
- */
758
- export async function exportWorkflow(options) {
759
- // If multi mode, use multi-workflow export
760
- if (options.multi) {
761
- return exportMultiWorkflow(options);
762
- }
763
- const inputPath = path.resolve(options.input);
764
- const outputDir = path.resolve(options.output);
765
- const isDryRun = options.dryRun ?? false;
120
+ async function exportMultiWorkflowViaRegistry(target, inputPath, outputDir, isDryRun, options) {
766
121
  // Validate input file exists
767
122
  if (!fs.existsSync(inputPath)) {
768
123
  throw new Error(`Input file not found: ${inputPath}`);
@@ -773,81 +128,55 @@ export async function exportWorkflow(options) {
773
128
  if (parseResult.workflows.length === 0) {
774
129
  throw new Error(`No workflows found in ${inputPath}`);
775
130
  }
776
- // Select workflow
777
- const workflow = options.workflow
778
- ? parseResult.workflows.find((w) => w.name === options.workflow || w.functionName === options.workflow)
779
- : parseResult.workflows[0];
780
- if (!workflow) {
781
- const available = parseResult.workflows.map((w) => w.name).join(', ');
782
- throw new Error(`Workflow "${options.workflow}" not found. Available: ${available}`);
783
- }
784
- // For dry-run, use temp directory for compilation
785
- const workDir = isDryRun ? path.join(os.tmpdir(), `fw-export-dryrun-${Date.now()}`) : outputDir;
786
- // Create working directory
787
- fs.mkdirSync(workDir, { recursive: true });
788
- let compiledContent;
789
- let compiledPath;
790
- try {
791
- // Compile workflow to working directory
792
- compiledPath = await compileToOutput(inputPath, workflow.functionName, workDir, options.production);
793
- compiledContent = fs.readFileSync(compiledPath, 'utf8');
794
- }
795
- finally {
796
- // Clean up temp directory in dry-run mode
797
- if (isDryRun) {
798
- try {
799
- fs.rmSync(workDir, { recursive: true, force: true });
800
- }
801
- catch {
802
- // Ignore cleanup errors
803
- }
131
+ // Filter workflows if specific ones are requested
132
+ let selectedWorkflows = parseResult.workflows;
133
+ if (options.workflows && options.workflows.length > 0) {
134
+ selectedWorkflows = parseResult.workflows.filter((w) => options.workflows.includes(w.name) || options.workflows.includes(w.functionName));
135
+ if (selectedWorkflows.length === 0) {
136
+ const available = parseResult.workflows.map((w) => w.name).join(', ');
137
+ throw new Error(`None of the requested workflows found. Available: ${available}`);
804
138
  }
805
139
  }
806
- // Generate handler based on target
807
- let handler;
808
- if (options.target === 'inngest' && options.durableSteps) {
809
- // Use deep generator for durable Inngest steps
810
- const allNodeTypes = [...(workflow.nodeTypes || [])];
811
- handler = generateInngestFunction(workflow, allNodeTypes, {
812
- production: options.production ?? true,
813
- });
814
- }
815
- else {
816
- handler = generateHandler(options.target, compiledPath, workflow.functionName);
817
- }
818
- // Determine output file name
819
- const handlerFileName = getHandlerFileName(options.target, workflow.name);
820
- const handlerPath = path.join(outputDir, handlerFileName);
821
- const workflowOutputPath = path.join(outputDir, 'workflow.ts');
822
- // Generate config files (use outputDir for paths even in dry-run)
823
- const configFiles = generateConfigFiles(options.target, workflow.name, outputDir, workflow.description);
824
- // Only write files if not a dry-run
140
+ const serviceName = path.basename(options.input, path.extname(options.input)) + '-service';
141
+ // Build bundle items
142
+ const bundleWorkflows = selectedWorkflows.map((w) => ({
143
+ name: w.name,
144
+ functionName: w.functionName,
145
+ description: w.description,
146
+ expose: true,
147
+ }));
148
+ if (!target.generateBundle) {
149
+ throw new Error(`Target "${options.target}" does not support multi-workflow export`);
150
+ }
151
+ const artifacts = await target.generateBundle(bundleWorkflows, [], {
152
+ sourceFile: inputPath,
153
+ workflowName: serviceName,
154
+ displayName: serviceName,
155
+ outputDir,
156
+ production: options.production ?? true,
157
+ includeDocs: options.includeDocs,
158
+ targetOptions: {
159
+ ...(options.durableSteps && { durableSteps: true }),
160
+ },
161
+ });
162
+ // Collect output files
163
+ const files = artifacts.files.map((f) => ({
164
+ path: path.join(outputDir, f.relativePath),
165
+ content: f.content,
166
+ }));
167
+ // Write files if not dry-run
825
168
  if (!isDryRun) {
826
- // Create output directory
827
- fs.mkdirSync(outputDir, { recursive: true });
828
- // Write handler file (ensure parent directory exists for api/ subdirectory)
829
- const handlerDir = path.dirname(handlerPath);
830
- if (!fs.existsSync(handlerDir)) {
831
- fs.mkdirSync(handlerDir, { recursive: true });
169
+ for (const file of files) {
170
+ const dirPath = path.dirname(file.path);
171
+ fs.mkdirSync(dirPath, { recursive: true });
172
+ fs.writeFileSync(file.path, file.content, 'utf-8');
832
173
  }
833
- fs.writeFileSync(handlerPath, handler);
834
- // Write config files
835
- for (const file of configFiles) {
836
- fs.writeFileSync(file.path, file.content);
837
- }
838
- // Write compiled workflow
839
- fs.writeFileSync(workflowOutputPath, compiledContent);
840
174
  }
841
- const files = [
842
- { path: handlerPath, content: handler },
843
- ...configFiles,
844
- { path: workflowOutputPath, content: compiledContent },
845
- ];
846
175
  return {
847
176
  target: options.target,
848
177
  files,
849
- workflow: workflow.name,
850
- description: workflow.description,
178
+ workflow: serviceName,
179
+ workflows: selectedWorkflows.map((w) => w.name),
851
180
  };
852
181
  }
853
182
  /**
@@ -867,208 +196,11 @@ async function compileToOutput(inputPath, functionName, outputDir, production) {
867
196
  return outputPath;
868
197
  }
869
198
  /**
870
- * Generate handler code from template
871
- */
872
- function generateHandler(target, workflowPath, functionName) {
873
- const template = getTemplate(target);
874
- // Vercel handlers live in api/ subdirectory, so import from parent
875
- const relativePath = target === 'vercel'
876
- ? `../${path.basename(workflowPath).replace('.ts', '.js')}`
877
- : `./${path.basename(workflowPath).replace('.ts', '.js')}`;
878
- return template
879
- .replace('{{GENERATED_HEADER}}', getGeneratedBranding().header(`export --target ${target}`))
880
- .replace('{{WORKFLOW_IMPORT}}', `import { ${functionName} } from '${relativePath}';`)
881
- .replace(/\{\{FUNCTION_NAME\}\}/g, functionName)
882
- .replace('{{MAX_DURATION}}', '60');
883
- }
884
- /**
885
- * Get template for target platform
886
- */
887
- function getTemplate(target) {
888
- switch (target) {
889
- case 'lambda':
890
- return LAMBDA_TEMPLATE;
891
- case 'vercel':
892
- return VERCEL_TEMPLATE;
893
- case 'cloudflare':
894
- return CLOUDFLARE_TEMPLATE;
895
- case 'inngest':
896
- return INNGEST_TEMPLATE;
897
- }
898
- }
899
- /**
900
- * Get handler file name for target platform
901
- */
902
- function getHandlerFileName(target, workflowName) {
903
- switch (target) {
904
- case 'lambda':
905
- return 'handler.ts';
906
- case 'vercel':
907
- // Vercel uses file-based routing under api/
908
- return `api/${workflowName}.ts`;
909
- case 'cloudflare':
910
- return 'index.ts';
911
- case 'inngest':
912
- return 'handler.ts';
913
- }
914
- }
915
- /**
916
- * Generate config files for target platform
917
- */
918
- function generateConfigFiles(target, workflowName, outputDir, description) {
919
- const files = [];
920
- switch (target) {
921
- case 'lambda':
922
- // Generate SAM template
923
- files.push({
924
- path: path.join(outputDir, 'template.yaml'),
925
- content: SAM_TEMPLATE.replace(/\{\{WORKFLOW_NAME\}\}/g, workflowName)
926
- .replace('{{WORKFLOW_DESCRIPTION}}', description || `Flow Weaver workflow: ${workflowName}`)
927
- .replace(/\{\{WORKFLOW_PATH\}\}/g, workflowName),
928
- });
929
- // Generate package.json for Lambda
930
- files.push({
931
- path: path.join(outputDir, 'package.json'),
932
- content: JSON.stringify({
933
- name: `fw-${workflowName}`,
934
- version: '1.0.0',
935
- type: 'module',
936
- main: 'handler.js',
937
- scripts: {
938
- build: 'tsc',
939
- deploy: 'sam build && sam deploy --guided',
940
- },
941
- devDependencies: {
942
- '@types/aws-lambda': '^8.10.0',
943
- typescript: '^5.0.0',
944
- },
945
- }, null, 2),
946
- });
947
- // Generate tsconfig.json
948
- files.push({
949
- path: path.join(outputDir, 'tsconfig.json'),
950
- content: JSON.stringify({
951
- compilerOptions: {
952
- target: 'ES2022',
953
- module: 'NodeNext',
954
- moduleResolution: 'NodeNext',
955
- outDir: './dist',
956
- strict: true,
957
- esModuleInterop: true,
958
- skipLibCheck: true,
959
- },
960
- include: ['*.ts'],
961
- }, null, 2),
962
- });
963
- break;
964
- case 'vercel':
965
- // Generate vercel.json
966
- files.push({
967
- path: path.join(outputDir, 'vercel.json'),
968
- content: JSON.stringify({
969
- functions: {
970
- [`api/${workflowName}.ts`]: {
971
- memory: 1024,
972
- maxDuration: 60,
973
- },
974
- },
975
- }, null, 2),
976
- });
977
- break;
978
- case 'cloudflare':
979
- // Generate wrangler.toml
980
- files.push({
981
- path: path.join(outputDir, 'wrangler.toml'),
982
- content: `name = "${workflowName}"
983
- main = "dist/index.js"
984
- compatibility_date = "2024-01-01"
985
-
986
- [build]
987
- command = "npm run build"
988
- `,
989
- });
990
- // Generate package.json for Cloudflare
991
- files.push({
992
- path: path.join(outputDir, 'package.json'),
993
- content: JSON.stringify({
994
- name: `fw-${workflowName}`,
995
- version: '1.0.0',
996
- type: 'module',
997
- scripts: {
998
- build: 'tsc',
999
- deploy: 'wrangler deploy',
1000
- dev: 'wrangler dev',
1001
- },
1002
- devDependencies: {
1003
- '@cloudflare/workers-types': '^4.0.0',
1004
- typescript: '^5.0.0',
1005
- wrangler: '^3.0.0',
1006
- },
1007
- }, null, 2),
1008
- });
1009
- // Generate tsconfig.json
1010
- files.push({
1011
- path: path.join(outputDir, 'tsconfig.json'),
1012
- content: JSON.stringify({
1013
- compilerOptions: {
1014
- target: 'ES2022',
1015
- module: 'ESNext',
1016
- moduleResolution: 'Bundler',
1017
- outDir: './dist',
1018
- strict: true,
1019
- skipLibCheck: true,
1020
- types: ['@cloudflare/workers-types'],
1021
- },
1022
- include: ['*.ts'],
1023
- }, null, 2),
1024
- });
1025
- break;
1026
- case 'inngest':
1027
- // Generate package.json for Inngest
1028
- files.push({
1029
- path: path.join(outputDir, 'package.json'),
1030
- content: JSON.stringify({
1031
- name: `fw-${workflowName}`,
1032
- version: '1.0.0',
1033
- type: 'module',
1034
- main: 'handler.js',
1035
- scripts: {
1036
- build: 'tsc',
1037
- dev: 'npx inngest-cli@latest dev & npx tsx handler.ts',
1038
- serve: 'npx tsx handler.ts',
1039
- },
1040
- dependencies: {
1041
- inngest: '^3.0.0',
1042
- },
1043
- devDependencies: {
1044
- typescript: '^5.0.0',
1045
- },
1046
- }, null, 2),
1047
- });
1048
- // Generate tsconfig.json
1049
- files.push({
1050
- path: path.join(outputDir, 'tsconfig.json'),
1051
- content: JSON.stringify({
1052
- compilerOptions: {
1053
- target: 'ES2022',
1054
- module: 'NodeNext',
1055
- moduleResolution: 'NodeNext',
1056
- outDir: './dist',
1057
- strict: true,
1058
- esModuleInterop: true,
1059
- skipLibCheck: true,
1060
- },
1061
- include: ['*.ts'],
1062
- }, null, 2),
1063
- });
1064
- break;
1065
- }
1066
- return files;
1067
- }
1068
- /**
1069
- * List supported export targets
199
+ * List installed export targets by querying the registry.
1070
200
  */
1071
- export function getSupportedTargets() {
1072
- return ['lambda', 'vercel', 'cloudflare', 'inngest'];
201
+ export async function getSupportedTargets(projectDir) {
202
+ const { createTargetRegistry } = await import('../deployment/index.js');
203
+ const registry = await createTargetRegistry(projectDir ?? process.cwd());
204
+ return registry.getNames();
1073
205
  }
1074
206
  //# sourceMappingURL=index.js.map