@actuallyjamez/elysian 0.6.0 → 0.7.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/dist/cli/commands/build.js +10 -9
- package/dist/cli/commands/dev.js +44 -23
- package/dist/cli/commands/init/scaffold.js +1 -1
- package/dist/cli/commands/init/templates.js +3 -3
- package/dist/core/bundler.js +52 -5
- package/dist/core/handler-wrapper.js +20 -2
- package/dist/core/openapi.d.ts +3 -3
- package/dist/core/openapi.js +28 -10
- package/package.json +3 -2
|
@@ -9,7 +9,7 @@ import { bundleLambda } 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
|
-
import { shouldGenerateOpenApi, writeOpenApiLambda,
|
|
12
|
+
import { shouldGenerateOpenApi, writeOpenApiLambda, } from "../../core/openapi";
|
|
13
13
|
import { createWrapperEntry } from "../../core/handler-wrapper";
|
|
14
14
|
import { getLambdaBundleName } from "../../core/naming";
|
|
15
15
|
import { ui, pc, formatDuration, formatSize } from "../ui";
|
|
@@ -53,6 +53,9 @@ export const buildCommand = defineCommand({
|
|
|
53
53
|
}
|
|
54
54
|
mkdirSync(outputDir, { recursive: true });
|
|
55
55
|
mkdirSync(terraformDir, { recursive: true });
|
|
56
|
+
// Create temp directory for generated files
|
|
57
|
+
const tempDir = join(outputDir, "__temp__");
|
|
58
|
+
mkdirSync(tempDir, { recursive: true });
|
|
56
59
|
// Get lambda files
|
|
57
60
|
let lambdaFiles = readdirSync(lambdasDir).filter((f) => f.endsWith(".ts") && !f.startsWith("__"));
|
|
58
61
|
if (lambdaFiles.length === 0) {
|
|
@@ -61,18 +64,19 @@ export const buildCommand = defineCommand({
|
|
|
61
64
|
}
|
|
62
65
|
// Generate OpenAPI aggregator if enabled
|
|
63
66
|
if (shouldGenerateOpenApi(config)) {
|
|
64
|
-
await writeOpenApiLambda(lambdaFiles, lambdasDir, config);
|
|
67
|
+
await writeOpenApiLambda(lambdaFiles, lambdasDir, config, tempDir);
|
|
65
68
|
lambdaFiles.push("__openapi__.ts");
|
|
66
69
|
}
|
|
67
70
|
// Build phase
|
|
68
71
|
ui.success(`Compiling ${lambdaFiles.length} lambdas...`);
|
|
69
|
-
const tempDir = join(outputDir, "__temp__");
|
|
70
|
-
mkdirSync(tempDir, { recursive: true });
|
|
71
72
|
const buildResults = [];
|
|
72
73
|
for (const file of lambdaFiles) {
|
|
73
74
|
const lambdaName = file.replace(/\.ts$/, "");
|
|
74
75
|
const bundleName = getLambdaBundleName(name, lambdaName);
|
|
75
|
-
|
|
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);
|
|
76
80
|
// Create wrapper entry that imports the original and exports handler
|
|
77
81
|
const wrapperPath = join(tempDir, `${lambdaName}-wrapper.ts`);
|
|
78
82
|
const wrapperContent = createWrapperEntry(inputPath);
|
|
@@ -87,10 +91,7 @@ export const buildCommand = defineCommand({
|
|
|
87
91
|
}
|
|
88
92
|
// Clean up temp directory
|
|
89
93
|
rmSync(tempDir, { recursive: true, force: true });
|
|
90
|
-
//
|
|
91
|
-
if (shouldGenerateOpenApi(config)) {
|
|
92
|
-
await cleanupOpenApiLambda(lambdasDir);
|
|
93
|
-
}
|
|
94
|
+
// No need to clean up OpenAPI file separately - it's in tempDir
|
|
94
95
|
// Package phase
|
|
95
96
|
ui.success("Packaging lambdas...");
|
|
96
97
|
const packageSizes = new Map();
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createWrapperEntry } from "../../core/handler-wrapper";
|
|
|
11
11
|
import { getLambdaBundleName } from "../../core/naming";
|
|
12
12
|
import { generateManifest, writeManifest } from "../../core/manifest";
|
|
13
13
|
import { writeTerraformVars } from "../../core/terraform";
|
|
14
|
-
import { shouldGenerateOpenApi, writeOpenApiLambda,
|
|
14
|
+
import { shouldGenerateOpenApi, writeOpenApiLambda, } from "../../core/openapi";
|
|
15
15
|
import { detectLocalStack, isTerraformInitialized, runTfLocalInit, runTfLocalApply, getTerraformOutputs, } from "../../core/localstack";
|
|
16
16
|
import { ui, pc, createSpinner, formatDuration } from "../ui";
|
|
17
17
|
export const devCommand = defineCommand({
|
|
@@ -95,11 +95,15 @@ export const devCommand = defineCommand({
|
|
|
95
95
|
// Track last terraform outputs and build info for display
|
|
96
96
|
let lastOutputs = null;
|
|
97
97
|
let lastBuildInfo = null;
|
|
98
|
+
let lastError = null;
|
|
98
99
|
// Build function for a single lambda
|
|
99
100
|
async function buildSingleLambda(filename) {
|
|
100
101
|
const lambdaName = filename.replace(/\.ts$/, "");
|
|
101
102
|
const bundleName = getLambdaBundleName(name, lambdaName);
|
|
102
|
-
|
|
103
|
+
// For OpenAPI, the source is in tempDir; for regular lambdas, it's in lambdasDir
|
|
104
|
+
const inputPath = filename === "__openapi__.ts"
|
|
105
|
+
? join(tempDir, filename)
|
|
106
|
+
: join(lambdasDir, filename);
|
|
103
107
|
// Create wrapper entry
|
|
104
108
|
const wrapperPath = join(tempDir, `${lambdaName}-wrapper.ts`);
|
|
105
109
|
const wrapperContent = createWrapperEntry(inputPath);
|
|
@@ -107,17 +111,17 @@ export const devCommand = defineCommand({
|
|
|
107
111
|
// Bundle with prefixed name
|
|
108
112
|
const buildResult = await bundleLambda(bundleName, wrapperPath, outputDir, config);
|
|
109
113
|
if (!buildResult.success) {
|
|
110
|
-
return false;
|
|
114
|
+
return { success: false, error: buildResult.error };
|
|
111
115
|
}
|
|
112
116
|
// Package if not disabled
|
|
113
117
|
if (!args["no-package"]) {
|
|
114
118
|
const jsPath = join(outputDir, `${bundleName}.js`);
|
|
115
119
|
const packageResult = await packageLambda(bundleName, jsPath, outputDir);
|
|
116
120
|
if (!packageResult.success) {
|
|
117
|
-
return false;
|
|
121
|
+
return { success: false, error: packageResult.error };
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
|
-
return true;
|
|
124
|
+
return { success: true };
|
|
121
125
|
}
|
|
122
126
|
// Build all lambdas (including OpenAPI if enabled)
|
|
123
127
|
async function buildAll() {
|
|
@@ -126,20 +130,17 @@ export const devCommand = defineCommand({
|
|
|
126
130
|
const filesToBuild = [...lambdaFiles];
|
|
127
131
|
// Generate OpenAPI aggregator if enabled
|
|
128
132
|
if (shouldGenerateOpenApi(config)) {
|
|
129
|
-
await writeOpenApiLambda(lambdaFiles, lambdasDir, config);
|
|
133
|
+
await writeOpenApiLambda(lambdaFiles, lambdasDir, config, tempDir);
|
|
130
134
|
filesToBuild.push("__openapi__.ts");
|
|
131
135
|
}
|
|
132
136
|
// Build all lambdas
|
|
133
137
|
for (const file of filesToBuild) {
|
|
134
|
-
const
|
|
135
|
-
if (!success) {
|
|
136
|
-
return { success: false, count: 0 };
|
|
138
|
+
const result = await buildSingleLambda(file);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
return { success: false, count: 0, error: `${file}: ${result.error || "Unknown error"}` };
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
|
-
//
|
|
140
|
-
if (shouldGenerateOpenApi(config)) {
|
|
141
|
-
await cleanupOpenApiLambda(lambdasDir);
|
|
142
|
-
}
|
|
143
|
+
// No need to cleanup OpenAPI - it's in tempDir which persists during dev
|
|
143
144
|
return { success: true, count: filesToBuild.length };
|
|
144
145
|
}
|
|
145
146
|
// Generate manifest and terraform vars
|
|
@@ -155,19 +156,19 @@ export const devCommand = defineCommand({
|
|
|
155
156
|
await writeTerraformVars(manifest, config);
|
|
156
157
|
return { success: true, routes: manifest.routes.length };
|
|
157
158
|
}
|
|
158
|
-
catch {
|
|
159
|
-
return { success: false, routes: 0 };
|
|
159
|
+
catch (err) {
|
|
160
|
+
return { success: false, routes: 0, error: err instanceof Error ? err.message : String(err) };
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
// Deploy to LocalStack
|
|
163
164
|
async function deployToLocalStack() {
|
|
164
165
|
const applyResult = await runTfLocalApply(terraformDir);
|
|
165
166
|
if (!applyResult.success) {
|
|
166
|
-
return false;
|
|
167
|
+
return { success: false, error: applyResult.error };
|
|
167
168
|
}
|
|
168
169
|
// Get and store outputs (transformed to LocalStack URLs)
|
|
169
170
|
lastOutputs = await getTerraformOutputs(terraformDir, true);
|
|
170
|
-
return true;
|
|
171
|
+
return { success: true };
|
|
171
172
|
}
|
|
172
173
|
// Show the final status screen (Vite-like)
|
|
173
174
|
function showReadyScreen(trigger, failed) {
|
|
@@ -178,6 +179,18 @@ export const devCommand = defineCommand({
|
|
|
178
179
|
if (trigger) {
|
|
179
180
|
ui.info(`Triggered by: ${trigger}`);
|
|
180
181
|
}
|
|
182
|
+
if (lastError) {
|
|
183
|
+
ui.blank();
|
|
184
|
+
console.log(pc.dim(" Error:"));
|
|
185
|
+
// Show first few lines of error
|
|
186
|
+
const errorLines = lastError.split("\n").slice(0, 10);
|
|
187
|
+
for (const line of errorLines) {
|
|
188
|
+
console.log(pc.red(` ${line}`));
|
|
189
|
+
}
|
|
190
|
+
if (lastError.split("\n").length > 10) {
|
|
191
|
+
console.log(pc.dim(" ... (truncated)"));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
181
194
|
}
|
|
182
195
|
else if (lastBuildInfo) {
|
|
183
196
|
ui.success(`Ready in ${pc.bold(formatDuration(lastBuildInfo.duration))}`);
|
|
@@ -211,6 +224,7 @@ export const devCommand = defineCommand({
|
|
|
211
224
|
// Run the full build and deploy cycle
|
|
212
225
|
async function runBuildCycle(trigger) {
|
|
213
226
|
const cycleStart = Date.now();
|
|
227
|
+
lastError = null;
|
|
214
228
|
// Show building status
|
|
215
229
|
ui.clear();
|
|
216
230
|
ui.header(pc.dim("dev"));
|
|
@@ -223,6 +237,7 @@ export const devCommand = defineCommand({
|
|
|
223
237
|
const buildResult = await buildAll();
|
|
224
238
|
if (!buildResult.success) {
|
|
225
239
|
buildSpinner.fail("Build failed");
|
|
240
|
+
lastError = buildResult.error || "Unknown build error";
|
|
226
241
|
showReadyScreen(trigger, true);
|
|
227
242
|
return;
|
|
228
243
|
}
|
|
@@ -232,6 +247,7 @@ export const devCommand = defineCommand({
|
|
|
232
247
|
const manifestResult = await generateManifestFiles();
|
|
233
248
|
if (!manifestResult.success) {
|
|
234
249
|
manifestSpinner.fail("Manifest failed");
|
|
250
|
+
lastError = manifestResult.error || "Unknown manifest error";
|
|
235
251
|
showReadyScreen(trigger, true);
|
|
236
252
|
return;
|
|
237
253
|
}
|
|
@@ -239,9 +255,10 @@ export const devCommand = defineCommand({
|
|
|
239
255
|
// Deploy if LocalStack enabled
|
|
240
256
|
if (localstackEnabled) {
|
|
241
257
|
const deploySpinner = createSpinner("Deploying...").start();
|
|
242
|
-
const
|
|
243
|
-
if (!
|
|
258
|
+
const deployResult = await deployToLocalStack();
|
|
259
|
+
if (!deployResult.success) {
|
|
244
260
|
deploySpinner.fail("Deploy failed");
|
|
261
|
+
lastError = deployResult.error || "Unknown deploy error";
|
|
245
262
|
showReadyScreen(trigger, true);
|
|
246
263
|
return;
|
|
247
264
|
}
|
|
@@ -260,14 +277,16 @@ export const devCommand = defineCommand({
|
|
|
260
277
|
// Run terraform-only deploy
|
|
261
278
|
async function runTerraformCycle(trigger) {
|
|
262
279
|
const cycleStart = Date.now();
|
|
280
|
+
lastError = null;
|
|
263
281
|
ui.clear();
|
|
264
282
|
ui.header(pc.dim("dev"));
|
|
265
283
|
ui.info(`Terraform: ${trigger}`);
|
|
266
284
|
ui.blank();
|
|
267
285
|
const deploySpinner = createSpinner("Deploying...").start();
|
|
268
|
-
const
|
|
269
|
-
if (!
|
|
286
|
+
const deployResult = await deployToLocalStack();
|
|
287
|
+
if (!deployResult.success) {
|
|
270
288
|
deploySpinner.fail("Deploy failed");
|
|
289
|
+
lastError = deployResult.error || "Unknown deploy error";
|
|
271
290
|
showReadyScreen(trigger, true);
|
|
272
291
|
return;
|
|
273
292
|
}
|
|
@@ -322,12 +341,14 @@ export const devCommand = defineCommand({
|
|
|
322
341
|
terraformWatcher = watch(terraformDir, { recursive: false }, (event, filename) => {
|
|
323
342
|
if (!filename)
|
|
324
343
|
return;
|
|
325
|
-
// Skip auto-generated files
|
|
344
|
+
// Skip auto-generated files and tflocal override files
|
|
326
345
|
if (filename === config.terraform.tfvarsFilename ||
|
|
327
346
|
filename.endsWith(".auto.tfvars") ||
|
|
328
347
|
filename.startsWith(".terraform") ||
|
|
329
348
|
filename.endsWith(".tfstate") ||
|
|
330
|
-
filename.endsWith(".tfstate.backup")
|
|
349
|
+
filename.endsWith(".tfstate.backup") ||
|
|
350
|
+
filename.includes("override") ||
|
|
351
|
+
filename.startsWith("localstack")) {
|
|
331
352
|
return;
|
|
332
353
|
}
|
|
333
354
|
// Only watch .tf files
|
|
@@ -147,7 +147,7 @@ async function scaffoldTerraform(cwd, info, name, result) {
|
|
|
147
147
|
*/
|
|
148
148
|
export async function installDependencies(cwd, packageManager) {
|
|
149
149
|
const deps = ["elysia", "@actuallyjamez/elysian"];
|
|
150
|
-
const devDeps = ["@types/
|
|
150
|
+
const devDeps = ["@types/node", "typescript"];
|
|
151
151
|
const addCmd = packageManager === "npm" ? "install" : "add";
|
|
152
152
|
const devFlag = packageManager === "npm" ? "--save-dev" : "-D";
|
|
153
153
|
ui.info("Installing dependencies...");
|
|
@@ -19,8 +19,8 @@ export function exampleLambdaTemplate() {
|
|
|
19
19
|
return `import { createLambda, t } from "@actuallyjamez/elysian";
|
|
20
20
|
|
|
21
21
|
export default createLambda()
|
|
22
|
-
.get("/
|
|
23
|
-
return \`Hello, \${query.name ?? "
|
|
22
|
+
.get("/", ({ query }) => {
|
|
23
|
+
return \`Hello, \${query.name ?? "Elysian"}!\`;
|
|
24
24
|
}, {
|
|
25
25
|
response: t.String(),
|
|
26
26
|
query: t.Object({
|
|
@@ -91,7 +91,7 @@ export function tsconfigTemplate() {
|
|
|
91
91
|
esModuleInterop: true,
|
|
92
92
|
skipLibCheck: true,
|
|
93
93
|
noEmit: true,
|
|
94
|
-
types: ["
|
|
94
|
+
types: ["@types/node"],
|
|
95
95
|
},
|
|
96
96
|
include: ["src/**/*", "elysian.config.ts"],
|
|
97
97
|
exclude: ["node_modules", "dist"],
|
package/dist/core/bundler.js
CHANGED
|
@@ -21,15 +21,37 @@ export async function bundleLambda(name, inputPath, outputDir, config) {
|
|
|
21
21
|
plugins: [createHandlerWrapperPlugin()],
|
|
22
22
|
});
|
|
23
23
|
if (!result.success) {
|
|
24
|
-
|
|
24
|
+
// Extract detailed error messages from build logs
|
|
25
|
+
const errorMessages = result.logs
|
|
25
26
|
.filter((log) => log.level === "error")
|
|
26
|
-
.map((log) =>
|
|
27
|
-
|
|
27
|
+
.map((log) => {
|
|
28
|
+
// Include file position if available
|
|
29
|
+
const position = log.position;
|
|
30
|
+
if (position) {
|
|
31
|
+
return `${position.file}:${position.line}:${position.column}: ${log.message}`;
|
|
32
|
+
}
|
|
33
|
+
return log.message;
|
|
34
|
+
});
|
|
35
|
+
// Also include warnings that might be relevant
|
|
36
|
+
const warningMessages = result.logs
|
|
37
|
+
.filter((log) => log.level === "warning")
|
|
38
|
+
.map((log) => log.message);
|
|
39
|
+
const allMessages = [...errorMessages, ...warningMessages];
|
|
40
|
+
// If no messages captured, try to get string representation of all logs
|
|
41
|
+
if (allMessages.length === 0 && result.logs.length > 0) {
|
|
42
|
+
const allLogs = result.logs.map((log) => `[${log.level}] ${log.message}`);
|
|
43
|
+
return {
|
|
44
|
+
name,
|
|
45
|
+
outputPath: join(outputDir, `${name}.js`),
|
|
46
|
+
success: false,
|
|
47
|
+
error: allLogs.join("\n") || "Build failed with no error message",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
28
50
|
return {
|
|
29
51
|
name,
|
|
30
52
|
outputPath: join(outputDir, `${name}.js`),
|
|
31
53
|
success: false,
|
|
32
|
-
error:
|
|
54
|
+
error: allMessages.length > 0 ? allMessages.join("\n") : "Build failed with no error message",
|
|
33
55
|
};
|
|
34
56
|
}
|
|
35
57
|
return {
|
|
@@ -39,11 +61,36 @@ export async function bundleLambda(name, inputPath, outputDir, config) {
|
|
|
39
61
|
};
|
|
40
62
|
}
|
|
41
63
|
catch (error) {
|
|
64
|
+
// Handle AggregateError from Bun bundler (contains detailed parse errors)
|
|
65
|
+
if (error instanceof AggregateError && error.errors?.length > 0) {
|
|
66
|
+
const errorMessages = error.errors.map((e) => {
|
|
67
|
+
// Use Bun.inspect for nicely formatted error output
|
|
68
|
+
if (typeof Bun !== "undefined" && Bun.inspect) {
|
|
69
|
+
return Bun.inspect(e);
|
|
70
|
+
}
|
|
71
|
+
// Fallback: try to extract message and position
|
|
72
|
+
const err = e;
|
|
73
|
+
if (err.position) {
|
|
74
|
+
return `${err.position.file}:${err.position.line}:${err.position.column}: ${err.message}`;
|
|
75
|
+
}
|
|
76
|
+
return String(e);
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
outputPath: join(outputDir, `${name}.js`),
|
|
81
|
+
success: false,
|
|
82
|
+
error: errorMessages.join("\n\n"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Capture full error details including stack trace
|
|
86
|
+
const errorMessage = error instanceof Error
|
|
87
|
+
? `${error.message}${error.stack ? `\n${error.stack}` : ""}`
|
|
88
|
+
: String(error);
|
|
42
89
|
return {
|
|
43
90
|
name,
|
|
44
91
|
outputPath: join(outputDir, `${name}.js`),
|
|
45
92
|
success: false,
|
|
46
|
-
error:
|
|
93
|
+
error: errorMessage,
|
|
47
94
|
};
|
|
48
95
|
}
|
|
49
96
|
}
|
|
@@ -124,14 +124,32 @@ export const handler = __createElysiaHandler(__route);
|
|
|
124
124
|
`;
|
|
125
125
|
return modifiedCode + handlerWrapper;
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Get the path to a module from elysian's node_modules
|
|
129
|
+
*/
|
|
130
|
+
function resolveFromElysian(modulePath) {
|
|
131
|
+
// Use import.meta.resolve to get the absolute path
|
|
132
|
+
// This resolves relative to where this code is located (elysian package)
|
|
133
|
+
try {
|
|
134
|
+
const resolved = import.meta.resolve(modulePath);
|
|
135
|
+
// Convert file:// URL to path
|
|
136
|
+
return resolved.replace("file://", "");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Fallback to relative import if resolution fails
|
|
140
|
+
return modulePath;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
127
143
|
/**
|
|
128
144
|
* Create a wrapper entry file that imports and re-exports with handler
|
|
129
145
|
*/
|
|
130
146
|
export function createWrapperEntry(originalPath) {
|
|
147
|
+
const honoTinyPath = resolveFromElysian("hono/tiny");
|
|
148
|
+
const honoLambdaPath = resolveFromElysian("hono/aws-lambda");
|
|
131
149
|
return `
|
|
132
150
|
import route from "${originalPath}";
|
|
133
|
-
import { Hono } from "
|
|
134
|
-
import { handle } from "
|
|
151
|
+
import { Hono } from "${honoTinyPath}";
|
|
152
|
+
import { handle } from "${honoLambdaPath}";
|
|
135
153
|
|
|
136
154
|
// Re-export the route as default for introspection
|
|
137
155
|
export default route;
|
package/dist/core/openapi.d.ts
CHANGED
|
@@ -8,11 +8,11 @@ import type { ResolvedConfig } from "./config";
|
|
|
8
8
|
/**
|
|
9
9
|
* Generate the OpenAPI aggregator lambda source code
|
|
10
10
|
*/
|
|
11
|
-
export declare function generateOpenApiLambdaSource(lambdaFiles: string[], config: ResolvedConfig): string;
|
|
11
|
+
export declare function generateOpenApiLambdaSource(lambdaFiles: string[], lambdasDir: string, config: ResolvedConfig): string;
|
|
12
12
|
/**
|
|
13
13
|
* Write the OpenAPI aggregator lambda to a temp location for bundling
|
|
14
14
|
*/
|
|
15
|
-
export declare function writeOpenApiLambda(lambdaFiles: string[], lambdasDir: string, config: ResolvedConfig): Promise<string>;
|
|
15
|
+
export declare function writeOpenApiLambda(lambdaFiles: string[], lambdasDir: string, config: ResolvedConfig, tempDir: string): Promise<string>;
|
|
16
16
|
/**
|
|
17
17
|
* Check if OpenAPI aggregator needs to be generated
|
|
18
18
|
*/
|
|
@@ -20,4 +20,4 @@ export declare function shouldGenerateOpenApi(config: ResolvedConfig): boolean;
|
|
|
20
20
|
/**
|
|
21
21
|
* Clean up the generated OpenAPI lambda file
|
|
22
22
|
*/
|
|
23
|
-
export declare function cleanupOpenApiLambda(
|
|
23
|
+
export declare function cleanupOpenApiLambda(tempDir: string): Promise<void>;
|
package/dist/core/openapi.js
CHANGED
|
@@ -5,28 +5,46 @@
|
|
|
5
5
|
* from all other lambdas to provide a complete API specification.
|
|
6
6
|
*/
|
|
7
7
|
import { join } from "path";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a module path from elysian's node_modules
|
|
10
|
+
*/
|
|
11
|
+
function resolveFromElysian(modulePath) {
|
|
12
|
+
try {
|
|
13
|
+
const resolved = import.meta.resolve(modulePath);
|
|
14
|
+
return resolved.replace("file://", "");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return modulePath;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
/**
|
|
9
21
|
* Generate the OpenAPI aggregator lambda source code
|
|
10
22
|
*/
|
|
11
|
-
export function generateOpenApiLambdaSource(lambdaFiles, config) {
|
|
23
|
+
export function generateOpenApiLambdaSource(lambdaFiles, lambdasDir, config) {
|
|
12
24
|
// Filter out any existing openapi file to avoid circular imports
|
|
13
25
|
const routeFiles = lambdaFiles.filter((f) => !f.includes("openapi") && !f.startsWith("__"));
|
|
14
26
|
const imports = routeFiles
|
|
15
27
|
.map((file, index) => {
|
|
16
28
|
const name = file.replace(/\.ts$/, "");
|
|
17
|
-
|
|
29
|
+
// Use absolute path to the lambda file
|
|
30
|
+
const absolutePath = join(lambdasDir, name);
|
|
31
|
+
return `import route${index} from "${absolutePath}";`;
|
|
18
32
|
})
|
|
19
33
|
.join("\n");
|
|
20
34
|
const uses = routeFiles.map((_, index) => `.use(route${index})`).join("\n\t\t");
|
|
35
|
+
// Resolve paths from elysian's dependencies
|
|
36
|
+
const honoTinyPath = resolveFromElysian("hono/tiny");
|
|
37
|
+
const honoLambdaPath = resolveFromElysian("hono/aws-lambda");
|
|
38
|
+
const openapiPath = resolveFromElysian("@elysiajs/openapi");
|
|
21
39
|
return `/**
|
|
22
40
|
* Auto-generated OpenAPI aggregator lambda
|
|
23
41
|
* Generated by elysia-apigw - DO NOT EDIT
|
|
24
42
|
*/
|
|
25
43
|
|
|
26
44
|
import Elysia from "elysia";
|
|
27
|
-
import { openapi } from "
|
|
28
|
-
import { Hono } from "
|
|
29
|
-
import { handle } from "
|
|
45
|
+
import { openapi } from "${openapiPath}";
|
|
46
|
+
import { Hono } from "${honoTinyPath}";
|
|
47
|
+
import { handle } from "${honoLambdaPath}";
|
|
30
48
|
|
|
31
49
|
${imports}
|
|
32
50
|
|
|
@@ -54,9 +72,9 @@ export const handler = handle(api);
|
|
|
54
72
|
/**
|
|
55
73
|
* Write the OpenAPI aggregator lambda to a temp location for bundling
|
|
56
74
|
*/
|
|
57
|
-
export async function writeOpenApiLambda(lambdaFiles, lambdasDir, config) {
|
|
58
|
-
const source = generateOpenApiLambdaSource(lambdaFiles, config);
|
|
59
|
-
const outputPath = join(
|
|
75
|
+
export async function writeOpenApiLambda(lambdaFiles, lambdasDir, config, tempDir) {
|
|
76
|
+
const source = generateOpenApiLambdaSource(lambdaFiles, lambdasDir, config);
|
|
77
|
+
const outputPath = join(tempDir, "__openapi__.ts");
|
|
60
78
|
await Bun.write(outputPath, source);
|
|
61
79
|
return outputPath;
|
|
62
80
|
}
|
|
@@ -69,8 +87,8 @@ export function shouldGenerateOpenApi(config) {
|
|
|
69
87
|
/**
|
|
70
88
|
* Clean up the generated OpenAPI lambda file
|
|
71
89
|
*/
|
|
72
|
-
export async function cleanupOpenApiLambda(
|
|
73
|
-
const openApiPath = join(
|
|
90
|
+
export async function cleanupOpenApiLambda(tempDir) {
|
|
91
|
+
const openApiPath = join(tempDir, "__openapi__.ts");
|
|
74
92
|
try {
|
|
75
93
|
const file = Bun.file(openApiPath);
|
|
76
94
|
if (await file.exists()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuallyjamez/elysian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Automatic Lambda bundler for Elysia with API Gateway and Terraform integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
|
-
"templates"
|
|
23
|
+
"templates",
|
|
24
|
+
"README.md"
|
|
24
25
|
],
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|