@geek-fun/serverlessinsight 0.3.4 → 0.4.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.
Files changed (43) hide show
  1. package/README.md +209 -21
  2. package/README.zh-CN.md +232 -0
  3. package/dist/package.json +33 -33
  4. package/dist/src/commands/index.js +50 -12
  5. package/dist/src/commands/local.js +6 -3
  6. package/dist/src/commands/template.js +3 -1
  7. package/dist/src/common/constants.js +3 -1
  8. package/dist/src/common/context.js +56 -30
  9. package/dist/src/common/credentials.js +15 -0
  10. package/dist/src/common/iacHelper.js +39 -4
  11. package/dist/src/common/index.d.ts +2 -1
  12. package/dist/src/common/index.js +2 -1
  13. package/dist/src/common/logger.js +6 -0
  14. package/dist/src/common/requestHelper.js +16 -0
  15. package/dist/src/common/rosClient.js +3 -0
  16. package/dist/src/parser/eventParser.js +1 -1
  17. package/dist/src/parser/index.d.ts +2 -1
  18. package/dist/src/parser/index.js +32 -1
  19. package/dist/src/stack/localStack/aliyunFc.js +145 -0
  20. package/dist/src/stack/localStack/bucket.js +226 -0
  21. package/dist/src/stack/localStack/event.js +133 -26
  22. package/dist/src/stack/localStack/function.js +120 -0
  23. package/dist/src/stack/localStack/functionRunner.js +270 -0
  24. package/dist/src/stack/localStack/index.d.ts +4 -1
  25. package/dist/src/stack/localStack/index.js +14 -4
  26. package/dist/src/stack/localStack/localServer.js +111 -0
  27. package/dist/src/stack/localStack/utils.js +36 -0
  28. package/dist/src/stack/rosStack/bootstrap.js +1 -1
  29. package/dist/src/types/localStack/index.d.ts +81 -0
  30. package/dist/src/types/localStack/index.js +10 -0
  31. package/dist/src/validator/iacSchema.js +17 -2
  32. package/dist/src/validator/rootSchema.js +46 -0
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/layers/si-bootstrap-sdk/README.md +63 -0
  35. package/layers/si-bootstrap-sdk/package-lock.json +39 -33
  36. package/layers/si-bootstrap-sdk/package.json +5 -5
  37. package/layers/si-bootstrap-sdk/support/operation-collection/README.md +47 -0
  38. package/layers/si-bootstrap-sdk/support/operation-collection/package-lock.json +298 -0
  39. package/layers/si-bootstrap-sdk/support/operation-collection/package.json +18 -0
  40. package/layers/si-bootstrap-sdk/support/operation-collection/publish.js +257 -0
  41. package/package.json +33 -33
  42. package/samples/aliyun-poc-es.yml +16 -12
  43. package/dist/src/common/domainHelper.js +0 -10
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateRequestId = exports.logApiGatewayRequest = exports.transformFCResponse = exports.addFCHeaders = exports.createAliyunContext = exports.createAliyunContextSerializable = exports.transformToAliyunEvent = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const common_1 = require("../../common");
6
+ const createFCLogger = (requestId) => {
7
+ const formatLog = (level, message) => {
8
+ const timestamp = new Date().toISOString();
9
+ console.log(`${timestamp} ${requestId} [${level}] ${message}`);
10
+ };
11
+ return {
12
+ debug: (message) => formatLog('DEBUG', message),
13
+ info: (message) => formatLog('INFO', message),
14
+ warn: (message) => formatLog('WARNING', message),
15
+ error: (message) => formatLog('ERROR', message),
16
+ log: (message) => formatLog('INFO', message),
17
+ };
18
+ };
19
+ const transformToAliyunEvent = async (req, url, query) => {
20
+ const rawBody = await (0, common_1.readRequestBody)(req);
21
+ const pathParameters = {};
22
+ const event = {
23
+ path: url,
24
+ httpMethod: req.method || 'GET',
25
+ headers: req.headers,
26
+ queryParameters: query,
27
+ pathParameters,
28
+ body: rawBody || undefined,
29
+ isBase64Encoded: false,
30
+ };
31
+ const eventBuffer = Buffer.from(JSON.stringify(event));
32
+ return { event: eventBuffer, headers: req.headers };
33
+ };
34
+ exports.transformToAliyunEvent = transformToAliyunEvent;
35
+ const createAliyunContextSerializable = (iac, functionName, handler, memory, timeout, requestId) => {
36
+ return {
37
+ requestId,
38
+ region: iac.provider.region || 'cn-hangzhou',
39
+ accountId: process.env.ALIYUN_ACCOUNT_ID || '000000000000',
40
+ credentials: {
41
+ accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID || 'mock-access-key-id',
42
+ accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET || 'mock-access-key-secret',
43
+ securityToken: process.env.ALIYUN_SECURITY_TOKEN || '',
44
+ },
45
+ function: {
46
+ name: functionName,
47
+ handler,
48
+ memory,
49
+ timeout,
50
+ initializer: '',
51
+ },
52
+ service: {
53
+ name: iac.service || 'default-service',
54
+ logProject: `${iac.service}-log-project`,
55
+ logStore: `${iac.service}-log-store`,
56
+ qualifier: 'LATEST',
57
+ versionId: '1',
58
+ },
59
+ tracing: {
60
+ spanContext: '',
61
+ jaegerEndpoint: '',
62
+ spanBaggages: {},
63
+ },
64
+ };
65
+ };
66
+ exports.createAliyunContextSerializable = createAliyunContextSerializable;
67
+ const createAliyunContext = (iac, functionName, handler, memory, timeout, requestId) => {
68
+ const baseContext = (0, exports.createAliyunContextSerializable)(iac, functionName, handler, memory, timeout, requestId);
69
+ return {
70
+ ...baseContext,
71
+ tracing: {
72
+ ...baseContext.tracing,
73
+ parseOpenTracingBaggages: () => ({}),
74
+ },
75
+ logger: createFCLogger(requestId),
76
+ };
77
+ };
78
+ exports.createAliyunContext = createAliyunContext;
79
+ const addFCHeaders = (context, headers) => {
80
+ return {
81
+ ...headers,
82
+ 'x-fc-request-id': context.requestId,
83
+ 'x-fc-access-key-id': context.credentials.accessKeyId,
84
+ 'x-fc-access-key-secret': context.credentials.accessKeySecret,
85
+ 'x-fc-security-token': context.credentials.securityToken,
86
+ 'x-fc-function-handler': context.function.handler,
87
+ 'x-fc-function-memory': String(context.function.memory),
88
+ 'x-fc-region': context.region,
89
+ 'x-fc-account-id': context.accountId,
90
+ 'x-fc-qualifier': context.service.qualifier,
91
+ 'x-fc-version-id': context.service.versionId,
92
+ 'x-fc-function-name': context.function.name,
93
+ 'x-fc-service-logproject': context.service.logProject,
94
+ 'x-fc-service-logstore': context.service.logStore,
95
+ 'x-fc-control-path': '/http-invoke',
96
+ };
97
+ };
98
+ exports.addFCHeaders = addFCHeaders;
99
+ const transformFCResponse = (result) => {
100
+ if (result && typeof result === 'object' && 'statusCode' in result && 'body' in result) {
101
+ const { statusCode: rawStatus = 200, body: rawBody, isBase64Encoded, headers = {}, } = result;
102
+ const parsedStatus = typeof rawStatus === 'string' ? parseInt(rawStatus, 10) : rawStatus;
103
+ const statusCode = isNaN(parsedStatus) ? 200 : parsedStatus;
104
+ let body = rawBody;
105
+ if (isBase64Encoded && typeof body === 'string') {
106
+ body = Buffer.from(body, 'base64').toString('utf-8');
107
+ }
108
+ if (typeof body === 'string') {
109
+ try {
110
+ body = JSON.parse(body);
111
+ }
112
+ catch {
113
+ // If parsing fails, keep as string
114
+ }
115
+ }
116
+ return { statusCode, headers, body };
117
+ }
118
+ return {
119
+ statusCode: 200,
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: result,
122
+ };
123
+ };
124
+ exports.transformFCResponse = transformFCResponse;
125
+ const logApiGatewayRequest = (requestId, apiPath, statusCode, startTime, endTime, sourceIp) => {
126
+ const duration = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed(1);
127
+ const startTimeStr = formatDateTime(startTime);
128
+ const endTimeStr = formatDateTime(endTime);
129
+ const timestamp = formatDateTime(new Date());
130
+ console.log(`${timestamp} | ${requestId} | ${apiPath} | Sync Call | local-app | Development | local-project | ${statusCode} | ${startTimeStr} | ${endTimeStr} | ${duration}s | - | ${sourceIp}`);
131
+ };
132
+ exports.logApiGatewayRequest = logApiGatewayRequest;
133
+ const formatDateTime = (date) => {
134
+ const year = date.getFullYear();
135
+ const month = String(date.getMonth() + 1).padStart(2, '0');
136
+ const day = String(date.getDate()).padStart(2, '0');
137
+ const hours = String(date.getHours()).padStart(2, '0');
138
+ const minutes = String(date.getMinutes()).padStart(2, '0');
139
+ const seconds = String(date.getSeconds()).padStart(2, '0');
140
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
141
+ };
142
+ const generateRequestId = () => {
143
+ return (0, crypto_1.randomUUID)().replace(/-/g, '');
144
+ };
145
+ exports.generateRequestId = generateRequestId;
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.bucketsHandler = void 0;
7
+ const common_1 = require("../../common");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const TEXT_MIME_TYPES = new Set([
11
+ 'text/plain',
12
+ 'text/html',
13
+ 'text/css',
14
+ 'text/javascript',
15
+ 'text/markdown',
16
+ 'text/xml',
17
+ 'application/json',
18
+ 'application/javascript',
19
+ 'application/xml',
20
+ ]);
21
+ const getMimeType = (filename) => {
22
+ const ext = node_path_1.default.extname(filename).toLowerCase();
23
+ const mimeTypes = {
24
+ '.html': 'text/html',
25
+ '.htm': 'text/html',
26
+ '.css': 'text/css',
27
+ '.js': 'application/javascript',
28
+ '.json': 'application/json',
29
+ '.xml': 'application/xml',
30
+ '.txt': 'text/plain',
31
+ '.md': 'text/markdown',
32
+ '.jpg': 'image/jpeg',
33
+ '.jpeg': 'image/jpeg',
34
+ '.png': 'image/png',
35
+ '.gif': 'image/gif',
36
+ '.svg': 'image/svg+xml',
37
+ '.ico': 'image/x-icon',
38
+ '.pdf': 'application/pdf',
39
+ '.zip': 'application/zip',
40
+ '.tar': 'application/x-tar',
41
+ '.gz': 'application/gzip',
42
+ };
43
+ return mimeTypes[ext] || 'application/octet-stream';
44
+ };
45
+ const listDirectory = (dirPath, bucketPath) => {
46
+ try {
47
+ const entries = node_fs_1.default.readdirSync(dirPath, { withFileTypes: true });
48
+ return entries.map((entry) => {
49
+ const fullPath = node_path_1.default.join(dirPath, entry.name);
50
+ const relativePath = node_path_1.default.relative(bucketPath, fullPath);
51
+ const stats = node_fs_1.default.statSync(fullPath);
52
+ return {
53
+ name: entry.name,
54
+ type: entry.isDirectory() ? 'directory' : 'file',
55
+ size: entry.isDirectory() ? 0 : stats.size,
56
+ path: relativePath.replace(/\\/g, '/'), // Normalize path separators
57
+ };
58
+ });
59
+ }
60
+ catch (error) {
61
+ common_1.logger.error(`Error listing directory: ${error}`);
62
+ return [];
63
+ }
64
+ };
65
+ const getAllFiles = (dirPath, bucketPath, fileList = []) => {
66
+ try {
67
+ const entries = node_fs_1.default.readdirSync(dirPath, { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ const fullPath = node_path_1.default.join(dirPath, entry.name);
70
+ const relativePath = node_path_1.default.relative(bucketPath, fullPath);
71
+ if (entry.isDirectory()) {
72
+ // Recursively list files in subdirectories
73
+ getAllFiles(fullPath, bucketPath, fileList);
74
+ }
75
+ else {
76
+ const stats = node_fs_1.default.statSync(fullPath);
77
+ fileList.push({
78
+ name: entry.name,
79
+ type: 'file',
80
+ size: stats.size,
81
+ path: relativePath.replace(/\\/g, '/'),
82
+ });
83
+ }
84
+ }
85
+ return fileList;
86
+ }
87
+ catch (error) {
88
+ common_1.logger.error(`Error getting all files: ${error}`);
89
+ return fileList;
90
+ }
91
+ };
92
+ const bucketsHandler = async (req, parsed, iac) => {
93
+ common_1.logger.info(`Bucket request received by local server -> ${req.method} ${parsed.identifier ?? '/'} ${parsed.url}`);
94
+ // Find the bucket definition
95
+ const bucketDef = iac.buckets?.find((bucket) => bucket.key === parsed.identifier);
96
+ if (!bucketDef) {
97
+ return {
98
+ statusCode: 404,
99
+ body: { error: 'Bucket not found', bucketKey: parsed.identifier },
100
+ };
101
+ }
102
+ // Determine the bucket path - use website.code if available, otherwise use bucket name
103
+ let bucketBasePath;
104
+ if (bucketDef.website?.code) {
105
+ bucketBasePath = node_path_1.default.resolve(process.cwd(), bucketDef.website.code);
106
+ }
107
+ else {
108
+ // Fallback: create a directory based on bucket name (for non-website buckets)
109
+ bucketBasePath = node_path_1.default.resolve(process.cwd(), bucketDef.name);
110
+ }
111
+ // Check if bucket path exists
112
+ if (!node_fs_1.default.existsSync(bucketBasePath)) {
113
+ return {
114
+ statusCode: 404,
115
+ body: {
116
+ error: 'Bucket directory not found',
117
+ bucketKey: bucketDef.key,
118
+ bucketName: bucketDef.name,
119
+ expectedPath: bucketBasePath,
120
+ },
121
+ };
122
+ }
123
+ // Root path lists all files in bucket
124
+ if (parsed.url === '/') {
125
+ try {
126
+ const files = getAllFiles(bucketBasePath, bucketBasePath);
127
+ return {
128
+ statusCode: 200,
129
+ headers: { 'Content-Type': 'application/json' },
130
+ body: {
131
+ bucket: bucketDef.name,
132
+ bucketKey: bucketDef.key,
133
+ files,
134
+ count: files.length,
135
+ },
136
+ };
137
+ }
138
+ catch (error) {
139
+ common_1.logger.error(`Error listing bucket files: ${error}`);
140
+ return {
141
+ statusCode: 500,
142
+ body: {
143
+ error: 'Failed to list bucket files',
144
+ message: error instanceof Error ? error.message : String(error),
145
+ },
146
+ };
147
+ }
148
+ }
149
+ // Otherwise, serve the requested file
150
+ const requestedPath = parsed.url.startsWith('/') ? parsed.url.slice(1) : parsed.url;
151
+ const filePath = node_path_1.default.join(bucketBasePath, requestedPath);
152
+ // Security check: ensure the requested file is within the bucket directory
153
+ const normalizedFilePath = node_path_1.default.normalize(filePath);
154
+ const normalizedBucketPath = node_path_1.default.normalize(bucketBasePath);
155
+ if (!normalizedFilePath.startsWith(normalizedBucketPath)) {
156
+ return {
157
+ statusCode: 403,
158
+ body: { error: 'Access denied: Path traversal attempt detected' },
159
+ };
160
+ }
161
+ // Check if file exists
162
+ if (!node_fs_1.default.existsSync(filePath)) {
163
+ return {
164
+ statusCode: 404,
165
+ body: {
166
+ error: 'File not found',
167
+ path: requestedPath,
168
+ bucketKey: bucketDef.key,
169
+ },
170
+ };
171
+ }
172
+ // If it's a directory, list its contents
173
+ const stats = node_fs_1.default.statSync(filePath);
174
+ if (stats.isDirectory()) {
175
+ try {
176
+ const files = listDirectory(filePath, bucketBasePath);
177
+ return {
178
+ statusCode: 200,
179
+ headers: { 'Content-Type': 'application/json' },
180
+ body: {
181
+ bucket: bucketDef.name,
182
+ bucketKey: bucketDef.key,
183
+ directory: requestedPath,
184
+ files,
185
+ count: files.length,
186
+ },
187
+ };
188
+ }
189
+ catch (error) {
190
+ common_1.logger.error(`Error listing directory: ${error}`);
191
+ return {
192
+ statusCode: 500,
193
+ body: {
194
+ error: 'Failed to list directory',
195
+ message: error instanceof Error ? error.message : String(error),
196
+ },
197
+ };
198
+ }
199
+ }
200
+ // Serve the file
201
+ try {
202
+ const fileContent = node_fs_1.default.readFileSync(filePath);
203
+ const mimeType = getMimeType(filePath);
204
+ // For text files, return as string; for binary files, return as base64
205
+ const isTextFile = TEXT_MIME_TYPES.has(mimeType);
206
+ return {
207
+ statusCode: 200,
208
+ headers: {
209
+ 'Content-Type': mimeType,
210
+ 'Content-Length': String(fileContent.length),
211
+ },
212
+ body: isTextFile ? fileContent.toString('utf-8') : fileContent.toString('base64'),
213
+ };
214
+ }
215
+ catch (error) {
216
+ common_1.logger.error(`Error reading file: ${error}`);
217
+ return {
218
+ statusCode: 500,
219
+ body: {
220
+ error: 'Failed to read file',
221
+ message: error instanceof Error ? error.message : String(error),
222
+ },
223
+ };
224
+ }
225
+ };
226
+ exports.bucketsHandler = bucketsHandler;
@@ -3,36 +3,143 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.startEvents = void 0;
7
- const node_http_1 = __importDefault(require("node:http"));
8
- const common_1 = require("../../common");
6
+ exports.eventsHandler = void 0;
9
7
  const types_1 = require("../../types");
10
8
  const lodash_1 = require("lodash");
11
- const startApiGatewayServer = (event) => {
12
- const server = node_http_1.default.createServer((req, res) => {
13
- const matchedTrigger = event.triggers.find((trigger) => trigger.method === req.method && trigger.path === req.url);
14
- if (!matchedTrigger) {
15
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
16
- res.end('Not Found\n');
17
- common_1.logger.warn(`API Gateway Event - ${req.method} ${req.url} -> Not Found`);
18
- return;
9
+ const common_1 = require("../../common");
10
+ const function_1 = require("./function");
11
+ const aliyunFc_1 = require("./aliyunFc");
12
+ const functionRunner_1 = require("./functionRunner");
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const node_fs_1 = __importDefault(require("node:fs"));
15
+ const utils_1 = require("./utils");
16
+ const matchTrigger = (req, trigger) => {
17
+ if (req.method !== 'ANY' && req.method !== trigger.method) {
18
+ return false;
19
+ }
20
+ const normalize = (s) => s.replace(/^\/+|\/+$/g, '');
21
+ const [pathSegments, triggerSegments] = [
22
+ normalize(req.path).split('/'),
23
+ normalize(trigger.path).split('/'),
24
+ ];
25
+ const hasWildcard = triggerSegments[triggerSegments.length - 1] === '*';
26
+ const prefixSegments = hasWildcard ? triggerSegments.slice(0, -1) : triggerSegments;
27
+ const minRequiredSegments = prefixSegments.length;
28
+ if (pathSegments.length < minRequiredSegments)
29
+ return false;
30
+ return prefixSegments.every((triggerSegment, index) => {
31
+ const pathSegment = pathSegments[index];
32
+ if (triggerSegment.startsWith('[') && triggerSegment.endsWith(']')) {
33
+ return pathSegment !== '';
19
34
  }
20
- res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
21
- res.end(`Invoked backend: ${matchedTrigger.backend}\n`);
22
- common_1.logger.info(`API Gateway Event - ${req.method} ${req.url} -> ${matchedTrigger.backend}`);
23
- });
24
- const port = 3000 + Math.floor(Math.random() * 1000);
25
- server.listen(port, () => {
26
- common_1.logger.info(`API Gateway "${event.name}" listening on http://localhost:${port}`);
35
+ return triggerSegment === pathSegment;
27
36
  });
28
37
  };
29
- const startEvents = (events) => {
30
- const apiGateways = events?.filter((event) => event.type === types_1.EventTypes.API_GATEWAY);
31
- if ((0, lodash_1.isEmpty)(apiGateways)) {
32
- return;
38
+ const servEvent = async (req, parsed, iac) => {
39
+ const startTime = new Date();
40
+ const requestId = (0, aliyunFc_1.generateRequestId)();
41
+ const sourceIp = req.socket?.remoteAddress || '127.0.0.1';
42
+ const isAliyun = iac.provider.name === common_1.ProviderEnum.ALIYUN;
43
+ const event = iac.events?.find((event) => event.type === types_1.EventTypes.API_GATEWAY && event.key === parsed.identifier);
44
+ if ((0, lodash_1.isEmpty)(event)) {
45
+ return {
46
+ statusCode: 404,
47
+ body: { error: 'API Gateway event not found', event: parsed.identifier },
48
+ };
33
49
  }
34
- apiGateways.forEach((gateway) => {
35
- startApiGatewayServer(gateway);
36
- });
50
+ common_1.logger.info(`Event trigger ${JSON.stringify(event.triggers)}, req method: ${req.method}, req url${req.url}`);
51
+ const matchedTrigger = event.triggers.find((trigger) => matchTrigger({ method: parsed.method, path: parsed.url }, trigger));
52
+ if (!matchedTrigger) {
53
+ const endTime = new Date();
54
+ if (isAliyun) {
55
+ (0, aliyunFc_1.logApiGatewayRequest)(requestId, parsed.url, 404, startTime, endTime, sourceIp);
56
+ }
57
+ return { statusCode: 404, body: { error: 'No matching trigger found' } };
58
+ }
59
+ if (matchedTrigger.backend) {
60
+ const backendDef = (0, common_1.getIacDefinition)(iac, matchedTrigger.backend);
61
+ if (!backendDef) {
62
+ const endTime = new Date();
63
+ if (isAliyun) {
64
+ (0, aliyunFc_1.logApiGatewayRequest)(requestId, parsed.url, 500, startTime, endTime, sourceIp);
65
+ }
66
+ return {
67
+ statusCode: 500,
68
+ body: { error: 'Backend definition missing', backend: matchedTrigger.backend },
69
+ };
70
+ }
71
+ // For Aliyun, handle the function execution with proper event transformation
72
+ if (isAliyun && backendDef.code) {
73
+ let tempDir = null;
74
+ try {
75
+ const { event: aliyunEvent } = await (0, aliyunFc_1.transformToAliyunEvent)(req, parsed.url, parsed.query);
76
+ const codePath = node_path_1.default.resolve(process.cwd(), backendDef.code.path);
77
+ let codeDir;
78
+ if (codePath.endsWith('.zip') && node_fs_1.default.existsSync(codePath)) {
79
+ tempDir = await (0, utils_1.extractZipFile)(codePath);
80
+ codeDir = tempDir;
81
+ }
82
+ else if (node_fs_1.default.existsSync(codePath) && node_fs_1.default.statSync(codePath).isDirectory()) {
83
+ codeDir = codePath;
84
+ }
85
+ else {
86
+ codeDir = node_path_1.default.dirname(codePath);
87
+ }
88
+ const funOptions = {
89
+ codeDir,
90
+ functionKey: backendDef.key,
91
+ handler: backendDef.code.handler,
92
+ servicePath: '',
93
+ timeout: backendDef.timeout * 1000,
94
+ };
95
+ const aliyunContext = (0, aliyunFc_1.createAliyunContextSerializable)(iac, backendDef.name, backendDef.code.handler, backendDef.memory, backendDef.timeout, requestId);
96
+ const env = {
97
+ ...backendDef.environment,
98
+ };
99
+ common_1.logger.debug(`Invoking FC function with Aliyun event format`);
100
+ const result = await (0, functionRunner_1.invokeFunction)(funOptions, env, aliyunEvent, aliyunContext);
101
+ const endTime = new Date();
102
+ const transformed = (0, aliyunFc_1.transformFCResponse)(result);
103
+ // Log API Gateway request
104
+ (0, aliyunFc_1.logApiGatewayRequest)(requestId, parsed.url, transformed.statusCode, startTime, endTime, sourceIp);
105
+ return {
106
+ statusCode: transformed.statusCode,
107
+ headers: transformed.headers,
108
+ body: transformed.body,
109
+ };
110
+ }
111
+ catch (error) {
112
+ const endTime = new Date();
113
+ (0, aliyunFc_1.logApiGatewayRequest)(requestId, parsed.url, 500, startTime, endTime, sourceIp);
114
+ common_1.logger.error(`Function execution error: ${error}`);
115
+ return {
116
+ statusCode: 500,
117
+ body: {
118
+ error: 'Function execution failed',
119
+ message: error instanceof Error ? error.message : String(error),
120
+ },
121
+ };
122
+ }
123
+ finally {
124
+ if (tempDir && node_fs_1.default.existsSync(tempDir)) {
125
+ node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
126
+ }
127
+ }
128
+ }
129
+ // For non-Aliyun or when using functionsHandler
130
+ const result = await (0, function_1.functionsHandler)(req, { ...parsed, identifier: backendDef?.key }, iac);
131
+ const endTime = new Date();
132
+ if (isAliyun) {
133
+ (0, aliyunFc_1.logApiGatewayRequest)(requestId, parsed.url, result.statusCode, startTime, endTime, sourceIp);
134
+ }
135
+ return result;
136
+ }
137
+ return {
138
+ statusCode: 202,
139
+ body: { message: 'Trigger matched but no backend configured' },
140
+ };
141
+ };
142
+ const eventsHandler = async (req, parsed, iac) => {
143
+ return await servEvent(req, parsed, iac);
37
144
  };
38
- exports.startEvents = startEvents;
145
+ exports.eventsHandler = eventsHandler;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.functionsHandler = void 0;
7
+ const common_1 = require("../../common");
8
+ const functionRunner_1 = require("./functionRunner");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const aliyunFc_1 = require("./aliyunFc");
12
+ const utils_1 = require("./utils");
13
+ const functionsHandler = async (req, parsed, iac) => {
14
+ common_1.logger.info(`Function request received by local server -> ${req.method} ${parsed.identifier ?? '/'} `);
15
+ const fcDef = iac.functions?.find((fn) => fn.key === parsed.identifier);
16
+ if (!fcDef) {
17
+ return {
18
+ statusCode: 404,
19
+ body: { error: 'Function not found', functionKey: parsed.identifier },
20
+ };
21
+ }
22
+ if (!fcDef.code) {
23
+ return {
24
+ statusCode: 400,
25
+ body: { error: 'Function code configuration not found', functionKey: fcDef.key },
26
+ };
27
+ }
28
+ let tempDir = null;
29
+ try {
30
+ const codePath = node_path_1.default.resolve(process.cwd(), fcDef.code.path);
31
+ let codeDir;
32
+ if (codePath.endsWith('.zip') && node_fs_1.default.existsSync(codePath)) {
33
+ tempDir = await (0, utils_1.extractZipFile)(codePath);
34
+ codeDir = tempDir;
35
+ }
36
+ else if (node_fs_1.default.existsSync(codePath) && node_fs_1.default.statSync(codePath).isDirectory()) {
37
+ codeDir = codePath;
38
+ }
39
+ else {
40
+ codeDir = node_path_1.default.dirname(codePath);
41
+ }
42
+ const funOptions = {
43
+ codeDir,
44
+ functionKey: fcDef.key,
45
+ handler: fcDef.code.handler,
46
+ servicePath: '',
47
+ timeout: fcDef.timeout * 1000,
48
+ };
49
+ // Check if provider is Aliyun to use Aliyun FC format
50
+ const isAliyun = iac.provider.name === common_1.ProviderEnum.ALIYUN;
51
+ let event;
52
+ let fcContext;
53
+ let env;
54
+ if (isAliyun) {
55
+ // Aliyun FC format: event is a Buffer containing JSON
56
+ const requestId = (0, aliyunFc_1.generateRequestId)();
57
+ const { event: aliyunEvent } = await (0, aliyunFc_1.transformToAliyunEvent)(req, parsed.url, parsed.query);
58
+ event = aliyunEvent;
59
+ // Use serializable context for worker thread (logger will be added inside worker)
60
+ fcContext = (0, aliyunFc_1.createAliyunContextSerializable)(iac, fcDef.name, fcDef.code.handler, fcDef.memory, fcDef.timeout, requestId);
61
+ env = {
62
+ ...fcDef.environment,
63
+ };
64
+ }
65
+ else {
66
+ // AWS Lambda format (default)
67
+ const rawBody = await (0, common_1.readRequestBody)(req);
68
+ event = rawBody ? JSON.parse(rawBody) : {};
69
+ env = {
70
+ ...fcDef.environment,
71
+ AWS_REGION: iac.provider.region || 'us-east-1',
72
+ FUNCTION_NAME: fcDef.name,
73
+ FUNCTION_MEMORY_SIZE: String(fcDef.memory),
74
+ FUNCTION_TIMEOUT: String(fcDef.timeout),
75
+ };
76
+ fcContext = {
77
+ functionName: fcDef.name,
78
+ functionVersion: '$LATEST',
79
+ memoryLimitInMB: fcDef.memory,
80
+ logGroupName: `/aws/lambda/${fcDef.name}`,
81
+ logStreamName: `${new Date().toISOString().split('T')[0]}/[$LATEST]${Math.random().toString(36).substring(7)}`,
82
+ invokedFunctionArn: `arn:aws:lambda:${iac.provider.region}:000000000000:function:${fcDef.name}`,
83
+ awsRequestId: Math.random().toString(36).substring(2, 15),
84
+ };
85
+ }
86
+ common_1.logger.debug(`Invoking worker with event type: ${isAliyun ? 'Buffer' : 'Object'} and context`);
87
+ common_1.logger.debug(`Worker codeDir: ${codeDir}, handler: ${funOptions.handler}`);
88
+ const result = await (0, functionRunner_1.invokeFunction)(funOptions, env, event, fcContext);
89
+ common_1.logger.info(`Function execution result: ${JSON.stringify(result)}`);
90
+ // For Aliyun, transform FC response to HTTP response if needed
91
+ if (isAliyun && result) {
92
+ const transformed = (0, aliyunFc_1.transformFCResponse)(result);
93
+ return {
94
+ statusCode: transformed.statusCode,
95
+ headers: transformed.headers,
96
+ body: transformed.body,
97
+ };
98
+ }
99
+ return {
100
+ statusCode: 200,
101
+ body: result,
102
+ };
103
+ }
104
+ catch (error) {
105
+ common_1.logger.error(`Function execution error: ${error}`);
106
+ return {
107
+ statusCode: 500,
108
+ body: {
109
+ error: 'Function execution failed',
110
+ message: error instanceof Error ? error.message : String(error),
111
+ },
112
+ };
113
+ }
114
+ finally {
115
+ if (tempDir && node_fs_1.default.existsSync(tempDir)) {
116
+ node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
117
+ }
118
+ }
119
+ };
120
+ exports.functionsHandler = functionsHandler;