@gugananuvem/aws-local-simulator 1.0.28 → 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':
|
|
@@ -77,8 +77,14 @@ class CognitoSimulator {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
try {
|
|
81
|
+
const result = await this.lambdaSimulator.invoke(fnName, event, "RequestResponse");
|
|
82
|
+
return result.Payload;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const wrappedError = new Error(error.message);
|
|
85
|
+
wrappedError.code = "UserLambdaValidationException";
|
|
86
|
+
throw wrappedError;
|
|
87
|
+
}
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
async initialize() {
|
|
@@ -1278,6 +1284,28 @@ class CognitoSimulator {
|
|
|
1278
1284
|
return password.sort(() => Math.random() - 0.5).join("");
|
|
1279
1285
|
}
|
|
1280
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
|
+
|
|
1281
1309
|
adminSetUserPassword(params) {
|
|
1282
1310
|
const { UserPoolId, Username, Password, Permanent } = params;
|
|
1283
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,11 +109,17 @@ 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;
|
|
117
|
+
try {
|
|
118
|
+
result = await this.executeHandler(lambda, functionName, event);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logger.error(`❌ Lambda handler error (${functionName}):`, error);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
116
123
|
this.audit.record({
|
|
117
124
|
eventName: "Invoke",
|
|
118
125
|
readOnly: false,
|
|
@@ -122,29 +129,63 @@ class LambdaSimulator {
|
|
|
122
129
|
return { StatusCode: result.statusCode || 200, Payload: result };
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
async executeHandler(
|
|
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;
|
|
126
156
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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;
|
|
136
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;
|
|
176
|
+
return result;
|
|
137
177
|
}
|
|
138
178
|
|
|
139
|
-
createContext() {
|
|
179
|
+
createContext(functionName = "local-lambda", requestId = null) {
|
|
180
|
+
const reqId = requestId || Math.random().toString(36).substring(2, 18);
|
|
140
181
|
return {
|
|
141
|
-
awsRequestId:
|
|
142
|
-
functionName
|
|
182
|
+
awsRequestId: reqId,
|
|
183
|
+
functionName,
|
|
143
184
|
functionVersion: "$LATEST",
|
|
144
|
-
invokedFunctionArn:
|
|
185
|
+
invokedFunctionArn: `arn:aws:lambda:local:000000000000:function:${functionName}`,
|
|
145
186
|
memoryLimitInMB: "1024",
|
|
146
|
-
logGroupName:
|
|
147
|
-
logStreamName:
|
|
187
|
+
logGroupName: `/aws/lambda/${functionName}`,
|
|
188
|
+
logStreamName: `${new Date().toISOString().slice(0, 10).replace(/-/g, '/')}/${reqId.slice(0, 8)}`,
|
|
148
189
|
getRemainingTimeInMillis: () => 30000,
|
|
149
190
|
callbackWaitsForEmptyEventLoop: true,
|
|
150
191
|
identity: null,
|