@actuallyjamez/elysian 0.11.0 → 0.12.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 (50) hide show
  1. package/dist/cli/commands/build.js +172 -87
  2. package/dist/cli/commands/dev.d.ts +2 -0
  3. package/dist/cli/commands/dev.js +491 -178
  4. package/dist/cli/commands/generate-iac.js +23 -23
  5. package/dist/cli/commands/init/detect.d.ts +3 -3
  6. package/dist/cli/commands/init/detect.js +4 -4
  7. package/dist/cli/commands/init/prompts.js +2 -1
  8. package/dist/cli/commands/init/scaffold.js +55 -93
  9. package/dist/cli/commands/init/templates.d.ts +9 -1
  10. package/dist/cli/commands/init/templates.js +28 -4
  11. package/dist/cli/commands/init/terraform-module.d.ts +76 -0
  12. package/dist/cli/commands/init/terraform-module.js +1353 -0
  13. package/dist/cli/commands/init.js +16 -16
  14. package/dist/cli/logger.d.ts +346 -0
  15. package/dist/cli/logger.js +968 -0
  16. package/dist/core/appsync-client.d.ts +5 -0
  17. package/dist/core/appsync-client.js +9 -2
  18. package/dist/core/bundler.d.ts +20 -3
  19. package/dist/core/bundler.js +141 -67
  20. package/dist/core/config.d.ts +51 -11
  21. package/dist/core/config.js +59 -17
  22. package/dist/core/discovery.d.ts +96 -0
  23. package/dist/core/discovery.js +229 -0
  24. package/dist/core/handler-wrapper.d.ts +22 -10
  25. package/dist/core/handler-wrapper.js +44 -45
  26. package/dist/core/localstack.d.ts +36 -0
  27. package/dist/core/localstack.js +180 -1
  28. package/dist/core/manifest.d.ts +51 -2
  29. package/dist/core/manifest.js +175 -20
  30. package/dist/core/openapi.d.ts +8 -3
  31. package/dist/core/openapi.js +24 -14
  32. package/dist/core/terraform.d.ts +34 -3
  33. package/dist/core/terraform.js +115 -6
  34. package/dist/core/worker-runner.d.ts +2 -1
  35. package/dist/core/worker-runner.js +132 -15
  36. package/dist/index.d.ts +2 -29
  37. package/dist/index.js +3 -47
  38. package/dist/runtime/define-lambda.d.ts +246 -0
  39. package/dist/runtime/define-lambda.js +140 -0
  40. package/dist/runtime/define-routes.d.ts +48 -0
  41. package/dist/runtime/define-routes.js +67 -0
  42. package/dist/runtime/stub.d.ts +1 -0
  43. package/dist/runtime/stub.js +44 -26
  44. package/package.json +6 -2
  45. package/dist/cli/commands/init/terraform-live.d.ts +0 -27
  46. package/dist/cli/commands/init/terraform-live.js +0 -140
  47. package/dist/cli/commands/init/terraform.d.ts +0 -44
  48. package/dist/cli/commands/init/terraform.js +0 -333
  49. package/dist/cli/ui.d.ts +0 -114
  50. package/dist/cli/ui.js +0 -233
@@ -2,17 +2,18 @@
2
2
  * Build command - Production build of all lambdas
3
3
  */
4
4
  import { defineCommand } from "citty";
5
- import { readdirSync, mkdirSync, existsSync, rmSync } from "fs";
5
+ import { mkdirSync, rmSync } from "fs";
6
6
  import { join } from "path";
7
7
  import { loadConfig } from "../../core/config";
8
- import { bundleLambda } from "../../core/bundler";
8
+ import { bundleApiRoute, bundleGenericLambda, } from "../../core/bundler";
9
9
  import { packageLambda } from "../../core/packager";
10
10
  import { generateManifest, writeManifest } from "../../core/manifest";
11
11
  import { writeTerraformVars } from "../../core/terraform";
