lua-cli 3.0.2-alpha.1 → 3.0.2-alpha.2

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.
@@ -7,7 +7,7 @@ import path from "path";
7
7
  import { build } from "esbuild";
8
8
  import { Project, Node } from "ts-morph";
9
9
  import { writeProgress } from "./cli.js";
10
- import { COMPILE_DIRS, COMPILE_FILES, ESBUILD_TOOL_CONFIG, ESBUILD_INDEX_CONFIG, DEFAULT_INPUT_SCHEMA, } from '../config/compile.constants.js';
10
+ import { COMPILE_DIRS, COMPILE_FILES, ESBUILD_TOOL_CONFIG, ESBUILD_INDEX_CONFIG, DEFAULT_INPUT_SCHEMA, EXTERNAL_PACKAGES, } from '../config/compile.constants.js';
11
11
  import { wrapToolForVM, createExecuteFunction, evaluateZodSchemaToJsonSchema } from './compile.js';
12
12
  /**
13
13
  * Helper function to remove lua-cli and api-exports imports from source code.
@@ -30,6 +30,108 @@ function stripLuaCliImports(sourceCode) {
30
30
  : '// lua-cli/skill imports removed - using sandbox globals';
31
31
  });
32
32
  }
33
+ /**
34
+ * Extracts and filters relevant imports from source code.
35
+ * Removes lua-cli internal classes and API imports that are available in the sandbox.
36
+ *
37
+ * @param sourceFilePath - Path to the source file
38
+ * @returns Array of relevant import statements
39
+ */
40
+ function extractRelevantImports(sourceFilePath) {
41
+ const sourceContent = fs.readFileSync(sourceFilePath, 'utf8');
42
+ // Extract all import statements from the source
43
+ const importPattern = /^import\s+.+\s+from\s+['"][^'"]+['"];?$/gm;
44
+ const imports = sourceContent.match(importPattern) || [];
45
+ // Filter to only imports that might be used (exclude lua-cli internal classes and APIs)
46
+ return imports.filter(imp => !imp.includes('LuaTool') &&
47
+ !imp.includes('LuaWebhook') &&
48
+ !imp.includes('LuaJob') &&
49
+ !imp.includes('PreProcessor') &&
50
+ !imp.includes('PostProcessor') &&
51
+ !imp.includes('LuaAgent') &&
52
+ !imp.includes('lua-cli') && // Exclude all lua-cli imports (they're available in sandbox)
53
+ !imp.includes('api-exports') && // Exclude api-exports
54
+ !imp.includes('../../../dist/api-exports') // Exclude direct api-exports imports
55
+ );
56
+ }
57
+ /**
58
+ * Generic function to bundle and compress execute function code with dependencies.
59
+ *
60
+ * @param executeFunction - Raw execute function code
61
+ * @param componentName - Name of the component (webhook/job/processor)
62
+ * @param componentType - Type identifier for temp files
63
+ * @param vmWrapperGenerator - Function that generates the VM wrapper code
64
+ * @param distDir - Distribution directory
65
+ * @param sourceFilePath - Path to source file containing the component
66
+ * @returns Compressed base64-encoded bundled code
67
+ */
68
+ async function bundleAndCompressExecuteFunction(executeFunction, componentName, componentType, vmWrapperGenerator, distDir, sourceFilePath) {
69
+ const { compressCode } = await import('./compile.js');
70
+ // Create temporary file with the execute function wrapped as a module
71
+ const tempDir = path.join(distDir, '.temp');
72
+ if (!fs.existsSync(tempDir)) {
73
+ fs.mkdirSync(tempDir, { recursive: true });
74
+ }
75
+ const tempFile = path.join(tempDir, `${componentName}-${componentType}.ts`);
76
+ const tempOutput = path.join(tempDir, `${componentName}-${componentType}.js`);
77
+ try {
78
+ // Extract relevant imports from source file
79
+ const relevantImports = extractRelevantImports(sourceFilePath);
80
+ // Write execute function as a module export with all relevant imports
81
+ const moduleCode = `
82
+ // ${componentType} execute function for ${componentName}
83
+ ${relevantImports.join('\n')}
84
+
85
+ // The execute function with all dependencies available
86
+ const executeFunc = ${executeFunction};
87
+
88
+ export default executeFunc;
89
+ `;
90
+ fs.writeFileSync(tempFile, moduleCode);
91
+ // Bundle with esbuild using the source file's directory for import resolution
92
+ await build({
93
+ entryPoints: [tempFile],
94
+ bundle: true,
95
+ platform: 'node',
96
+ target: 'node16',
97
+ format: 'cjs',
98
+ minify: true,
99
+ outfile: tempOutput,
100
+ external: [...EXTERNAL_PACKAGES], // Use same external packages as tools
101
+ plugins: [sandboxGlobalsPlugin],
102
+ absWorkingDir: path.dirname(sourceFilePath), // Use source file's directory for resolution
103
+ });
104
+ // Read bundled code
105
+ const bundledCode = fs.readFileSync(tempOutput, 'utf8');
106
+ // Wrap for VM execution using provided generator
107
+ const wrappedCode = vmWrapperGenerator(bundledCode);
108
+ // Compress the wrapped code
109
+ const compressed = compressCode(wrappedCode);
110
+ // Clean up temp files
111
+ try {
112
+ fs.unlinkSync(tempFile);
113
+ fs.unlinkSync(tempOutput);
114
+ }
115
+ catch (cleanupError) {
116
+ // Ignore cleanup errors
117
+ }
118
+ return compressed;
119
+ }
120
+ catch (error) {
121
+ console.warn(`Warning: Could not bundle ${componentType} ${componentName} code:`, error);
122
+ // Clean up on error
123
+ try {
124
+ if (fs.existsSync(tempFile))
125
+ fs.unlinkSync(tempFile);
126
+ if (fs.existsSync(tempOutput))
127
+ fs.unlinkSync(tempOutput);
128
+ }
129
+ catch (cleanupError) {
130
+ // Ignore cleanup errors
131
+ }
132
+ return '';
133
+ }
134
+ }
33
135
  /**
34
136
  * esbuild plugin to inject sandbox globals instead of requiring lua-cli
35
137
  * This removes require("lua-cli") statements and injects the global API objects
@@ -226,6 +328,7 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
226
328
  let querySchema = undefined;
227
329
  let headerSchema = undefined;
228
330
  let bodySchema = undefined;
331
+ let sourceFilePath = indexFile.getFilePath();
229
332
  // Helper function to search for webhook in a file
230
333
  const searchFileForWebhook = (file) => {
231
334
  file.forEachDescendant((node) => {
@@ -271,6 +374,8 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
271
374
  querySchema = foundQuerySchema;
272
375
  headerSchema = foundHeaderSchema;
273
376
  bodySchema = foundBodySchema;
377
+ // Update source file path to the file where webhook was found
378
+ sourceFilePath = file.getFilePath();
274
379
  }
275
380
  }
276
381
  }
@@ -315,7 +420,7 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
315
420
  // Bundle and compress the execute function (like tools)
316
421
  let compressedCode = '';
317
422
  if (executeFunction) {
318
- compressedCode = await bundleAndCompressWebhookCode(executeFunction, webhook.name, distDir);
423
+ compressedCode = await bundleAndCompressWebhookCode(executeFunction, webhook.name, distDir, sourceFilePath);
319
424
  }
320
425
  return {
321
426
  ...webhook,
@@ -332,44 +437,16 @@ export async function bundleWebhook(webhook, indexFile, distDir, project) {
332
437
  /**
333
438
  * Bundles and compresses webhook execute function code.
334
439
  * Creates a temporary file, bundles with esbuild, compresses with gzip.
440
+ * Includes all imports from the source file to ensure dependencies are bundled.
335
441
  *
336
442
  * @param executeFunction - Raw execute function code
337
443
  * @param webhookName - Name of the webhook
338
444
  * @param distDir - Distribution directory
445
+ * @param sourceFilePath - Path to the source file containing the webhook
339
446
  * @returns Compressed base64-encoded bundled code
340
447
  */
