@actuallyjamez/elysian 0.7.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 +13 -13
- package/dist/cli/commands/init/scaffold.js +1 -1
- package/dist/cli/commands/init/templates.js +1 -1
- 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 +1 -1
|
@@ -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({
|
|
@@ -100,7 +100,10 @@ export const devCommand = defineCommand({
|
|
|
100
100
|
async function buildSingleLambda(filename) {
|
|
101
101
|
const lambdaName = filename.replace(/\.ts$/, "");
|
|
102
102
|
const bundleName = getLambdaBundleName(name, lambdaName);
|
|
103
|
-
|
|
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);
|
|
104
107
|
// Create wrapper entry
|
|
105
108
|
const wrapperPath = join(tempDir, `${lambdaName}-wrapper.ts`);
|
|
106
109
|
const wrapperContent = createWrapperEntry(inputPath);
|
|
@@ -108,17 +111,17 @@ export const devCommand = defineCommand({
|
|
|
108
111
|
// Bundle with prefixed name
|
|
109
112
|
const buildResult = await bundleLambda(bundleName, wrapperPath, outputDir, config);
|
|
110
113
|
if (!buildResult.success) {
|
|
111
|
-
return false;
|
|
114
|
+
return { success: false, error: buildResult.error };
|
|
112
115
|
}
|
|
113
116
|
// Package if not disabled
|
|
114
117
|
if (!args["no-package"]) {
|
|
115
118
|
const jsPath = join(outputDir, `${bundleName}.js`);
|
|
116
119
|
const packageResult = await packageLambda(bundleName, jsPath, outputDir);
|
|
117
120
|
if (!packageResult.success) {
|
|
118
|
-
return false;
|
|
121
|
+
return { success: false, error: packageResult.error };
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
|
-
return true;
|
|
124
|
+
return { success: true };
|
|
122
125
|
}
|
|
123
126
|
// Build all lambdas (including OpenAPI if enabled)
|
|
124
127
|
async function buildAll() {
|
|
@@ -127,20 +130,17 @@ export const devCommand = defineCommand({
|
|
|
127
130
|
const filesToBuild = [...lambdaFiles];
|
|
128
131
|
// Generate OpenAPI aggregator if enabled
|
|
129
132
|
if (shouldGenerateOpenApi(config)) {
|
|
130
|
-
await writeOpenApiLambda(lambdaFiles, lambdasDir, config);
|
|
133
|
+
await writeOpenApiLambda(lambdaFiles, lambdasDir, config, tempDir);
|
|
131
134
|
filesToBuild.push("__openapi__.ts");
|
|
132
135
|
}
|
|
133
136
|
// Build all lambdas
|
|
134
137
|
for (const file of filesToBuild) {
|
|
135
|
-
const
|
|
136
|
-
if (!success) {
|
|
137
|
-
return { success: false, count: 0, error:
|
|
138
|
+
const result = await buildSingleLambda(file);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
return { success: false, count: 0, error: `${file}: ${result.error || "Unknown error"}` };
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
|
-
//
|
|
141
|
-
if (shouldGenerateOpenApi(config)) {
|
|
142
|
-
await cleanupOpenApiLambda(lambdasDir);
|
|
143
|
-
}
|
|
143
|
+
// No need to cleanup OpenAPI - it's in tempDir which persists during dev
|
|
144
144
|
return { success: true, count: filesToBuild.length };
|
|
145
145
|
}
|
|
146
146
|
// Generate manifest and terraform vars
|
|
@@ -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...");
|
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()) {
|