@actuallyjamez/elysian 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # elysian
2
+
3
+ Automatic Lambda bundler for [Elysia](https://elysiajs.com/) with AWS API Gateway and Terraform integration.
4
+
5
+ ## Features
6
+
7
+ - **Zero-config Lambda handlers** - Just export your Elysia routes as default, handlers are auto-generated
8
+ - **Automatic OpenAPI aggregation** - All routes are aggregated into a single OpenAPI spec endpoint
9
+ - **Terraform integration** - Generates `tfvars` files for seamless infrastructure deployment
10
+ - **Type-safe configuration** - Full TypeScript support with `defineConfig()`
11
+ - **Watch mode** - Fast rebuilds during development
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ # Configure GitHub registry for @actuallyjamez scope
17
+ echo "@actuallyjamez:registry=https://npm.pkg.github.com" >> .npmrc
18
+
19
+ # Install
20
+ bun add elysia @actuallyjamez/elysian
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Initialize your project
26
+
27
+ ```bash
28
+ bunx @actuallyjamez/elysian init --name my-api
29
+ ```
30
+
31
+ This creates:
32
+ - `elysian.config.ts` - Configuration file
33
+ - `src/lambdas/hello.ts` - Example lambda
34
+ - `terraform/main.tf` - Terraform infrastructure
35
+
36
+ ### 2. Write your lambdas
37
+
38
+ ```typescript
39
+ // src/lambdas/users.ts
40
+ import { createLambda, t } from "@actuallyjamez/elysian";
41
+
42
+ export default createLambda()
43
+ .get("/users", () => db.getUsers(), {
44
+ response: t.Array(t.Object({ id: t.String(), name: t.String() })),
45
+ detail: { summary: "List all users", tags: ["Users"] },
46
+ })
47
+ .get("/users/:id", ({ params }) => db.getUser(params.id), {
48
+ params: t.Object({ id: t.String() }),
49
+ detail: { summary: "Get user by ID", tags: ["Users"] },
50
+ })
51
+ .post("/users", ({ body }) => db.createUser(body), {
52
+ body: t.Object({ name: t.String(), email: t.String() }),
53
+ detail: { summary: "Create user", tags: ["Users"] },
54
+ });
55
+ ```
56
+
57
+ **That's it!** No need to export a handler - the bundler wraps your default export automatically.
58
+
59
+ ### 3. Build
60
+
61
+ ```bash
62
+ # Development build
63
+ bunx elysian build
64
+
65
+ # Production build (minified)
66
+ bunx elysian build --prod
67
+ ```
68
+
69
+ ### 4. Deploy
70
+
71
+ ```bash
72
+ cd terraform
73
+ terraform init
74
+ terraform apply
75
+ ```
76
+
77
+ ## Configuration
78
+
79
+ Create `elysian.config.ts` in your project root:
80
+
81
+ ```typescript
82
+ import { defineConfig } from "@actuallyjamez/elysian";
83
+
84
+ export default defineConfig({
85
+ // Required
86
+ apiName: "my-api",
87
+
88
+ // Optional (showing defaults)
89
+ lambdasDir: "src/lambdas",
90
+ outputDir: "dist",
91
+
92
+ // OpenAPI configuration
93
+ openapi: {
94
+ enabled: true,
95
+ title: "My API",
96
+ version: "1.0.0",
97
+ description: "API description",
98
+ },
99
+
100
+ // Terraform output configuration
101
+ terraform: {
102
+ outputDir: "terraform",
103
+ tfvarsFilename: "api-routes.auto.tfvars", // Won't overwrite your existing tfvars
104
+ },
105
+
106
+ // Lambda defaults (used in generated tfvars)
107
+ lambda: {
108
+ runtime: "nodejs20.x",
109
+ memorySize: 256,
110
+ timeout: 30,
111
+ },
112
+ });
113
+ ```
114
+
115
+ ## CLI Commands
116
+
117
+ ### `elysian build`
118
+
119
+ Build all lambdas for deployment.
120
+
121
+ ```bash
122
+ bunx elysian build # Development build
123
+ bunx elysian build --prod # Production build (minified)
124
+ ```
125
+
126
+ **Output:**
127
+ - `dist/*.js` - Bundled lambda code
128
+ - `dist/*.zip` - Lambda deployment packages
129
+ - `dist/manifest.json` - Route manifest (for debugging)
130
+ - `terraform/api-routes.auto.tfvars` - Terraform variables
131
+
132
+ ### `elysian dev`
133
+
134
+ Watch mode for development - rebuilds on file changes.
135
+
136
+ ```bash
137
+ bunx elysian dev # Watch with packaging
138
+ bunx elysian dev --no-package # Skip zip creation (faster)
139
+ ```
140
+
141
+ ### `elysian init`
142
+
143
+ Initialize a new project with example files.
144
+
145
+ ```bash
146
+ bunx elysian init --name my-api
147
+ bunx elysian init --force # Overwrite existing files
148
+ ```
149
+
150
+ ### `elysian generate-iac`
151
+
152
+ Regenerate Terraform files without rebuilding lambdas.
153
+
154
+ ```bash
155
+ bunx elysian generate-iac
156
+ ```
157
+
158
+ ## How It Works
159
+
160
+ ### 1. Route Discovery
161
+
162
+ The bundler scans your `lambdasDir` for `.ts` files. Each file becomes a separate Lambda function.
163
+
164
+ ### 2. Handler Injection
165
+
166
+ When you export an Elysia app as default:
167
+
168
+ ```typescript
169
+ export default createLambda().get("/hello", () => "Hello!");
170
+ ```
171
+
172
+ The bundler automatically wraps it with a Lambda handler:
173
+
174
+ ```typescript
175
+ import { Hono } from "hono/tiny";
176
+ import { handle } from "hono/aws-lambda";
177
+
178
+ const app = /* your exported app */;
179
+ export const handler = handle(new Hono().mount("/", app.fetch));
180
+ ```
181
+
182
+ ### 3. OpenAPI Aggregation
183
+
184
+ An `__openapi__` lambda is automatically generated that imports all your routes and exposes:
185
+ - `GET /openapi` - Swagger UI
186
+ - `GET /openapi/json` - OpenAPI JSON spec
187
+
188
+ ### 4. Terraform Integration
189
+
190
+ The generated `tfvars` file contains:
191
+ - List of Lambda names
192
+ - Route-to-Lambda mappings (with API Gateway path format)
193
+ - Lambda configuration defaults
194
+
195
+ ## Project Structure
196
+
197
+ ```
198
+ my-api/
199
+ ├── elysian.config.ts # Configuration
200
+ ├── src/
201
+ │ └── lambdas/
202
+ │ ├── users.ts # → users.zip Lambda
203
+ │ ├── posts.ts # → posts.zip Lambda
204
+ │ └── auth.ts # → auth.zip Lambda
205
+ ├── dist/ # Build output
206
+ │ ├── users.js
207
+ │ ├── users.zip
208
+ │ ├── posts.js
209
+ │ ├── posts.zip
210
+ │ ├── __openapi__.js # Auto-generated
211
+ │ ├── __openapi__.zip
212
+ │ └── manifest.json
213
+ └── terraform/
214
+ ├── main.tf
215
+ └── api-routes.auto.tfvars # Auto-generated
216
+ ```
217
+
218
+ ## Requirements
219
+
220
+ - [Bun](https://bun.sh/) runtime
221
+ - [Elysia](https://elysiajs.com/) v1.0+
222
+
223
+ ## License
224
+
225
+ MIT
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Build command - Production build of all lambdas
3
+ */
4
+ export declare const buildCommand: import("citty").CommandDef<{
5
+ prod: {
6
+ type: "boolean";
7
+ description: string;
8
+ default: false;
9
+ };
10
+ }>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Build command - Production build of all lambdas
3
+ */
4
+ import { defineCommand } from "citty";
5
+ import { readdirSync, mkdirSync, existsSync } from "fs";
6
+ import { join } from "path";
7
+ import pc from "picocolors";
8
+ import { loadConfig } from "../../core/config";
9
+ import { bundleLambda } from "../../core/bundler";
10
+ import { packageLambda } from "../../core/packager";
11
+ import { generateManifest, writeManifest } from "../../core/manifest";
12
+ import { writeTerraformVars } from "../../core/terraform";
13
+ import { shouldGenerateOpenApi, writeOpenApiLambda, cleanupOpenApiLambda, } from "../../core/openapi";
14
+ import { createWrapperEntry } from "../../core/handler-wrapper";
15
+ import { getLambdaBundleName } from "../../core/naming";
16
+ import { version } from "../../core/version";
17
+ function formatDuration(ms) {
18
+ if (ms < 1000)
19
+ return `${ms}ms`;
20
+ return `${(ms / 1000).toFixed(2)}s`;
21
+ }
22
+ function formatSize(bytes) {
23
+ if (bytes < 1024)
24
+ return `${bytes} B`;
25
+ if (bytes < 1024 * 1024)
26
+ return `${(bytes / 1024).toFixed(1)} KB`;
27
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
28
+ }
29
+ export const buildCommand = defineCommand({
30
+ meta: {
31
+ name: "build",
32
+ description: "Build all lambdas for production deployment",
33
+ },
34
+ args: {
35
+ prod: {
36
+ type: "boolean",
37
+ description: "Production build (minify, no sourcemaps)",
38
+ default: false,
39
+ },
40
+ },
41
+ async run({ args }) {
42
+ const startTime = Date.now();
43
+ // Set production mode if flag is set
44
+ if (args.prod) {
45
+ process.env.NODE_ENV = "production";
46
+ }
47
+ // Header
48
+ console.log();
49
+ console.log(` ${pc.bold(pc.cyan("elysian"))} ${pc.dim(`v${version}`)} ${args.prod ? pc.yellow("production") : pc.dim("development")}`);
50
+ console.log();
51
+ // Load config
52
+ let config;
53
+ try {
54
+ config = await loadConfig();
55
+ }
56
+ catch (error) {
57
+ console.log(` ${pc.red("✗")} ${error instanceof Error ? error.message : error}`);
58
+ process.exit(1);
59
+ }
60
+ const apiName = config.apiName;
61
+ const lambdasDir = join(process.cwd(), config.lambdasDir);
62
+ const outputDir = join(process.cwd(), config.outputDir);
63
+ const terraformDir = join(process.cwd(), config.terraform.outputDir);
64
+ // Ensure directories exist
65
+ if (!existsSync(lambdasDir)) {
66
+ console.log(` ${pc.red("✗")} Lambdas directory not found: ${lambdasDir}`);
67
+ process.exit(1);
68
+ }
69
+ mkdirSync(outputDir, { recursive: true });
70
+ mkdirSync(terraformDir, { recursive: true });
71
+ // Get lambda files
72
+ let lambdaFiles = readdirSync(lambdasDir).filter((f) => f.endsWith(".ts") && !f.startsWith("__"));
73
+ if (lambdaFiles.length === 0) {
74
+ console.log(` ${pc.yellow("!")} No lambda files found in ${config.lambdasDir}`);
75
+ return;
76
+ }
77
+ // Generate OpenAPI aggregator if enabled
78
+ if (shouldGenerateOpenApi(config)) {
79
+ await writeOpenApiLambda(lambdaFiles, lambdasDir, config);
80
+ lambdaFiles.push("__openapi__.ts");
81
+ }
82
+ // Build phase
83
+ console.log(` ${pc.green("✓")} Compiling ${lambdaFiles.length} lambdas...`);
84
+ const tempDir = join(outputDir, "__temp__");
85
+ mkdirSync(tempDir, { recursive: true });
86
+ const buildResults = [];
87
+ for (const file of lambdaFiles) {
88
+ const name = file.replace(/\.ts$/, "");
89
+ const bundleName = getLambdaBundleName(apiName, name);
90
+ const inputPath = join(lambdasDir, file);
91
+ // Create wrapper entry that imports the original and exports handler
92
+ const wrapperPath = join(tempDir, `${name}-wrapper.ts`);
93
+ const wrapperContent = createWrapperEntry(inputPath);
94
+ await Bun.write(wrapperPath, wrapperContent);
95
+ // Bundle the wrapper with prefixed name
96
+ const result = await bundleLambda(bundleName, wrapperPath, outputDir, config);
97
+ buildResults.push({ ...result, name, bundleName });
98
+ if (!result.success) {
99
+ console.log(` ${pc.red("✗")} Failed to build ${name}: ${result.error}`);
100
+ process.exit(1);
101
+ }
102
+ }
103
+ // Clean up temp directory
104
+ const { rmSync } = await import("fs");
105
+ rmSync(tempDir, { recursive: true, force: true });
106
+ // Clean up generated OpenAPI file
107
+ if (shouldGenerateOpenApi(config)) {
108
+ await cleanupOpenApiLambda(lambdasDir);
109
+ }
110
+ // Package phase
111
+ console.log(` ${pc.green("✓")} Packaging lambdas...`);
112
+ const packageSizes = new Map();
113
+ for (const file of lambdaFiles) {
114
+ const name = file.replace(/\.ts$/, "");
115
+ const bundleName = getLambdaBundleName(apiName, name);
116
+ const jsPath = join(outputDir, `${bundleName}.js`);
117
+ const result = await packageLambda(bundleName, jsPath, outputDir);
118
+ if (!result.success) {
119
+ console.log(` ${pc.red("✗")} Failed to package ${name}: ${result.error}`);
120
+ process.exit(1);
121
+ }
122
+ // Get zip size (store by original name for display)
123
+ const zipPath = join(outputDir, `${bundleName}.zip`);
124
+ const stat = await Bun.file(zipPath).stat();
125
+ if (stat) {
126
+ packageSizes.set(name, stat.size);
127
+ }
128
+ }
129
+ // Generate manifest
130
+ console.log(` ${pc.green("✓")} Generating manifest...`);
131
+ try {
132
+ const manifest = await generateManifest(lambdaFiles, outputDir, config.openapi.enabled, apiName);
133
+ // Write JSON manifest
134
+ const manifestPath = join(outputDir, "manifest.json");
135
+ await writeManifest(manifest, manifestPath);
136
+ // Write Terraform variables
137
+ await writeTerraformVars(manifest, config);
138
+ // Duration
139
+ const duration = Date.now() - startTime;
140
+ // Route table header
141
+ console.log();
142
+ console.log(` ${pc.bold("Routes")}`);
143
+ console.log();
144
+ // Group routes by lambda (use original name for display)
145
+ const routesByLambda = new Map();
146
+ for (const route of manifest.routes) {
147
+ // Extract display name (original name) from bundle name
148
+ const displayName = route.lambda.startsWith(`${apiName}-`)
149
+ ? route.lambda.slice(apiName.length + 1)
150
+ : route.lambda;
151
+ const existing = routesByLambda.get(displayName) || [];
152
+ existing.push(route);
153
+ routesByLambda.set(displayName, existing);
154
+ }
155
+ // Method colors
156
+ const methodColor = (method) => {
157
+ switch (method) {
158
+ case "GET":
159
+ return pc.green;
160
+ case "POST":
161
+ return pc.blue;
162
+ case "PUT":
163
+ return pc.yellow;
164
+ case "DELETE":
165
+ return pc.red;
166
+ case "PATCH":
167
+ return pc.magenta;
168
+ default:
169
+ return pc.white;
170
+ }
171
+ };
172
+ // Find longest path for alignment
173
+ const maxPathLen = Math.max(...manifest.routes.map((r) => r.path.length));
174
+ for (const [displayName, routes] of routesByLambda) {
175
+ const size = packageSizes.get(displayName);
176
+ const sizeStr = size ? pc.dim(` (${formatSize(size)})`) : "";
177
+ console.log(` ${pc.dim("λ")} ${pc.bold(displayName)}${sizeStr}`);
178
+ for (const route of routes) {
179
+ const method = methodColor(route.method)(route.method.padEnd(6));
180
+ const path = route.path.padEnd(maxPathLen + 2);
181
+ const params = route.pathParameters.length > 0
182
+ ? pc.dim(` [${route.pathParameters.join(", ")}]`)
183
+ : "";
184
+ console.log(` ${method} ${path}${params}`);
185
+ }
186
+ console.log();
187
+ }
188
+ // Summary footer
189
+ console.log(pc.dim(" " + "─".repeat(40)));
190
+ console.log();
191
+ console.log(` ${pc.green("✓")} Compiled ${pc.bold(String(manifest.lambdas.length))} lambdas (${manifest.routes.length} routes) in ${pc.bold(formatDuration(duration))}`);
192
+ console.log();
193
+ }
194
+ catch (error) {
195
+ console.log(` ${pc.red("✗")} ${error instanceof Error ? error.message : String(error)}`);
196
+ process.exit(1);
197
+ }
198
+ },
199
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dev command - Watch mode for development
3
+ */
4
+ export declare const devCommand: import("citty").CommandDef<{
5
+ "no-package": {
6
+ type: "boolean";
7
+ description: string;
8
+ default: false;
9
+ };
10
+ }>;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Dev command - Watch mode for development
3
+ */
4
+ import { defineCommand } from "citty";
5
+ import consola from "consola";
6
+ import { watch, readdirSync, mkdirSync, existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { loadConfig } from "../../core/config";
9
+ import { bundleLambda } from "../../core/bundler";
10
+ import { packageLambda } from "../../core/packager";
11
+ import { createWrapperEntry } from "../../core/handler-wrapper";
12
+ import { getLambdaBundleName } from "../../core/naming";
13
+ export const devCommand = defineCommand({
14
+ meta: {
15
+ name: "dev",
16
+ description: "Watch mode - rebuild lambdas on file changes",
17
+ },
18
+ args: {
19
+ "no-package": {
20
+ type: "boolean",
21
+ description: "Skip creating zip files (faster rebuilds)",
22
+ default: false,
23
+ },
24
+ },
25
+ async run({ args }) {
26
+ consola.start("Loading configuration...");
27
+ let config;
28
+ try {
29
+ config = await loadConfig();
30
+ }
31
+ catch (error) {
32
+ consola.error(error instanceof Error ? error.message : error);
33
+ process.exit(1);
34
+ }
35
+ const apiName = config.apiName;
36
+ const lambdasDir = join(process.cwd(), config.lambdasDir);
37
+ const outputDir = join(process.cwd(), config.outputDir);
38
+ const tempDir = join(outputDir, "__temp__");
39
+ // Ensure directories exist
40
+ if (!existsSync(lambdasDir)) {
41
+ consola.error(`Lambdas directory not found: ${lambdasDir}`);
42
+ process.exit(1);
43
+ }
44
+ mkdirSync(outputDir, { recursive: true });
45
+ mkdirSync(tempDir, { recursive: true });
46
+ // Get initial lambda files
47
+ const lambdaFiles = readdirSync(lambdasDir).filter((f) => f.endsWith(".ts") && !f.startsWith("__"));
48
+ if (lambdaFiles.length === 0) {
49
+ consola.warn("No lambda files found in", config.lambdasDir);
50
+ }
51
+ // Build function for a single lambda
52
+ async function buildSingleLambda(filename) {
53
+ const name = filename.replace(/\.ts$/, "");
54
+ const bundleName = getLambdaBundleName(apiName, name);
55
+ const inputPath = join(lambdasDir, filename);
56
+ // Create wrapper entry
57
+ const wrapperPath = join(tempDir, `${name}-wrapper.ts`);
58
+ const wrapperContent = createWrapperEntry(inputPath);
59
+ await Bun.write(wrapperPath, wrapperContent);
60
+ // Bundle with prefixed name
61
+ const buildResult = await bundleLambda(bundleName, wrapperPath, outputDir, config);
62
+ if (!buildResult.success) {
63
+ consola.error(`Failed to build ${name}: ${buildResult.error}`);
64
+ return false;
65
+ }
66
+ consola.success(`Built ${bundleName}.js`);
67
+ // Package if not disabled
68
+ if (!args["no-package"]) {
69
+ const jsPath = join(outputDir, `${bundleName}.js`);
70
+ const packageResult = await packageLambda(bundleName, jsPath, outputDir);
71
+ if (!packageResult.success) {
72
+ consola.error(`Failed to package ${name}: ${packageResult.error}`);
73
+ return false;
74
+ }
75
+ consola.success(`Packaged ${bundleName}.zip`);
76
+ }
77
+ return true;
78
+ }
79
+ // Initial build of all lambdas
80
+ consola.start("Initial build...");
81
+ for (const file of lambdaFiles) {
82
+ await buildSingleLambda(file);
83
+ }
84
+ consola.info("Initial build complete");
85
+ console.log("");
86
+ consola.box("Watch mode active\n\n" +
87
+ `Watching: ${config.lambdasDir}/\n` +
88
+ `Output: ${config.outputDir}/\n\n` +
89
+ "Press Ctrl+C to stop");
90
+ // Set up file watcher
91
+ const watcher = watch(lambdasDir, { recursive: false }, async (event, filename) => {
92
+ if (!filename || !filename.endsWith(".ts") || filename.startsWith("__")) {
93
+ return;
94
+ }
95
+ console.log("");
96
+ consola.info(`Change detected: ${filename}`);
97
+ const success = await buildSingleLambda(filename);
98
+ if (success) {
99
+ consola.ready("Rebuild complete");
100
+ }
101
+ });
102
+ // Handle graceful shutdown
103
+ process.on("SIGINT", () => {
104
+ console.log("");
105
+ consola.info("Stopping watcher...");
106
+ watcher.close();
107
+ // Clean up temp directory
108
+ const { rmSync } = require("fs");
109
+ try {
110
+ rmSync(tempDir, { recursive: true, force: true });
111
+ }
112
+ catch {
113
+ // Ignore cleanup errors
114
+ }
115
+ process.exit(0);
116
+ });
117
+ // Keep process alive
118
+ await new Promise(() => { });
119
+ },
120
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Generate IAC command - Regenerate Terraform files without rebuilding
3
+ */
4
+ export declare const generateIacCommand: import("citty").CommandDef<{}>;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Generate IAC command - Regenerate Terraform files without rebuilding
3
+ */
4
+ import { defineCommand } from "citty";
5
+ import consola from "consola";
6
+ import { readdirSync, existsSync, mkdirSync } from "fs";
7
+ import { join } from "path";
8
+ import { loadConfig } from "../../core/config";
9
+ import { generateManifest, writeManifest } from "../../core/manifest";
10
+ import { writeTerraformVars } from "../../core/terraform";
11
+ import { getOriginalLambdaName } from "../../core/naming";
12
+ export const generateIacCommand = defineCommand({
13
+ meta: {
14
+ name: "generate-iac",
15
+ description: "Regenerate Terraform files from existing build artifacts",
16
+ },
17
+ args: {},
18
+ async run() {
19
+ consola.start("Loading configuration...");
20
+ let config;
21
+ try {
22
+ config = await loadConfig();
23
+ }
24
+ catch (error) {
25
+ consola.error(error instanceof Error ? error.message : error);
26
+ process.exit(1);
27
+ }
28
+ const apiName = config.apiName;
29
+ const outputDir = join(process.cwd(), config.outputDir);
30
+ const terraformDir = join(process.cwd(), config.terraform.outputDir);
31
+ // Check that build output exists
32
+ if (!existsSync(outputDir)) {
33
+ consola.error(`Build output directory not found: ${outputDir}\nRun 'elysian build' first.`);
34
+ process.exit(1);
35
+ }
36
+ // Ensure terraform directory exists
37
+ mkdirSync(terraformDir, { recursive: true });
38
+ // Get built lambda files (they have the apiName prefix)
39
+ const jsFiles = readdirSync(outputDir).filter((f) => f.endsWith(".js") && !f.startsWith("__temp__"));
40
+ if (jsFiles.length === 0) {
41
+ consola.error(`No built lambda files found in ${outputDir}\nRun 'elysian build' first.`);
42
+ process.exit(1);
43
+ }
44
+ // Convert bundle names back to .ts names for manifest generation
45
+ // The manifest generator will re-add the prefix
46
+ const lambdaFiles = jsFiles.map((f) => {
47
+ const bundleName = f.replace(/\.js$/, "");
48
+ const originalName = getOriginalLambdaName(apiName, bundleName);
49
+ return `${originalName}.ts`;
50
+ });
51
+ consola.info(`Found ${lambdaFiles.length} built lambda(s)`);
52
+ // Generate manifest
53
+ consola.start("Generating route manifest...");
54
+ try {
55
+ const manifest = await generateManifest(lambdaFiles, outputDir, config.openapi.enabled, apiName);
56
+ // Write JSON manifest
57
+ const manifestPath = join(outputDir, "manifest.json");
58
+ await writeManifest(manifest, manifestPath);
59
+ consola.success("Generated manifest.json");
60
+ // Write Terraform variables
61
+ const tfvarsPath = await writeTerraformVars(manifest, config);
62
+ consola.success(`Generated ${config.terraform.tfvarsFilename}`);
63
+ // Print summary
64
+ console.log("");
65
+ consola.box(`Infrastructure files generated\n\n` +
66
+ `Lambdas: ${manifest.lambdas.length}\n` +
67
+ `Routes: ${manifest.routes.length}\n\n` +
68
+ `Output: ${config.terraform.outputDir}/${config.terraform.tfvarsFilename}`);
69
+ // Print route summary
70
+ console.log("\nRoute Summary:");
71
+ for (const route of manifest.routes) {
72
+ const params = route.pathParameters.length > 0
73
+ ? ` [${route.pathParameters.join(", ")}]`
74
+ : "";
75
+ console.log(` ${route.method.padEnd(6)} ${route.path.padEnd(30)} → ${route.lambda}${params}`);
76
+ }
77
+ }
78
+ catch (error) {
79
+ consola.error(error instanceof Error ? error.message : error);
80
+ process.exit(1);
81
+ }
82
+ },
83
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Init command - Initialize a new elysian project
3
+ */
4
+ export declare const initCommand: import("citty").CommandDef<{
5
+ name: {
6
+ type: "string";
7
+ description: string;
8
+ default: string;
9
+ };
10
+ force: {
11
+ type: "boolean";
12
+ description: string;
13
+ default: false;
14
+ };
15
+ }>;