@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.29",
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-04-30T19:05:54.245Z",
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
- const event = req.body || {};
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.handler, event).catch((err) => logger.error(`❌ Async Lambda error (${functionName}):`, err));
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.handler, event);
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(handler, event) {
132
- const context = this.createContext();
133
- const result = await handler(event, context);
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: Math.random().toString(36).substring(7),
140
- functionName: "local-lambda",
182
+ awsRequestId: reqId,
183
+ functionName,
141
184
  functionVersion: "$LATEST",
142
- invokedFunctionArn: "arn:aws:lambda:local:000000000000:function:local-lambda",
185
+ invokedFunctionArn: `arn:aws:lambda:local:000000000000:function:${functionName}`,
143
186
  memoryLimitInMB: "1024",
144
- logGroupName: "/aws/lambda/local-lambda",
145
- logStreamName: "local-stream",
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,