12
12
  import { shouldGenerateOpenApi, writeOpenApiLambda, } from "../../core/openapi";
13
- import { createWrapperEntry } from "../../core/handler-wrapper";
13
+ import { createWrapperEntry, createGenericLambdaWrapper } from "../../core/handler-wrapper";
14
14
  import { getLambdaBundleName } from "../../core/naming";
15
- import { ui, pc, formatDuration, formatSize } from "../ui";
15
+ import { discoverLambdas, validateExport, } from "../../core/discovery";
16
+ import { logger, printHeader, printSection, printDivider, printBlank, printLambda, printRoute, formatDuration, formatSize, } from "../logger";
16
17
  export const buildCommand = defineCommand({
17
18
  meta: {
18
19
  name: "build",
@@ -32,127 +33,211 @@ export const buildCommand = defineCommand({
32
33
  process.env.NODE_ENV = "production";
33
34
  }
34
35
  // Header
35
- ui.header(args.prod ? pc.yellow("production") : "development");
36
+ printHeader(args.prod ? "\x1b[33mproduction\x1b[0m" : "development");
36
37
  // Load config
37
38
  let config;
38
39
  try {
39
40
  config = await loadConfig();
40
41
  }
41
42
  catch (error) {
42
- ui.error(error instanceof Error ? error.message : String(error));
43
+ logger.error(error instanceof Error ? error.message : String(error));
43
44
  process.exit(1);
44
45
  }
46
+ const cwd = process.cwd();
45
47
  const name = config.name;
46
- const lambdasDir = join(process.cwd(), config.lambdasDir);
47
- const outputDir = join(process.cwd(), config.outputDir);
48
- const terraformDir = join(process.cwd(), config.terraform.outputDir);
49
- // Ensure directories exist
50
- if (!existsSync(lambdasDir)) {
51
- ui.error(`Lambdas directory not found: ${lambdasDir}`);
52
- process.exit(1);
53
- }
48
+ const outputDir = join(cwd, config.outputDir);
49
+ const terraformDir = join(cwd, config.terraform.outputDir);
50
+ // Ensure output directories exist
54
51
  mkdirSync(outputDir, { recursive: true });
55
52
  mkdirSync(terraformDir, { recursive: true });
53
+ // Discover lambdas in both directories
54
+ const discovered = await discoverLambdas(cwd, config, (filename, reason) => {
55
+ logger.warn(`Skipping ${filename}: ${reason}`);
56
+ });
57
+ // Check if we have anything to build
58
+ if (discovered.apiRoutes.length === 0 && discovered.functions.length === 0) {
59
+ logger.warn(`No lambda files found in ${config.api.dir} or ${config.functions.dir}`);
60
+ return;
61
+ }
56
62
  // Create temp directory for generated files
57
63
  const tempDir = join(outputDir, "__temp__");
58
64
  mkdirSync(tempDir, { recursive: true });
59
- // Get lambda files
60
- let lambdaFiles = readdirSync(lambdasDir).filter((f) => f.endsWith(".ts") && !f.startsWith("__"));
61
- if (lambdaFiles.length === 0) {
62
- ui.warn(`No lambda files found in ${config.lambdasDir}`);
63
- return;
64
- }
65
- // Generate OpenAPI aggregator if enabled
66
- if (shouldGenerateOpenApi(config)) {
67
- await writeOpenApiLambda(lambdaFiles, lambdasDir, config, tempDir);
68
- lambdaFiles.push("openapi.ts");
65
+ // Track all build results
66
+ const allBuildResults = [];
67
+ const packageSizes = new Map();
68
+ // =====================
69
+ // Build API Routes
70
+ // =====================
71
+ if (discovered.apiRoutes.length > 0) {
72
+ printSection("API Routes");
73
+ logger.success(`Compiling ${discovered.apiRoutes.length} API routes from ${config.api.dir}...`);
74
+ // Generate OpenAPI aggregator if enabled
75
+ let apiRoutesWithOpenApi = [...discovered.apiRoutes];
76
+ if (shouldGenerateOpenApi(config)) {
77
+ const openApiPath = await writeOpenApiLambda(discovered.apiRoutes, config, tempDir);
78
+ const openApiRoute = {
79
+ name: "openapi",
80
+ sourcePath: openApiPath,
81
+ bundleName: getLambdaBundleName(name, "openapi"),
82
+ };
83
+ apiRoutesWithOpenApi.push(openApiRoute);
84
+ }
85
+ for (const route of apiRoutesWithOpenApi) {
86
+ // Create wrapper entry that imports the original and exports handler
87
+ const wrapperPath = join(tempDir, `${route.name}-wrapper.ts`);
88
+ const wrapperContent = createWrapperEntry(route.sourcePath);
89
+ await Bun.write(wrapperPath, wrapperContent);
90
+ // Bundle the wrapper
91
+ const result = await bundleApiRoute({ ...route, sourcePath: wrapperPath }, outputDir, config);
92
+ allBuildResults.push(result);
93
+ if (!result.success) {
94
+ logger.error(`Failed to build ${route.name}: ${result.error}`);
95
+ process.exit(1);
96
+ }
97
+ }
98
+ // Validate exports (skip openapi which is auto-generated)
99
+ for (const route of discovered.apiRoutes) {
100
+ const bundlePath = join(outputDir, `${route.bundleName}.js`);
101
+ const validationError = await validateExport(bundlePath, "routes", route.sourcePath);
102
+ if (validationError) {
103
+ logger.error(validationError.message);
104
+ process.exit(1);
105
+ }
106
+ }
69
107
  }
70
- // Build phase
71
- ui.success(`Compiling ${lambdaFiles.length} lambdas...`);
72
- const buildResults = [];
73
- for (const file of lambdaFiles) {
74
- const lambdaName = file.replace(/\.ts$/, "");
75
- const bundleName = getLambdaBundleName(name, lambdaName);
76
- // For OpenAPI, the source is in tempDir; for regular lambdas, it's in lambdasDir
77
- const inputPath = file === "openapi.ts"
78
- ? join(tempDir, file)
79
- : join(lambdasDir, file);
80
- // Create wrapper entry that imports the original and exports handler
81
- const wrapperPath = join(tempDir, `${lambdaName}-wrapper.ts`);
82
- const wrapperContent = createWrapperEntry(inputPath);
83
- await Bun.write(wrapperPath, wrapperContent);
84
- // Bundle the wrapper with prefixed name
85
- const result = await bundleLambda(bundleName, wrapperPath, outputDir, config);
86
- buildResults.push({ ...result, name: lambdaName, bundleName });
87
- if (!result.success) {
88
- ui.error(`Failed to build ${lambdaName}: ${result.error}`);
89
- process.exit(1);
108
+ // =====================
109
+ // Build Generic Functions
110
+ // =====================
111
+ if (discovered.functions.length > 0) {
112
+ printSection("Generic Functions");
113
+ logger.success(`Compiling ${discovered.functions.length} generic functions from ${config.functions.dir}...`);
114
+ for (const fn of discovered.functions) {
115
+ // Create wrapper that extracts handler from defineLambda export
116
+ const wrapperPath = join(tempDir, `${fn.name}-lambda-wrapper.ts`);
117
+ const wrapperContent = createGenericLambdaWrapper(fn.sourcePath);
118
+ await Bun.write(wrapperPath, wrapperContent);
119
+ // Bundle the wrapper
120
+ const result = await bundleGenericLambda({ ...fn, sourcePath: wrapperPath }, outputDir, config);
121
+ allBuildResults.push(result);
122
+ if (!result.success) {
123
+ logger.error(`Failed to build ${fn.name}: ${result.error}`);
124
+ process.exit(1);
125
+ }
126
+ }
127
+ // Validate exports
128
+ for (const fn of discovered.functions) {
129
+ const bundlePath = join(outputDir, `${fn.bundleName}.js`);
130
+ const validationError = await validateExport(bundlePath, "lambda", fn.sourcePath);
131
+ if (validationError) {
132
+ logger.error(validationError.message);
133
+ process.exit(1);
134
+ }
90
135
  }
91
136
  }
92
137
  // Clean up temp directory
93
138
  rmSync(tempDir, { recursive: true, force: true });
94
- // No need to clean up OpenAPI file separately - it's in tempDir
95
- // Package phase
96
- ui.success("Packaging lambdas...");
97
- const packageSizes = new Map();
98
- for (const file of lambdaFiles) {
99
- const lambdaName = file.replace(/\.ts$/, "");
100
- const bundleName = getLambdaBundleName(name, lambdaName);
101
- const jsPath = join(outputDir, `${bundleName}.js`);
102
- const result = await packageLambda(bundleName, jsPath, outputDir);
103
- if (!result.success) {
104
- ui.error(`Failed to package ${lambdaName}: ${result.error}`);
139
+ // =====================
140
+ // Package Phase
141
+ // =====================
142
+ printSection("Packaging");
143
+ logger.success("Packaging lambdas...");
144
+ for (const result of allBuildResults) {
145
+ if (!result.success)
146
+ continue;
147
+ const jsPath = join(outputDir, `${result.bundleName}.js`);
148
+ const packageResult = await packageLambda(result.bundleName, jsPath, outputDir);
149
+ if (!packageResult.success) {
150
+ logger.error(`Failed to package ${result.name}: ${packageResult.error}`);
105
151
  process.exit(1);
106
152
  }
107
- // Get zip size (store by original name for display)
108
- const zipPath = join(outputDir, `${bundleName}.zip`);
153
+ // Get zip size
154
+ const zipPath = join(outputDir, `${result.bundleName}.zip`);
109
155
  const stat = await Bun.file(zipPath).stat();
110
156
  if (stat) {
111
- packageSizes.set(lambdaName, stat.size);
157
+ packageSizes.set(result.name, stat.size);
112
158
  }
113
159
  }
114
- // Generate manifest
115
- ui.success("Generating manifest...");
160
+ // =====================
161
+ // Generate Manifest
162
+ // =====================
163
+ printSection("Manifest");
164
+ logger.success("Generating manifest...");
116
165
  try {
117
- const manifest = await generateManifest(lambdaFiles, outputDir, config.openapi.enabled, name);
166
+ const manifest = await generateManifest(discovered.apiRoutes, discovered.functions, outputDir, config.api.openapi.enabled, name);
118
167
  // Write JSON manifest
119
168
  const manifestPath = join(outputDir, "manifest.json");
120
169
  await writeManifest(manifest, manifestPath);
121
- // Write Terraform variables
170
+ // Write Terraform variables (both files)
122
171
  await writeTerraformVars(manifest, config);
123
172
  // Duration
124
173
  const duration = Date.now() - startTime;
125
- // Route table
126
- ui.section("Routes");
127
- // Group routes by lambda (use original name for display)
128
- const routesByLambda = new Map();
129
- for (const route of manifest.routes) {
130
- // Extract display name (original name) from bundle name
131
- const displayName = route.lambda.startsWith(`${name}-`)
132
- ? route.lambda.slice(name.length + 1)
133
- : route.lambda;
134
- const existing = routesByLambda.get(displayName) || [];
135
- existing.push(route);
136
- routesByLambda.set(displayName, existing);
174
+ // =====================
175
+ // Output Summary
176
+ // =====================
177
+ // API Routes table
178
+ if (manifest.routes.length > 0) {
179
+ printSection("API Routes");
180
+ // Group routes by lambda
181
+ const routesByLambda = new Map();
182
+ for (const route of manifest.routes) {
183
+ const displayName = route.lambda.startsWith(`${name}-`)
184
+ ? route.lambda.slice(name.length + 1)
185
+ : route.lambda;
186
+ const existing = routesByLambda.get(displayName) || [];
187
+ existing.push(route);
188
+ routesByLambda.set(displayName, existing);
189
+ }
190
+ const maxPathLen = Math.max(...manifest.routes.map((r) => r.path.length));
191
+ for (const [displayName, routes] of routesByLambda) {
192
+ const size = packageSizes.get(displayName);
193
+ const sizeStr = size ? formatSize(size) : undefined;
194
+ printLambda(displayName, sizeStr);
195
+ for (const route of routes) {
196
+ printRoute(route.method, route.path, route.pathParameters, maxPathLen);
197
+ }
198
+ printBlank();
199
+ }
137
200
  }
138
- // Find longest path for alignment
139
- const maxPathLen = Math.max(...manifest.routes.map((r) => r.path.length));
140
- for (const [displayName, routes] of routesByLambda) {
141
- const size = packageSizes.get(displayName);
142
- const sizeStr = size ? formatSize(size) : undefined;
143
- ui.labeled(displayName, sizeStr);
144
- for (const route of routes) {
145
- ui.route(route.method, route.path, route.pathParameters, maxPathLen);
201
+ // Generic Lambdas table
202
+ if (manifest.genericLambdas.length > 0) {
203
+ printSection("Functions");
204
+ for (const lambda of manifest.genericLambdas) {
205
+ const size = packageSizes.get(lambda.name);
206
+ const sizeStr = size ? formatSize(size) : undefined;
207
+ let triggerStr;
208
+ if (lambda.trigger?.type) {
209
+ const triggerType = lambda.trigger.type;
210
+ // Show schedule duration if available
211
+ if (triggerType === "schedule" && lambda.trigger.config && "every" in lambda.trigger.config) {
212
+ triggerStr = `\x1b[90m[${triggerType}: ${lambda.trigger.config.every}]\x1b[0m`;
213
+ }
214
+ else {
215
+ triggerStr = `\x1b[90m[${triggerType}]\x1b[0m`;
216
+ }
217
+ }
218
+ else {
219
+ triggerStr = "\x1b[90m[manual]\x1b[0m";
220
+ }
221
+ printLambda(`${lambda.name} ${triggerStr}`, sizeStr);
146
222
  }
147
- ui.blank();
223
+ printBlank();
148
224
  }
149
225
  // Summary footer
150
- ui.divider();
151
- ui.success(`Compiled ${pc.bold(String(manifest.lambdas.length))} lambdas (${manifest.routes.length} routes) in ${pc.bold(formatDuration(duration))}`);
152
- ui.blank();
226
+ printDivider();
227
+ const totalLambdas = manifest.lambdas.length + manifest.genericLambdas.length;
228
+ const routeCount = manifest.routes.length;
229
+ const parts = [];
230
+ if (manifest.lambdas.length > 0) {
231
+ parts.push(`${manifest.lambdas.length} API routes`);
232
+ }
233
+ if (manifest.genericLambdas.length > 0) {
234
+ parts.push(`${manifest.genericLambdas.length} functions`);
235
+ }
236
+ logger.success(`Compiled \x1b[1m${totalLambdas}\x1b[0m lambdas (${parts.join(", ")}) in \x1b[1m${formatDuration(duration)}\x1b[0m`);
237
+ printBlank();
153
238
  }
154
239
  catch (error) {
155
- ui.error(error instanceof Error ? error.message : String(error));
240
+ logger.error(error instanceof Error ? error.message : String(error));
156
241
  process.exit(1);
157
242
  }
158
243
  },
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * Dev command - Live mode using AppSync Events bridge
3
+ *
4
+ * Supports both API routes (src/api/) and generic functions (src/functions/).
3
5
  */
4
6
  export declare const devCommand: import("citty").CommandDef<{
5
7
  "no-localstack": {