341
- async function bundleAndCompressWebhookCode(executeFunction, webhookName, distDir) {
342
- const { compressCode } = await import('./compile.js');
343
- // Create temporary file with the execute function wrapped as a module
344
- const tempDir = path.join(distDir, '.temp');
345
- if (!fs.existsSync(tempDir)) {
346
- fs.mkdirSync(tempDir, { recursive: true });
347
- }
348
- const tempFile = path.join(tempDir, `${webhookName}-webhook.ts`);
349
- const tempOutput = path.join(tempDir, `${webhookName}-webhook.js`);
350
- try {
351
- // Write execute function as a module export
352
- const moduleCode = stripLuaCliImports(`
353
- // Webhook execute function for ${webhookName}
354
- export default ${executeFunction};
355
- `);
356
- fs.writeFileSync(tempFile, moduleCode);
357
- // Bundle with esbuild
358
- await build({
359
- entryPoints: [tempFile],
360
- bundle: true,
361
- platform: 'node',
362
- target: 'node16',
363
- format: 'cjs',
364
- minify: true,
365
- outfile: tempOutput,
366
- external: [], // Bundle everything
367
- plugins: [sandboxGlobalsPlugin],
368
- });
369
- // Read bundled code
370
- let bundledCode = fs.readFileSync(tempOutput, 'utf8');
371
- // Wrap for webhook VM execution (similar to tools)
372
- const wrappedCode = `(async (query, headers, body) => {
448
+ async function bundleAndCompressWebhookCode(executeFunction, webhookName, distDir, sourceFilePath) {
449
+ return bundleAndCompressExecuteFunction(executeFunction, webhookName, 'webhook', (bundledCode) => `(async (query, headers, body) => {
373
450
 
374
451
  // Execute the bundled webhook code
375
452
  ${bundledCode}
@@ -379,33 +456,7 @@ export default ${executeFunction};
379
456
 
380
457
  // Execute with three separate parameters (not an object)
381
458
  return await executeFunction(query, headers, body);
382
- })`;
383
- // Compress the wrapped code
384
- const compressed = compressCode(wrappedCode);
385
- // Clean up temp files
386
- try {
387
- fs.unlinkSync(tempFile);
388
- fs.unlinkSync(tempOutput);
389
- }
390
- catch (cleanupError) {
391
- // Ignore cleanup errors
392
- }
393
- return compressed;
394
- }
395
- catch (error) {
396
- console.warn(`Warning: Could not bundle webhook ${webhookName} code:`, error);
397
- // Clean up on error
398
- try {
399
- if (fs.existsSync(tempFile))
400
- fs.unlinkSync(tempFile);
401
- if (fs.existsSync(tempOutput))
402
- fs.unlinkSync(tempOutput);
403
- }
404
- catch (cleanupError) {
405
- // Ignore cleanup errors
406
- }
407
- return '';
408
- }
459
+ })`, distDir, sourceFilePath);
409
460
  }
