@gugananuvem/aws-local-simulator 1.0.29 → 1.0.30
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gugananuvem/aws-local-simulator",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"description": "Simulador local completo para serviços AWS",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"publishConfig": {
|
|
67
67
|
"directory": "dist"
|
|
68
68
|
},
|
|
69
|
-
"buildDate": "2026-
|
|
69
|
+
"buildDate": "2026-05-01T13:34:46.765Z",
|
|
70
70
|
"published": true
|
|
71
71
|
}
|
|
@@ -164,6 +164,8 @@ class CognitoServer {
|
|
|
164
164
|
return this.simulator.adminListGroupsForUser(params);
|
|
165
165
|
case 'AdminUserGlobalSignOut':
|
|
166
166
|
return this.simulator.adminUserGlobalSignOut(params);
|
|
167
|
+
case 'AdminUpdateUserAttributes':
|
|
168
|
+
return this.simulator.adminUpdateUserAttributes(params);
|
|
167
169
|
|
|
168
170
|
// Identity Pool Operations
|
|
169
171
|
case 'CreateIdentityPool':
|
|
@@ -1284,6 +1284,28 @@ class CognitoSimulator {
|
|
|
1284
1284
|
return password.sort(() => Math.random() - 0.5).join("");
|
|
1285
1285
|
}
|
|
1286
1286
|
|
|
1287
|
+
adminUpdateUserAttributes(params) {
|
|
1288
|
+
const { UserPoolId, Username, UserAttributes } = params;
|
|
1289
|
+
const userPool = this.userPools.get(UserPoolId);
|
|
1290
|
+
|
|
1291
|
+
if (!userPool) {
|
|
1292
|
+
throw new Error(`User pool ${UserPoolId} not found`);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const user = this.findUserByUsername(Username, null, UserPoolId);
|
|
1296
|
+
if (!user) {
|
|
1297
|
+
throw new Error(`User not found: ${Username}`);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const updates = this.normalizeUserAttributes(UserAttributes || []);
|
|
1301
|
+
Object.assign(user.Attributes, updates);
|
|
1302
|
+
user.LastModifiedDate = Math.floor(Date.now() / 1000);
|
|
1303
|
+
this.persistUsers();
|
|
1304
|
+
|
|
1305
|
+
logger.debug(`✅ AdminUpdateUserAttributes: ${Username} in ${UserPoolId}`);
|
|
1306
|
+
return {};
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1287
1309
|
adminSetUserPassword(params) {
|
|
1288
1310
|
const { UserPoolId, Username, Password, Permanent } = params;
|
|
1289
1311
|
const user = this.findUserByUsername(Username, null, UserPoolId);
|
|
@@ -36,6 +36,9 @@ class LambdaService {
|
|
|
36
36
|
injectDependencies(server) {
|
|
37
37
|
const ct = server.getService('cloudtrail');
|
|
38
38
|
if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
|
|
39
|
+
|
|
40
|
+
const cw = server.getService('cloudwatch');
|
|
41
|
+
if (cw?.simulator) this.simulator.cloudwatchSimulator = cw.simulator;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
async start() {
|
|
@@ -18,7 +18,6 @@ class LambdaServer {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
setupMiddlewares() {
|
|
21
|
-
this.app.use(express.json({ limit: '10mb' }));
|
|
22
21
|
this.app.use(express.urlencoded({ extended: true }));
|
|
23
22
|
this.app.use(cors());
|
|
24
23
|
|
|
@@ -51,7 +50,22 @@ class LambdaServer {
|
|
|
51
50
|
this.app.post('/2015-03-31/functions/:functionName/invocations', async (req, res) => {
|
|
52
51
|
const { functionName } = req.params;
|
|
53
52
|
const invocationType = req.headers['x-amz-invocation-type'] || 'RequestResponse';
|
|
54
|
-
|
|
53
|
+
|
|
54
|
+
// Read body directly from stream, bypassing all body parsers
|
|
55
|
+
let event = {};
|
|
56
|
+
try {
|
|
57
|
+
const rawBody = await new Promise((resolve, reject) => {
|
|
58
|
+
const chunks = [];
|
|
59
|
+
req.on('data', chunk => chunks.push(chunk));
|
|
60
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
61
|
+
req.on('error', reject);
|
|
62
|
+
});
|
|
63
|
+
if (rawBody.length > 0) {
|
|
64
|
+
event = JSON.parse(rawBody.toString('utf8'));
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
event = {};
|
|
68
|
+
}
|
|
55
69
|
|
|
56
70
|
logger.debug(`Lambda invoke: ${functionName} (${invocationType})`);
|
|
57
71
|
|
|
@@ -77,11 +91,19 @@ class LambdaServer {
|
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
setupAdminRoutes() {
|
|
94
|
+
// Helper: parse Buffer body as JSON for admin routes
|
|
95
|
+
const parseJson = (req, res, next) => {
|
|
96
|
+
if (Buffer.isBuffer(req.body) && req.body.length > 0) {
|
|
97
|
+
try { req.body = JSON.parse(req.body.toString('utf8')); } catch { req.body = {}; }
|
|
98
|
+
}
|
|
99
|
+
next();
|
|
100
|
+
};
|
|
101
|
+
|
|
80
102
|
this.app.get('/__admin/functions', (req, res) => {
|
|
81
103
|
res.json(this.simulator.listLambdas());
|
|
82
104
|
});
|
|
83
105
|
|
|
84
|
-
this.app.post('/__admin/functions', async (req, res) => {
|
|
106
|
+
this.app.post('/__admin/functions', parseJson, async (req, res) => {
|
|
85
107
|
try {
|
|
86
108
|
const lambda = await this.simulator.createFunction(req.body);
|
|
87
109
|
res.status(201).json(lambda);
|
|
@@ -90,7 +112,7 @@ class LambdaServer {
|
|
|
90
112
|
}
|
|
91
113
|
});
|
|
92
114
|
|
|
93
|
-
this.app.put('/__admin/functions/:name', async (req, res) => {
|
|
115
|
+
this.app.put('/__admin/functions/:name', parseJson, async (req, res) => {
|
|
94
116
|
try {
|
|
95
117
|
const lambda = await this.simulator.updateFunction(req.params.name, req.body);
|
|
96
118
|
res.json(lambda);
|
|
@@ -110,7 +132,7 @@ class LambdaServer {
|
|
|
110
132
|
res.json({ message: 'Lambdas recarregadas', count: this.simulator.getLambdasCount() });
|
|
111
133
|
});
|
|
112
134
|
|
|
113
|
-
this.app.post('/__admin/env', (req, res) => {
|
|
135
|
+
this.app.post('/__admin/env', parseJson, (req, res) => {
|
|
114
136
|
const { key, value } = req.body;
|
|
115
137
|
if (key && value !== undefined) {
|
|
116
138
|
this.simulator.setEnvironmentVariable(key, value);
|
|
@@ -13,6 +13,7 @@ class LambdaSimulator {
|
|
|
13
13
|
this.lambdas = new Map(); // functionName -> { handler, env, config }
|
|
14
14
|
this.environment = { ...process.env };
|
|
15
15
|
this.audit = new CloudTrailAudit("lambda.amazonaws.com");
|
|
16
|
+
this.cloudwatchSimulator = null; // injected via injectDependencies
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
async initialize() {
|
|
@@ -108,13 +109,13 @@ class LambdaSimulator {
|
|
|
108
109
|
logger.debug(`🎯 Invocando Lambda: ${functionName}`);
|
|
109
110
|
|
|
110
111
|
if (invocationType === "Event") {
|
|
111
|
-
this.executeHandler(lambda
|
|
112
|
+
this.executeHandler(lambda, functionName, event).catch((err) => logger.error(`❌ Async Lambda error (${functionName}):`, err));
|
|
112
113
|
return { StatusCode: 202 };
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
let result;
|
|
116
117
|
try {
|
|
117
|
-
result = await this.executeHandler(lambda
|
|
118
|
+
result = await this.executeHandler(lambda, functionName, event);
|
|
118
119
|
} catch (error) {
|
|
119
120
|
logger.error(`❌ Lambda handler error (${functionName}):`, error);
|
|
120
121
|
throw error;
|
|
@@ -128,21 +129,63 @@ class LambdaSimulator {
|
|
|
128
129
|
return { StatusCode: result.statusCode || 200, Payload: result };
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
async executeHandler(
|
|
132
|
-
const
|
|
133
|
-
const
|
|
132
|
+
async executeHandler(lambda, functionName, event) {
|
|
133
|
+
const requestId = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
|
|
134
|
+
const capturedLogs = [];
|
|
135
|
+
|
|
136
|
+
const context = this.createContext(functionName, requestId);
|
|
137
|
+
|
|
138
|
+
// Intercept console output during handler execution
|
|
139
|
+
const origLog = console.log;
|
|
140
|
+
const origError = console.error;
|
|
141
|
+
const origWarn = console.warn;
|
|
142
|
+
const origInfo = console.info;
|
|
143
|
+
|
|
144
|
+
const capture = (...args) => {
|
|
145
|
+
const line = args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
|
|
146
|
+
capturedLogs.push(line);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
console.log = (...args) => { capture(...args); origLog(...args); };
|
|
150
|
+
console.error = (...args) => { capture(`[ERROR] ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`); origError(...args); };
|
|
151
|
+
console.warn = (...args) => { capture(`[WARN] ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`); origWarn(...args); };
|
|
152
|
+
console.info = (...args) => { capture(`[INFO] ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`); origInfo(...args); };
|
|
153
|
+
|
|
154
|
+
let result;
|
|
155
|
+
let execError;
|
|
156
|
+
try {
|
|
157
|
+
result = await lambda.handler(event, context);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
execError = err;
|
|
160
|
+
capturedLogs.push(`[ERROR] ${err.message}`);
|
|
161
|
+
} finally {
|
|
162
|
+
console.log = origLog;
|
|
163
|
+
console.error = origError;
|
|
164
|
+
console.warn = origWarn;
|
|
165
|
+
console.info = origInfo;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Send logs to CloudWatch asynchronously (non-blocking)
|
|
169
|
+
if (this.cloudwatchSimulator) {
|
|
170
|
+
this.cloudwatchSimulator
|
|
171
|
+
.putLambdaLogs(functionName, requestId, capturedLogs)
|
|
172
|
+
.catch((err) => logger.debug(`[CloudWatch] Failed to store Lambda logs: ${err.message}`));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (execError) throw execError;
|
|
134
176
|
return result;
|
|
135
177
|
}
|
|
136
178
|
|
|
137
|
-
createContext() {
|
|
179
|
+
createContext(functionName = "local-lambda", requestId = null) {
|
|
180
|
+
const reqId = requestId || Math.random().toString(36).substring(2, 18);
|
|
138
181
|
return {
|
|
139
|
-
awsRequestId:
|
|
140
|
-
functionName
|
|
182
|
+
awsRequestId: reqId,
|
|
183
|
+
functionName,
|
|
141
184
|
functionVersion: "$LATEST",
|
|
142
|
-
invokedFunctionArn:
|
|
185
|
+
invokedFunctionArn: `arn:aws:lambda:local:000000000000:function:${functionName}`,
|
|
143
186
|
memoryLimitInMB: "1024",
|
|
144
|
-
logGroupName:
|
|
145
|
-
logStreamName:
|
|
187
|
+
logGroupName: `/aws/lambda/${functionName}`,
|
|
188
|
+
logStreamName: `${new Date().toISOString().slice(0, 10).replace(/-/g, '/')}/${reqId.slice(0, 8)}`,
|
|
146
189
|
getRemainingTimeInMillis: () => 30000,
|
|
147
190
|
callbackWaitsForEmptyEventLoop: true,
|
|
148
191
|
identity: null,
|