@gugananuvem/aws-local-simulator 1.0.11 → 1.0.12
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/README.md +122 -69
- package/package.json +2 -2
- package/src/config/config-loader.js +2 -0
- package/src/config/default-config.js +2 -0
- package/src/server.js +3 -1
- package/src/services/apigateway/index.js +5 -3
- package/src/services/apigateway/server.js +53 -0
- package/src/services/cognito/server.js +54 -3
- package/src/services/cognito/simulator.js +269 -2
- package/src/services/lambda/handler-loader.js +13 -2
- package/src/services/lambda/index.js +2 -1
- package/src/services/lambda/server.js +32 -39
- package/src/services/lambda/simulator.js +44 -157
- package/src/services/sqs/server.js +88 -16
- package/src/services/sqs/simulator.js +79 -298
- package/src/services/sts/index.js +37 -0
- package/src/services/sts/server.js +142 -0
- package/src/services/sts/simulator.js +69 -0
|
@@ -140,6 +140,215 @@ class CognitoSimulator {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
listUsers(params = {}) {
|
|
144
|
+
const { UserPoolId, Filter, Limit = 60, PaginationToken } = params;
|
|
145
|
+
const userPool = this.userPools.get(UserPoolId);
|
|
146
|
+
|
|
147
|
+
if (!userPool) {
|
|
148
|
+
throw new Error(`User pool ${UserPoolId} not found`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let users = Array.from(this.users.values()).filter(u => u.UserPoolId === UserPoolId);
|
|
152
|
+
|
|
153
|
+
if (PaginationToken) {
|
|
154
|
+
const startIndex = parseInt(PaginationToken);
|
|
155
|
+
users = users.slice(startIndex);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const results = users.slice(0, Limit);
|
|
159
|
+
const nextToken = results.length === Limit && users.length > Limit ? String(Limit) : null;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
Users: results.map(u => ({
|
|
163
|
+
Username: u.Username,
|
|
164
|
+
UserStatus: u.UserStatus,
|
|
165
|
+
Enabled: u.Enabled,
|
|
166
|
+
UserCreateDate: u.CreatedDate,
|
|
167
|
+
UserLastModifiedDate: u.LastModifiedDate,
|
|
168
|
+
Attributes: this.formatUserAttributes(u.Attributes)
|
|
169
|
+
})),
|
|
170
|
+
PaginationToken: nextToken
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
listUserPoolClients(params = {}) {
|
|
175
|
+
const { UserPoolId, MaxResults = 60, NextToken } = params;
|
|
176
|
+
const userPool = this.userPools.get(UserPoolId);
|
|
177
|
+
if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
|
|
178
|
+
|
|
179
|
+
let clients = Array.from(userPool.Clients.values());
|
|
180
|
+
if (NextToken) clients = clients.slice(parseInt(NextToken));
|
|
181
|
+
const results = clients.slice(0, MaxResults);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
UserPoolClients: results.map(c => ({
|
|
185
|
+
ClientId: c.ClientId,
|
|
186
|
+
ClientName: c.ClientName,
|
|
187
|
+
UserPoolId: c.UserPoolId
|
|
188
|
+
})),
|
|
189
|
+
NextToken: results.length === MaxResults && clients.length > MaxResults ? String(MaxResults) : null
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
describeUserPoolClient(params = {}) {
|
|
194
|
+
const { UserPoolId, ClientId } = params;
|
|
195
|
+
const userPool = this.userPools.get(UserPoolId);
|
|
196
|
+
if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
|
|
197
|
+
const client = userPool.Clients.get(ClientId);
|
|
198
|
+
if (!client) throw new Error(`Client ${ClientId} not found`);
|
|
199
|
+
return { UserPoolClient: client };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
deleteUserPoolClient(params = {}) {
|
|
203
|
+
const { UserPoolId, ClientId } = params;
|
|
204
|
+
const userPool = this.userPools.get(UserPoolId);
|
|
205
|
+
if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
|
|
206
|
+
userPool.Clients.delete(ClientId);
|
|
207
|
+
this.persistUserPools();
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
forgotPassword(params = {}) {
|
|
212
|
+
const { ClientId, Username } = params;
|
|
213
|
+
const userPool = this.findUserPoolByClientId(ClientId);
|
|
214
|
+
if (!userPool) throw new Error(`Client ${ClientId} not found`);
|
|
215
|
+
return { CodeDeliveryDetails: { Destination: 'test@example.com', DeliveryMedium: 'EMAIL', AttributeName: 'email' } };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
confirmForgotPassword(params = {}) {
|
|
219
|
+
const { ClientId, Username, ConfirmationCode, Password } = params;
|
|
220
|
+
const user = this.findUserByUsername(Username, ClientId);
|
|
221
|
+
if (!user) throw new Error(`User not found: ${Username}`);
|
|
222
|
+
user.Password = this.hashPassword(Password);
|
|
223
|
+
user.UserStatus = 'CONFIRMED';
|
|
224
|
+
this.persistUsers();
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
changePassword(params = {}) {
|
|
229
|
+
const { AccessToken, PreviousPassword, ProposedPassword } = params;
|
|
230
|
+
const session = this.accessTokens.get(AccessToken);
|
|
231
|
+
if (!session) throw new Error('Invalid access token');
|
|
232
|
+
const user = this.users.get(session.UserId);
|
|
233
|
+
if (!user) throw new Error('User not found');
|
|
234
|
+
if (!this.verifyPassword(PreviousPassword, user.Password)) throw new Error('Incorrect previous password');
|
|
235
|
+
user.Password = this.hashPassword(ProposedPassword);
|
|
236
|
+
this.persistUsers();
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
respondToAuthChallenge(params = {}) {
|
|
241
|
+
return { AuthenticationResult: null, ChallengeName: null };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
revokeToken(params = {}) {
|
|
245
|
+
const { Token, ClientId } = params;
|
|
246
|
+
// Remove refresh token session if it exists
|
|
247
|
+
const session = this.refreshTokens.get(Token);
|
|
248
|
+
if (session) {
|
|
249
|
+
this.sessions.delete(session.Id);
|
|
250
|
+
this.accessTokens.delete(session.AccessToken);
|
|
251
|
+
this.refreshTokens.delete(Token);
|
|
252
|
+
this.persistSessions();
|
|
253
|
+
}
|
|
254
|
+
return {};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
globalSignOut(params = {}) {
|
|
258
|
+
const { AccessToken } = params;
|
|
259
|
+
const session = this.accessTokens.get(AccessToken);
|
|
260
|
+
if (session) {
|
|
261
|
+
this.sessions.delete(session.Id);
|
|
262
|
+
this.accessTokens.delete(AccessToken);
|
|
263
|
+
this.refreshTokens.delete(session.RefreshToken);
|
|
264
|
+
this.persistSessions();
|
|
265
|
+
}
|
|
266
|
+
return {};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
getUser(params = {}) {
|
|
270
|
+
const { AccessToken } = params;
|
|
271
|
+
const session = this.accessTokens.get(AccessToken);
|
|
272
|
+
if (!session) throw new Error('Invalid access token');
|
|
273
|
+
// Check session is still active
|
|
274
|
+
if (!this.sessions.has(session.Id)) throw new Error('Token has been revoked');
|
|
275
|
+
const user = this.users.get(session.UserId);
|
|
276
|
+
if (!user) throw new Error('User not found');
|
|
277
|
+
return {
|
|
278
|
+
Username: user.Username,
|
|
279
|
+
UserAttributes: this.formatUserAttributes(user.Attributes),
|
|
280
|
+
UserStatus: user.UserStatus
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
updateUserAttributes(params = {}) {
|
|
285
|
+
const { AccessToken, UserAttributes } = params;
|
|
286
|
+
const session = this.accessTokens.get(AccessToken);
|
|
287
|
+
if (!session) throw new Error('Invalid access token');
|
|
288
|
+
const user = this.users.get(session.UserId);
|
|
289
|
+
if (!user) throw new Error('User not found');
|
|
290
|
+
const updates = this.normalizeUserAttributes(UserAttributes || []);
|
|
291
|
+
Object.assign(user.Attributes, updates);
|
|
292
|
+
this.persistUsers();
|
|
293
|
+
return { CodeDeliveryDetailsList: [] };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
deleteUser(params = {}) {
|
|
297
|
+
const { AccessToken } = params;
|
|
298
|
+
const session = this.accessTokens.get(AccessToken);
|
|
299
|
+
if (!session) throw new Error('Invalid access token');
|
|
300
|
+
this.users.delete(session.UserId);
|
|
301
|
+
this.persistUsers();
|
|
302
|
+
return {};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
adminDisableUser(params = {}) {
|
|
306
|
+
const { UserPoolId, Username } = params;
|
|
307
|
+
const user = this.findUserByUsername(Username, null, UserPoolId);
|
|
308
|
+
if (!user) throw new Error(`User not found: ${Username}`);
|
|
309
|
+
user.Enabled = false;
|
|
310
|
+
this.persistUsers();
|
|
311
|
+
return {};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
adminEnableUser(params = {}) {
|
|
315
|
+
const { UserPoolId, Username } = params;
|
|
316
|
+
const user = this.findUserByUsername(Username, null, UserPoolId);
|
|
317
|
+
if (!user) throw new Error(`User not found: ${Username}`);
|
|
318
|
+
user.Enabled = true;
|
|
319
|
+
this.persistUsers();
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
adminResetUserPassword(params = {}) {
|
|
324
|
+
const { UserPoolId, Username } = params;
|
|
325
|
+
const user = this.findUserByUsername(Username, null, UserPoolId);
|
|
326
|
+
if (!user) throw new Error(`User not found: ${Username}`);
|
|
327
|
+
user.UserStatus = 'RESET_REQUIRED';
|
|
328
|
+
this.persistUsers();
|
|
329
|
+
return {};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
adminUserGlobalSignOut(params = {}) {
|
|
333
|
+
const { UserPoolId, Username } = params;
|
|
334
|
+
const user = this.findUserByUsername(Username, null, UserPoolId);
|
|
335
|
+
if (!user) throw new Error(`User not found: ${Username}`);
|
|
336
|
+
// Invalidate all sessions for this user
|
|
337
|
+
for (const [id, session] of this.sessions.entries()) {
|
|
338
|
+
if (session.UserId === user.UserId) {
|
|
339
|
+
this.accessTokens.delete(session.AccessToken);
|
|
340
|
+
this.refreshTokens.delete(session.RefreshToken);
|
|
341
|
+
this.sessions.delete(id);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
this.persistSessions();
|
|
345
|
+
return {};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
adminListGroupsForUser(params = {}) {
|
|
349
|
+
return { Groups: [], NextToken: null };
|
|
350
|
+
}
|
|
351
|
+
|
|
143
352
|
deleteUserPool(params) {
|
|
144
353
|
const { UserPoolId } = params;
|
|
145
354
|
|
|
@@ -207,7 +416,7 @@ class CognitoSimulator {
|
|
|
207
416
|
|
|
208
417
|
// ============ User Operations ============
|
|
209
418
|
|
|
210
|
-
signUp(params) {
|
|
419
|
+
signUp(params = {}) {
|
|
211
420
|
const { ClientId, Username, Password, UserAttributes, ValidationData } = params;
|
|
212
421
|
|
|
213
422
|
// Encontra o user pool pelo client id
|
|
@@ -726,10 +935,10 @@ class CognitoSimulator {
|
|
|
726
935
|
// ============ Persistence ============
|
|
727
936
|
|
|
728
937
|
loadUserPools() {
|
|
938
|
+
// Load persisted pools first
|
|
729
939
|
const saved = this.store.read('__userpools__');
|
|
730
940
|
if (saved) {
|
|
731
941
|
for (const [id, data] of Object.entries(saved)) {
|
|
732
|
-
// Reconstitui Maps
|
|
733
942
|
data.Clients = new Map(Object.entries(data.Clients || {}));
|
|
734
943
|
data.Groups = new Map(Object.entries(data.Groups || {}));
|
|
735
944
|
data.IdentityProviders = new Map(Object.entries(data.IdentityProviders || {}));
|
|
@@ -737,6 +946,64 @@ class CognitoSimulator {
|
|
|
737
946
|
this.userPools.set(id, data);
|
|
738
947
|
}
|
|
739
948
|
}
|
|
949
|
+
|
|
950
|
+
// Create user pools from config if not already persisted
|
|
951
|
+
if (this.config.cognito?.userPools) {
|
|
952
|
+
let configChanged = false;
|
|
953
|
+
const configPath = this.config._configPath;
|
|
954
|
+
|
|
955
|
+
for (let i = 0; i < this.config.cognito.userPools.length; i++) {
|
|
956
|
+
const poolConfig = this.config.cognito.userPools[i];
|
|
957
|
+
const existing = Array.from(this.userPools.values()).find(p => p.Name === poolConfig.PoolName);
|
|
958
|
+
|
|
959
|
+
if (!existing) {
|
|
960
|
+
const result = this.createUserPool(poolConfig);
|
|
961
|
+
const poolId = result.UserPool.Id;
|
|
962
|
+
logger.debug(`✅ User Pool criado a partir da config: ${poolConfig.PoolName} (${poolId})`);
|
|
963
|
+
|
|
964
|
+
// Auto-create a default client if not specified
|
|
965
|
+
if (!poolConfig.ClientId) {
|
|
966
|
+
const clientResult = this.createUserPoolClient({
|
|
967
|
+
UserPoolId: poolId,
|
|
968
|
+
ClientName: `${poolConfig.PoolName}-client`,
|
|
969
|
+
GenerateSecret: false
|
|
970
|
+
});
|
|
971
|
+
const clientId = clientResult.UserPoolClient.ClientId;
|
|
972
|
+
this.config.cognito.userPools[i].ClientId = clientId;
|
|
973
|
+
this.config.cognito.userPools[i].UserPoolId = poolId;
|
|
974
|
+
configChanged = true;
|
|
975
|
+
logger.debug(`✅ Client criado automaticamente: ${clientId}`);
|
|
976
|
+
}
|
|
977
|
+
} else if (!poolConfig.ClientId) {
|
|
978
|
+
// Pool exists but no clientId in config — write it back
|
|
979
|
+
const firstClient = existing.Clients.size > 0 ? existing.Clients.values().next().value : null;
|
|
980
|
+
if (firstClient) {
|
|
981
|
+
this.config.cognito.userPools[i].ClientId = firstClient.ClientId;
|
|
982
|
+
this.config.cognito.userPools[i].UserPoolId = existing.Id;
|
|
983
|
+
configChanged = true;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Write clientId back to aws-local-simulator.json
|
|
989
|
+
if (configChanged && configPath) {
|
|
990
|
+
try {
|
|
991
|
+
const fs = require('fs');
|
|
992
|
+
const fileContent = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
993
|
+
fileContent.cognito = fileContent.cognito || {};
|
|
994
|
+
fileContent.cognito.userPools = this.config.cognito.userPools.map(p => ({
|
|
995
|
+
PoolName: p.PoolName,
|
|
996
|
+
AutoVerifiedAttributes: p.AutoVerifiedAttributes,
|
|
997
|
+
UserPoolId: p.UserPoolId,
|
|
998
|
+
ClientId: p.ClientId
|
|
999
|
+
}));
|
|
1000
|
+
fs.writeFileSync(configPath, JSON.stringify(fileContent, null, 2));
|
|
1001
|
+
logger.info(`✅ ClientId gravado em: ${configPath}`);
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
logger.warn(`⚠️ Não foi possível gravar clientId no config: ${err.message}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
740
1007
|
}
|
|
741
1008
|
|
|
742
1009
|
loadIdentityPools() {
|
|
@@ -16,8 +16,19 @@ class HandlerLoader {
|
|
|
16
16
|
* @returns {Promise<Function>} - Função handler
|
|
17
17
|
*/
|
|
18
18
|
static async load(handlerPath, type = 'auto') {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Resolve path: try cwd first, then data dir
|
|
20
|
+
let fullPath = path.resolve(process.cwd(), handlerPath);
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(fullPath)) {
|
|
23
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
|
|
24
|
+
if (dataDir) {
|
|
25
|
+
const dataPath = path.resolve(dataDir, 'lambda', handlerPath.replace(/^\.\//, ''));
|
|
26
|
+
if (fs.existsSync(dataPath)) {
|
|
27
|
+
fullPath = dataPath;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
if (!fs.existsSync(fullPath)) {
|
|
22
33
|
throw new Error(`Handler não encontrado: ${fullPath}`);
|
|
23
34
|
}
|
|
@@ -22,7 +22,8 @@ class LambdaService {
|
|
|
22
22
|
|
|
23
23
|
// Cria o simulador
|
|
24
24
|
this.simulator = new LambdaSimulator(this.config);
|
|
25
|
-
|
|
25
|
+
await this.simulator.initialize();
|
|
26
|
+
|
|
26
27
|
// Cria o servidor HTTP
|
|
27
28
|
this.server = new LambdaServer(this.port, this.config);
|
|
28
29
|
this.server.simulator = this.simulator;
|
|
@@ -44,53 +44,48 @@ class LambdaServer {
|
|
|
44
44
|
setupRoutes() {
|
|
45
45
|
// Health check
|
|
46
46
|
this.app.get('/health', (req, res) => {
|
|
47
|
-
res.json({
|
|
48
|
-
status: 'healthy',
|
|
49
|
-
version: require('../../../package.json').version,
|
|
50
|
-
lambdas: this.simulator.getLambdasCount(),
|
|
51
|
-
routes: this.simulator.listRoutes()
|
|
52
|
-
});
|
|
47
|
+
res.json({ status: 'healthy', lambdas: this.simulator.getLambdasCount() });
|
|
53
48
|
});
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
this.app.
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
|
|
50
|
+
// AWS Lambda Invoke API: POST /2015-03-31/functions/{functionName}/invocations
|
|
51
|
+
this.app.post('/2015-03-31/functions/:functionName/invocations', async (req, res) => {
|
|
52
|
+
const { functionName } = req.params;
|
|
53
|
+
const invocationType = req.headers['x-amz-invocation-type'] || 'RequestResponse';
|
|
54
|
+
const event = req.body || {};
|
|
55
|
+
|
|
56
|
+
logger.debug(`Lambda invoke: ${functionName} (${invocationType})`);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = await this.simulator.invoke(functionName, event, invocationType);
|
|
60
|
+
|
|
61
|
+
if (invocationType === 'Event') {
|
|
62
|
+
return res.status(202).send();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
res.status(result.StatusCode || 200).json(result.Payload);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.message && err.message.includes('Function not found')) {
|
|
68
|
+
return res.status(404).json({ __type: 'ResourceNotFoundException', message: err.message });
|
|
69
|
+
}
|
|
70
|
+
logger.error('Lambda invoke error:', err);
|
|
71
|
+
res.status(500).json({ __type: 'ServiceException', message: err.message });
|
|
61
72
|
}
|
|
62
73
|
});
|
|
63
|
-
|
|
74
|
+
|
|
64
75
|
// Admin endpoints
|
|
65
76
|
this.setupAdminRoutes();
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
setupAdminRoutes() {
|
|
69
|
-
|
|
70
|
-
this.app.get('/__admin/lambdas', (req, res) => {
|
|
80
|
+
this.app.get('/__admin/functions', (req, res) => {
|
|
71
81
|
res.json(this.simulator.listLambdas());
|
|
72
82
|
});
|
|
73
|
-
|
|
74
|
-
// Detalhes de uma Lambda
|
|
75
|
-
this.app.get('/__admin/lambdas/:path', (req, res) => {
|
|
76
|
-
const lambda = this.simulator.getLambda(req.params.path);
|
|
77
|
-
if (lambda) {
|
|
78
|
-
res.json(lambda);
|
|
79
|
-
} else {
|
|
80
|
-
res.status(404).json({ error: 'Lambda not found' });
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// Recarregar Lambdas
|
|
83
|
+
|
|
85
84
|
this.app.post('/__admin/reload', async (req, res) => {
|
|
86
85
|
await this.simulator.reloadLambdas();
|
|
87
|
-
res.json({
|
|
88
|
-
message: 'Lambdas recarregadas',
|
|
89
|
-
count: this.simulator.getLambdasCount()
|
|
90
|
-
});
|
|
86
|
+
res.json({ message: 'Lambdas recarregadas', count: this.simulator.getLambdasCount() });
|
|
91
87
|
});
|
|
92
|
-
|
|
93
|
-
// Injetar variável de ambiente
|
|
88
|
+
|
|
94
89
|
this.app.post('/__admin/env', (req, res) => {
|
|
95
90
|
const { key, value } = req.body;
|
|
96
91
|
if (key && value !== undefined) {
|
|
@@ -100,13 +95,11 @@ class LambdaServer {
|
|
|
100
95
|
res.status(400).json({ error: 'Missing key or value' });
|
|
101
96
|
}
|
|
102
97
|
});
|
|
103
|
-
|
|
104
|
-
// Listar variáveis de ambiente
|
|
98
|
+
|
|
105
99
|
this.app.get('/__admin/env', (req, res) => {
|
|
106
100
|
res.json(this.simulator.getEnvironmentVariables());
|
|
107
101
|
});
|
|
108
|
-
|
|
109
|
-
// Estatísticas
|
|
102
|
+
|
|
110
103
|
this.app.get('/__admin/stats', (req, res) => {
|
|
111
104
|
res.json(this.simulator.getStats());
|
|
112
105
|
});
|
|
@@ -126,7 +119,7 @@ class LambdaServer {
|
|
|
126
119
|
logger.info('\n📚 Lambdas registradas:');
|
|
127
120
|
const lambdas = this.simulator.listLambdas();
|
|
128
121
|
for (const lambda of lambdas) {
|
|
129
|
-
logger.info(` ${lambda.
|
|
122
|
+
logger.info(` ${lambda.name.padEnd(30)} -> ${lambda.handlerName || 'anonymous'}`);
|
|
130
123
|
}
|
|
131
124
|
}
|
|
132
125
|
|