410
461
  /**
411
462
  * Extracts and bundles job execute function.
@@ -421,6 +472,7 @@ export async function bundleJob(job, indexFile, distDir, project) {
421
472
  try {
422
473
  // Find the LuaJob constructor in the AST
423
474
  let executeFunction = '';
475
+ let sourceFilePath = indexFile.getFilePath();
424
476
  // Helper function to search for job in a file
425
477
  const searchFileForJob = (file) => {
426
478
  file.forEachDescendant((node) => {
@@ -451,6 +503,8 @@ export async function bundleJob(job, indexFile, distDir, project) {
451
503
  // Only set executeFunction if this is the matching job
452
504
  if (isMatchingJob && foundExecute) {
453
505
  executeFunction = foundExecute;
506
+ // Update source file path to the file where job was found
507
+ sourceFilePath = file.getFilePath();
454
508
  }
455
509
  }
456
510
  }
@@ -484,7 +538,7 @@ export async function bundleJob(job, indexFile, distDir, project) {
484
538
  // Bundle and compress the execute function (like tools)
485
539
  let compressedCode = '';
486
540
  if (executeFunction) {
487
- compressedCode = await bundleAndCompressJobCode(executeFunction, job.name, distDir);
541
+ compressedCode = await bundleAndCompressJobCode(executeFunction, job.name, distDir, sourceFilePath);
488
542
  }
489
543
  return {
490
544
  ...job,
@@ -500,44 +554,16 @@ export async function bundleJob(job, indexFile, distDir, project) {
500
554
  /**
501
555
  * Bundles and compresses job execute function code.
502
556
  * Creates a temporary file, bundles with esbuild, compresses with gzip.
557
+ * Includes all imports from the source file to ensure dependencies are bundled.
503
558
  *
504
559
  * @param executeFunction - Raw execute function code
505
560
  * @param jobName - Name of the job
506
561
  * @param distDir - Distribution directory
562
+ * @param sourceFilePath - Path to the source file containing the job
507
563
  * @returns Compressed base64-encoded bundled code
508
564
  */
509
- async function bundleAndCompressJobCode(executeFunction, jobName, distDir) {
510
- const { compressCode } = await import('./compile.js');
511
- // Create temporary file with the execute function wrapped as a module
512
- const tempDir = path.join(distDir, '.temp');
513
- if (!fs.existsSync(tempDir)) {
514
- fs.mkdirSync(tempDir, { recursive: true });
515
- }
516
- const tempFile = path.join(tempDir, `${jobName}-job.ts`);
517
- const tempOutput = path.join(tempDir, `${jobName}-job.js`);
518
- try {
519
- // Write execute function as a module export
520
- const moduleCode = stripLuaCliImports(`
521
- // Job execute function for ${jobName}
522
- export default ${executeFunction};
523
- `);
524
- fs.writeFileSync(tempFile, moduleCode);
525
- // Bundle with esbuild
526
- await build({
527
- entryPoints: [tempFile],
528
- bundle: true,
529
- platform: 'node',
530
- target: 'node16',
531
- format: 'cjs',
532
- minify: true,
533
- outfile: tempOutput,
534
- external: [], // Bundle everything
535
- plugins: [sandboxGlobalsPlugin],
536
- });
537
- // Read bundled code
538
- let bundledCode = fs.readFileSync(tempOutput, 'utf8');
539
- // Wrap for job VM execution (similar to tools, but accepts job parameter)
540
- const wrappedCode = `(async (job) => {
565
+ async function bundleAndCompressJobCode(executeFunction, jobName, distDir, sourceFilePath) {
566
+ return bundleAndCompressExecuteFunction(executeFunction, jobName, 'job', (bundledCode) => `(async (job) => {
541
567
  // Execute the bundled job code
542
568
  ${bundledCode}
543
569
 
@@ -546,33 +572,7 @@ export default ${executeFunction};
546
572
 
547
573
  // Execute job with job instance parameter
548
574
  return await executeFunction(job);
549
- })`;
550
- // Compress the wrapped code
551
- const compressed = compressCode(wrappedCode);
552
- // Clean up temp files
553
- try {
554
- fs.unlinkSync(tempFile);
555
- fs.unlinkSync(tempOutput);
556
- }
557
- catch (cleanupError) {
558
- // Ignore cleanup errors
559
- }
560
- return compressed;
561
- }
562
- catch (error) {
563
- console.warn(`Warning: Could not bundle job ${jobName} code:`, error);
564
- // Clean up on error
565
- try {
566
- if (fs.existsSync(tempFile))
567
- fs.unlinkSync(tempFile);
568
- if (fs.existsSync(tempOutput))
569
- fs.unlinkSync(tempOutput);
570
- }
571
- catch (cleanupError) {
572
- // Ignore cleanup errors
573
- }
574
- return '';
575
- }
575
+ })`, distDir, sourceFilePath);
576
576
  }
577
577
  /**
578
578
  * Bundles and compresses preprocessor execute function code.
@@ -581,6 +581,7 @@ export async function bundlePreProcessor(preprocessor, indexFile, distDir, proje
581
581
  writeProgress(`📦 Bundling preprocessor ${preprocessor.name}...`);
582
582
  try {
583
583
  let executeFunction = '';
584
+ let sourceFilePath = indexFile.getFilePath();
584
585
  const searchFileForPreProcessor = (file) => {
585
586
  file.forEachDescendant((node) => {
586
587
  if (Node.isNewExpression(node)) {
@@ -608,6 +609,8 @@ export async function bundlePreProcessor(preprocessor, indexFile, distDir, proje
608
609
  });
609
610
  if (isMatching && foundExecute) {
610
611
  executeFunction = foundExecute;
612
+ // Update source file path to the file where preprocessor was found
613
+ sourceFilePath = file.getFilePath();
611
614
  }
612
615
  }
613
616
  }
@@ -639,7 +642,7 @@ export async function bundlePreProcessor(preprocessor, indexFile, distDir, proje
639
642
  }
640
643
  let compressedCode = '';
641
644
  if (executeFunction) {
642
- compressedCode = await bundleAndCompressProcessorCode(executeFunction, preprocessor.name, 'pre', distDir);
645
+ compressedCode = await bundleAndCompressProcessorCode(executeFunction, preprocessor.name, 'pre', distDir, sourceFilePath);
643
646
  }
644
647
  return {
645
648
  name: preprocessor.name,
@@ -663,6 +666,7 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
663
666
  writeProgress(`📦 Bundling postprocessor ${postprocessor.name}...`);
664
667
  try {
665
668
  let executeFunction = '';
669
+ let sourceFilePath = indexFile.getFilePath();
666
670
  const searchFileForPostProcessor = (file) => {
667
671
  file.forEachDescendant((node) => {
668
672
  if (Node.isNewExpression(node)) {
@@ -690,6 +694,8 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
690
694
  });
691
695
  if (isMatching && foundExecute) {
692
696
  executeFunction = foundExecute;
697
+ // Update source file path to the file where postprocessor was found
698
+ sourceFilePath = file.getFilePath();
693
699
  }
694
700
  }
695
701
  }
@@ -721,7 +727,7 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
721
727
  }
722
728
  let compressedCode = '';
723
729
  if (executeFunction) {
724
- compressedCode = await bundleAndCompressProcessorCode(executeFunction, postprocessor.name, 'post', distDir);
730
+ compressedCode = await bundleAndCompressProcessorCode(executeFunction, postprocessor.name, 'post', distDir, sourceFilePath);
725
731
  }
726
732
  return {
727
733
  name: postprocessor.name,
@@ -740,65 +746,18 @@ export async function bundlePostProcessor(postprocessor, indexFile, distDir, pro
740
746
  }
741
747
  /**
742
748
  * Bundles and compresses processor execute function code.
749
+ * Includes all imports from the source file to ensure dependencies are bundled.
743
750
  */
744
- async function bundleAndCompressProcessorCode(executeFunction, processorName, type, distDir) {
745
- const { compressCode } = await import('./compile.js');
746
- const tempDir = path.join(distDir, '.temp');
747
- if (!fs.existsSync(tempDir)) {
748
- fs.mkdirSync(tempDir, { recursive: true });
749
- }
750
- const tempFile = path.join(tempDir, `${processorName}-${type}processor.ts`);
751
- const tempOutput = path.join(tempDir, `${processorName}-${type}processor.js`);
752
- try {
753
- // Processor execute functions receive: (user, message, [response], channel)
754
- const paramList = type === 'pre' ? 'user, message, channel' : 'user, message, response, channel';
755
- const moduleCode = stripLuaCliImports(`
756
- // ${type === 'pre' ? 'Pre' : 'Post'}Processor execute function for ${processorName}
757
- export default ${executeFunction};
758
- `);
759
- fs.writeFileSync(tempFile, moduleCode);
760
- await build({
761
- entryPoints: [tempFile],
762
- bundle: true,
763
- platform: 'node',
764
- target: 'node16',
765
- format: 'cjs',
766
- minify: true,
767
- outfile: tempOutput,
768
- external: [],
769
- plugins: [sandboxGlobalsPlugin],
770
- });
771
- let bundledCode = fs.readFileSync(tempOutput, 'utf8');
772
- const wrappedCode = `(async (${paramList}) => {
751
+ async function bundleAndCompressProcessorCode(executeFunction, processorName, type, distDir, sourceFilePath) {
752
+ // Processor execute functions receive: (user, message, [response], channel)
753
+ const paramList = type === 'pre' ? 'user, message, channel' : 'user, message, response, channel';
754
+ return bundleAndCompressExecuteFunction(executeFunction, processorName, `${type}processor`, (bundledCode) => `(async (${paramList}) => {
773
755
  // Execute the bundled processor code
774
756
  ${bundledCode}
775
757
 
776
758
  const executeFunction = module.exports.default || module.exports;
777
759
  return await executeFunction(${paramList});
778
- })`;
779
- const compressed = compressCode(wrappedCode);
780
- try {
781
- fs.unlinkSync(tempFile);
782
- fs.unlinkSync(tempOutput);
783
- }
784
- catch (cleanupError) {
785
- // Ignore
786
- }
787
- return compressed;
788
- }
789
- catch (error) {
790
- console.warn(`Warning: Could not bundle ${type}processor ${processorName} code:`, error);
791
- try {
792
- if (fs.existsSync(tempFile))
793
- fs.unlinkSync(tempFile);
794
- if (fs.existsSync(tempOutput))
795
- fs.unlinkSync(tempOutput);
796
- }
797
- catch (cleanupError) {
798
- // Ignore
799
- }
800
- return '';
801
- }
760
+ })`, distDir, sourceFilePath);
802
761
  }
803
762
  /**
804
763
  * Extracts execute code and input schema from a tool.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lua-cli",
3
- "version": "3.0.2-alpha.1",
3
+ "version": "3.0.2-alpha.2",
4
4
  "description": "Build, test, and deploy AI agents with custom tools, webhooks, and scheduled jobs. Features LuaAgent unified configuration, streaming chat, and batch deployment.",
5
5
  "readmeFilename": "README.md",
6
6
  "main": "dist/api-exports.js",
@@ -89,6 +89,7 @@
89
89
  "@types/inquirer": "^9.0.9",
90
90
  "@types/jest": "^29.5.8",
91
91
  "@types/js-yaml": "^4.0.9",
92
+ "@types/lodash": "^4.17.20",
92
93
  "@types/node": "^24.5.1",
93
94
  "@types/node-fetch": "^2.6.13",
94
95
  "@types/react": "^18.2.0",
@@ -96,7 +97,10 @@
96
97
  "@types/ws": "^8.18.1",
97
98
  "@typescript-eslint/parser": "^8.44.1",
98
99
  "@typescript-eslint/typescript-estree": "^8.44.1",
100
+ "date-fns": "^4.1.0",
99
101
  "jest": "^29.7.0",
102
+ "lodash": "^4.17.21",
103
+ "stripe": "^19.2.0",
100
104
  "ts-jest": "^29.1.1",
101
105
  "ts-node": "^10.9.2",
102
106
  "typescript": "^5.9.2"
@@ -20,7 +20,7 @@
20
20
  "inquirer": "^12.9.6",
21
21
  "stripe": "^17.5.0",
22
22
  "js-yaml": "^4.1.0",
23
- "lua-cli": "^3.0.2-alpha.1",
23
+ "lua-cli": "file:..",
24
24
  "openai": "^5.23.0",
25
25
  "uuid": "^13.0.0",
26
26
  "zod": "^3.24.1"
@@ -1,17 +0,0 @@
1
- /**
2
- * Dynamic Job Bundler
3
- * Handles detection and bundling of Jobs.create() calls within execute functions
4
- */
5
- /**
6
- * Detects if code contains Jobs.create() calls
7
- */
8
- export declare function containsJobsCreate(code: string): boolean;
9
- /**
10
- * Extracts Jobs.create() execute functions and bundles them with dependencies.
11
- * Finds execute functions in the code and creates standalone bundles.
12
- */
13
- export declare function bundleNestedJobs(code: string, parentName: string, distDir: string): Promise<string>;
14
- /**
15
- * Cleans up temporary bundling directory
16
- */
17
- export declare function cleanupTempDir(distDir: string): void;
@@ -1,143 +0,0 @@
1
- /**
2
- * Dynamic Job Bundler
3
- * Handles detection and bundling of Jobs.create() calls within execute functions
4
- */
5
- import { build } from 'esbuild';
6
- import fs from 'fs';
7
- import path from 'path';
8
- import { compressCode } from './compile.js';
9
- import { sandboxGlobalsPlugin } from './bundling.js';
10
- /**
11
- * Detects if code contains Jobs.create() calls
12
- */
13
- export function containsJobsCreate(code) {
14
- const found = /Jobs\.create\s*\(/i.test(code);
15
- if (found) {
16
- console.log('[DynamicJobs] ✅ Detected Jobs.create() in code');
17
- }
18
- return found;
19
- }
20
- /**
21
- * Extracts Jobs.create() execute functions and bundles them with dependencies.
22
- * Finds execute functions in the code and creates standalone bundles.
23
- */
24
- export async function bundleNestedJobs(code, parentName, distDir) {
25
- if (!containsJobsCreate(code)) {
26
- return code;
27
- }
28
- console.log(`[DynamicJobs] ⚠️ Jobs.create() with external dependencies not yet fully supported`);
29
- console.log(`[DynamicJobs] 💡 Recommendation: Use fetch() or lua-cli APIs in job execute functions`);
30
- console.log(`[DynamicJobs] 📋 Full bundling support planned for v3.1.0`);
31
- // Return original code as-is
32
- // The job will have access to lua-cli globals but external packages won't be bundled
33
- return code;
34
- }
35
- /**
36
- * Helper to extract job execute function from code
37
- */
38
- function extractJobExecuteFromCode(code) {
39
- // This extracts the execute function body
40
- // For now, return a placeholder that indicates the limitation
41
- return `
42
- // Note: This job execute function runs with limited context
43
- // Use fetch() for external APIs or lua-cli globals (User, Data, Products)
44
- console.log('Job execute running with user:', user);
45
- return { executed: true };
46
- `;
47
- }
48
- /**
49
- * Bundles a job's execute function independently
50
- */
51
- async function bundleJobExecuteFunction(executeFunction, jobName, distDir) {
52
- const tempDir = path.join(distDir, '.temp');
53
- if (!fs.existsSync(tempDir)) {
54
- fs.mkdirSync(tempDir, { recursive: true });
55
- }
56
- const tempFile = path.join(tempDir, `${jobName}.js`);
57
- const tempOutput = path.join(tempDir, `${jobName}-bundled.js`);
58
- try {
59
- // Extract imports that the execute function might need
60
- // Common ones: Stripe, axios, etc.
61
- const moduleCode = `
62
- // Job execute function for ${jobName}
63
- // Import dependencies that might be used
64
- import Stripe from 'stripe';
65
- import { env, User, Data, Products, Baskets, Orders } from 'lua-cli';
66
-
67
- // The execute function with all dependencies available
68
- const executeFunc = ${executeFunction};
69
-
70
- // Export as default for esbuild to bundle
71
- export default executeFunc;
72
- `;
73
- fs.writeFileSync(tempFile, moduleCode);
74
- // Bundle with esbuild - bundle ALL dependencies (including Stripe, axios, etc.)
75
- await build({
76
- entryPoints: [tempFile],
77
- bundle: true,
78
- platform: 'node',
79
- target: 'node16',
80
- format: 'cjs',
81
- minify: true,
82
- outfile: tempOutput,
83
- external: [], // Bundle everything except lua-cli APIs (handled by plugin)
84
- plugins: [sandboxGlobalsPlugin], // This handles lua-cli globals
85
- // Important: Don't externalize packages like stripe, axios, etc.
86
- // They need to be included in the bundle
87
- });
88
- // Read bundled code (includes all dependencies like Stripe, axios, etc.)
89
- let bundledCode = fs.readFileSync(tempOutput, 'utf8');
90
- // Log bundle size for debugging
91
- console.log(`[DynamicJobs] Bundled size for ${jobName}: ${(bundledCode.length / 1024).toFixed(2)}KB`);
92
- // Wrap for VM execution - the bundled code includes ALL dependencies
93
- const wrappedCode = `(async (job) => {
94
-
95
- ${bundledCode}
96
-
97
- // Get the execute function from exports
98
- const executeFunction = module.exports || module.exports.default;
99
-
100
- // Execute with job
101
- return await executeFunction(job);
102
- })`;
103
- // Compress the wrapped code
104
- const compressed = compressCode(wrappedCode);
105
- // Clean up temp files
106
- try {
107
- fs.unlinkSync(tempFile);
108
- fs.unlinkSync(tempOutput);
109
- }
110
- catch (cleanupError) {
111
- // Ignore cleanup errors
112
- }
113
- return compressed;
114
- }
115
- catch (error) {
116
- console.warn(`Warning: Could not bundle job execute function ${jobName}:`, error);
117
- // Clean up on error
118
- try {
119
- if (fs.existsSync(tempFile))
120
- fs.unlinkSync(tempFile);
121
- if (fs.existsSync(tempOutput))
122
- fs.unlinkSync(tempOutput);
123
- }
124
- catch (cleanupError) {
125
- // Ignore cleanup errors
126
- }
127
- return '';
128
- }
129
- }
130
- /**
131
- * Cleans up temporary bundling directory
132
- */
133
- export function cleanupTempDir(distDir) {
134
- const tempDir = path.join(distDir, '.temp');
135
- if (fs.existsSync(tempDir)) {
136
- try {
137
- fs.rmSync(tempDir, { recursive: true, force: true });
138
- }
139
- catch (error) {
140
- // Ignore cleanup errors
141
- }
142
- }
143
- }