@gugananuvem/aws-local-simulator 1.0.25 → 1.0.26

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 CHANGED
@@ -186,6 +186,50 @@ npx aws-local-simulator reset
186
186
  npx aws-local-simulator status
187
187
  ```
188
188
 
189
+ ## 🖥️ Management API
190
+
191
+ O simulador expõe uma API de gerenciamento em tempo de execução na porta `9999` (configurável via `adminPort`). Ela permite habilitar e desabilitar serviços individualmente sem reiniciar o processo.
192
+
193
+ ### Endpoints da Management API
194
+
195
+ | Método | Rota | Descrição |
196
+ |--------|------|-----------|
197
+ | GET | `/__admin/services` | Lista todos os 19 serviços com status |
198
+ | GET | `/__admin/services/:name` | Status de um serviço específico |
199
+ | POST | `/__admin/services/:name/enable` | Habilita um serviço parado |
200
+ | POST | `/__admin/services/:name/disable` | Desabilita um serviço em execução |
201
+
202
+ ### Exemplos
203
+
204
+ ```bash
205
+ # Listar todos os serviços
206
+ curl http://localhost:9999/__admin/services
207
+
208
+ # Habilitar DynamoDB
209
+ curl -X POST http://localhost:9999/__admin/services/dynamodb/enable
210
+
211
+ # Desabilitar SQS
212
+ curl -X POST http://localhost:9999/__admin/services/sqs/disable
213
+
214
+ # Status de um serviço
215
+ curl http://localhost:9999/__admin/services/lambda
216
+ ```
217
+
218
+ ### Configuração da porta admin
219
+
220
+ ```json
221
+ {
222
+ "adminPort": 9999
223
+ }
224
+ ```
225
+
226
+ Ou via variável de ambiente:
227
+ ```bash
228
+ AWS_LOCAL_SIMULATOR_ADMIN_PORT=9999 npx aws-local-simulator start
229
+ ```
230
+
231
+ > A Management API nunca persiste alterações em disco — todo o estado é mantido em memória. Para uso em desenvolvimento local apenas.
232
+
189
233
  ## 🔌 Endpoints
190
234
 
191
235
  | Serviço | Endpoint | Admin |
@@ -210,6 +254,7 @@ npx aws-local-simulator status
210
254
  | CloudFormation | http://localhost:4580 | http://localhost:4580/__admin/stacks |
211
255
  | Athena | http://localhost:4599 | http://localhost:4599/__admin/health |
212
256
  | ECS | http://localhost:8080 | http://localhost:8080/__admin/clusters |
257
+ | Management API | http://localhost:9999 | http://localhost:9999/__admin/services |
213
258
 
214
259
  ## 🧪 Testando com AWS CLI
215
260
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gugananuvem/aws-local-simulator",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Simulador local completo para serviços AWS",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -9,31 +9,30 @@
9
9
  "scripts": {
10
10
  "start": "node bin/aws-local-simulator.js start"
11
11
  },
12
- "jest": {
13
- "setupFilesAfterEnv": [
14
- "<rootDir>/tests/setup.js"
15
- ],
16
- "testEnvironment": "node",
17
- "testMatch": [
18
- "**/tests/**/*.test.js"
19
- ],
20
- "collectCoverageFrom": [
21
- "src/**/*.js",
22
- "!src/templates/**",
23
- "!**/node_modules/**"
24
- ],
25
- "coverageThreshold": {
26
- "global": {
27
- "branches": 30,
28
- "functions": 30,
29
- "lines": 30,
30
- "statements": 30
31
- }
32
- },
33
- "verbose": true,
34
- "testTimeout": 30000,
35
- "forceExit": true,
36
- "detectOpenHandles": true
12
+ "dependencies": {
13
+ "@aws-sdk/client-api-gateway": "^3.0.0",
14
+ "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
15
+ "@aws-sdk/client-dynamodb": "^3.0.0",
16
+ "@aws-sdk/client-ecs": "^3.0.0",
17
+ "@aws-sdk/client-lambda": "^3.0.0",
18
+ "@aws-sdk/client-s3": "^3.0.0",
19
+ "@aws-sdk/client-sqs": "^3.0.0",
20
+ "@aws-sdk/lib-dynamodb": "^3.0.0",
21
+ "child_process": "1.0.2",
22
+ "cors": "^2.8.5",
23
+ "crypto": "1.0.1",
24
+ "dotenv": "^16.3.1",
25
+ "express": "^4.18.2",
26
+ "fs": "0.0.1-security",
27
+ "glob": "10.5.0",
28
+ "http": "0.0.1-security",
29
+ "js-yaml": "4.1.1",
30
+ "jsonwebtoken": "^9.0.2",
31
+ "mkdirp": "^3.0.1",
32
+ "path": "0.12.7",
33
+ "url": "0.11.4",
34
+ "urlpattern-polyfill": "^10.0.0",
35
+ "uuid": "14.0.0"
37
36
  },
38
37
  "keywords": [
39
38
  "aws",
@@ -43,21 +42,7 @@
43
42
  "s3",
44
43
  "sqs",
45
44
  "lambda",
46
- "cognito",
47
- "apigateway",
48
- "kms",
49
- "config",
50
- "parameter-store",
51
- "secret-manager",
52
- "cloudformation",
53
- "eventbridge",
54
- "sns",
55
- "ecs",
56
- "sts",
57
- "cloudtrail",
58
- "cloudwatch",
59
- "development",
60
- "testing"
45
+ "cognito"
61
46
  ],
62
47
  "author": {
63
48
  "name": "gugananuvem",
@@ -69,7 +54,7 @@
69
54
  "url": "git+https://github.com/gugananuvem/aws-local-simulator.git"
70
55
  },
71
56
  "engines": {
72
- "node": ">=14.0.0"
57
+ "node": ">=18.0.0"
73
58
  },
74
59
  "files": [
75
60
  "src/",
@@ -81,46 +66,6 @@
81
66
  "publishConfig": {
82
67
  "directory": "dist"
83
68
  },
84
- "dependencies": {
85
- "cors": "^2.8.5",
86
- "dotenv": "^16.3.1",
87
- "express": "^4.18.2",
88
- "glob": "10.5.0",
89
- "jsonwebtoken": "^9.0.2",
90
- "mkdirp": "^3.0.1",
91
- "urlpattern-polyfill": "^10.0.0",
92
- "uuid": "14.0.0"
93
- },
94
- "peerDependencies": {
95
- "@aws-sdk/client-api-gateway": "^3.0.0",
96
- "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
97
- "@aws-sdk/client-dynamodb": "^3.0.0",
98
- "@aws-sdk/client-ecs": "^3.0.0",
99
- "@aws-sdk/client-lambda": "^3.1032.0",
100
- "@aws-sdk/client-s3": "^3.0.0",
101
- "@aws-sdk/client-sqs": "^3.0.0",
102
- "@aws-sdk/lib-dynamodb": "^3.0.0"
103
- },
104
- "peerDependenciesMeta": {
105
- "@aws-sdk/client-dynamodb": {
106
- "optional": true
107
- },
108
- "@aws-sdk/client-s3": {
109
- "optional": true
110
- },
111
- "@aws-sdk/client-sqs": {
112
- "optional": true
113
- },
114
- "@aws-sdk/client-cognito-identity-provider": {
115
- "optional": true
116
- },
117
- "@aws-sdk/client-api-gateway": {
118
- "optional": true
119
- },
120
- "@aws-sdk/client-ecs": {
121
- "optional": true
122
- }
123
- },
124
- "buildDate": "2026-04-24T17:52:50.093Z",
69
+ "buildDate": "2026-04-27T11:03:00.202Z",
125
70
  "published": true
126
71
  }
@@ -9,12 +9,16 @@ module.exports = {
9
9
  s3: true,
10
10
  sqs: true,
11
11
  lambda: true,
12
- sns: false,
13
- eventbridge: false,
12
+ sns: true,
13
+ eventbridge: true,
14
14
  ecs: false,
15
- cognito: false,
16
- apigateway: false,
15
+ cognito: true,
16
+ apigateway: true,
17
17
  sts: true,
18
+ "secret-manager": true,
19
+ "parameter-store": true,
20
+ kms: true,
21
+ kinesis: true,
18
22
  },
19
23
 
20
24
  // Portas padrão
@@ -30,6 +34,10 @@ module.exports = {
30
34
  apigateway: 4567,
31
35
  sts: 9326,
32
36
  athena: 4599,
37
+ secretManager: 4001,
38
+ parameterStore: 4002,
39
+ kms: 4000,
40
+ kinesis: 4568,
33
41
  },
34
42
  apigateway: {
35
43
  defaultCors: {
@@ -65,4 +73,7 @@ module.exports = {
65
73
 
66
74
  // Configurações adicionais
67
75
  additional: {},
76
+
77
+ // Porta do servidor de administração (Management API)
78
+ adminPort: 9999,
68
79
  };
package/src/server.js CHANGED
@@ -5,6 +5,8 @@
5
5
  const path = require("path");
6
6
  const fs = require("fs");
7
7
  const mkdirp = require("mkdirp");
8
+ const express = require("express");
9
+ const cors = require("cors");
8
10
  const logger = require("./utils/logger");
9
11
 
10
12
  // Importa serviços
@@ -34,6 +36,10 @@ class Server {
34
36
  this.services = [];
35
37
  this.servicesMap = new Map();
36
38
  this.running = false;
39
+ this.managementApp = express();
40
+ this.managementApp.use(cors());
41
+ this.managementApp.use(express.json());
42
+ this.managementServer = null;
37
43
  this.setupDataDir();
38
44
  this.setupLogLevel();
39
45
  }
@@ -63,6 +69,8 @@ class Server {
63
69
  try {
64
70
  await this.initializeServices();
65
71
  await this.startServices();
72
+ this.setupManagementRoutes();
73
+ await this.startManagementServer();
66
74
 
67
75
  this.running = true;
68
76
  this.printStatus();
@@ -72,28 +80,150 @@ class Server {
72
80
  }
73
81
  }
74
82
 
75
- async initializeServices() {
76
- const serviceOrder = [
77
- { name: "sts", class: STSService, depends: [] },
78
- { name: "lambda", class: LambdaService, depends: [] },
79
- { name: "dynamodb", class: DynamoDBService, depends: [] },
80
- { name: "s3", class: S3Service, depends: [] },
81
- { name: "sqs", class: SQSService, depends: ["lambda"] },
82
- { name: "sns", class: SNSService, depends: [] },
83
- { name: "eventbridge", class: EventBridgeService, depends: [] },
84
- { name: "cognito", class: CognitoService, depends: ["lambda"] },
85
- { name: "ecs", class: ECSService, depends: [] },
86
- { name: "apigateway", class: APIGatewayService, depends: ["lambda", "cognito"] },
87
- { name: "kms", class: KMSService, depends: [] },
88
- { name: "cloudwatch", class: CloudWatchService, depends: [] },
89
- { name: "cloudtrail", class: CloudTrailService, depends: [] },
90
- { name: "cloudformation", class: CloudFormationService, depends: [] },
91
- { name: "xray", class: XRayService, depends: [] },
92
- { name: "secret-manager", class: SecretManagerService, depends: [] },
93
- { name: "parameter-store",class: ParameterStoreService, depends: [] },
94
- { name: "config", class: ConfigService, depends: [] },
95
- { name: "athena", class: AthenaService, depends: [] },
83
+ setupManagementRoutes() {
84
+ const app = this.managementApp;
85
+
86
+ // GET /__admin/services returns all services status
87
+ app.get("/__admin/services", (req, res) => {
88
+ const registry = this.buildServiceRegistry();
89
+ const services = registry.map((def) => {
90
+ const svc = this.servicesMap.get(def.name);
91
+ if (svc) {
92
+ const status = typeof svc.getStatus === "function"
93
+ ? svc.getStatus()
94
+ : { name: def.name, running: true, port: svc.port };
95
+ // Compute canDisable: no running service should depend on this one
96
+ const canDisable = !registry.some(
97
+ (other) =>
98
+ other.depends.includes(def.name) &&
99
+ this.servicesMap.has(other.name)
100
+ );
101
+ return {
102
+ name: def.name,
103
+ running: true,
104
+ enabled: true,
105
+ port: status.port || this.config.ports?.[def.name],
106
+ endpoint: status.endpoint || `http://localhost:${status.port || this.config.ports?.[def.name]}`,
107
+ dependencies: def.depends,
108
+ category: def.category,
109
+ canDisable,
110
+ ...status,
111
+ };
112
+ }
113
+ return {
114
+ name: def.name,
115
+ running: false,
116
+ enabled: false,
117
+ port: this.config.ports?.[def.name],
118
+ endpoint: `http://localhost:${this.config.ports?.[def.name]}`,
119
+ dependencies: def.depends,
120
+ canDisable: true,
121
+ };
122
+ });
123
+ res.json({ services });
124
+ });
125
+
126
+ // GET /__admin/services/:name — returns single service status
127
+ app.get("/__admin/services/:name", (req, res) => {
128
+ const { name } = req.params;
129
+ const registry = this.buildServiceRegistry();
130
+ const def = registry.find((d) => d.name === name);
131
+ if (!def) {
132
+ return res.status(404).json({ error: `Unknown service: ${name}` });
133
+ }
134
+ const svc = this.servicesMap.get(name);
135
+ const canDisable = !registry.some(
136
+ (other) =>
137
+ other.depends.includes(name) &&
138
+ this.servicesMap.has(other.name)
139
+ );
140
+ if (svc) {
141
+ const status = typeof svc.getStatus === "function"
142
+ ? svc.getStatus()
143
+ : { name, running: true, port: svc.port };
144
+ return res.json({
145
+ name,
146
+ running: true,
147
+ enabled: true,
148
+ port: status.port || this.config.ports?.[name],
149
+ endpoint: status.endpoint || `http://localhost:${status.port || this.config.ports?.[name]}`,
150
+ dependencies: def.depends,
151
+ canDisable,
152
+ ...status,
153
+ });
154
+ }
155
+ return res.json({
156
+ name,
157
+ running: false,
158
+ enabled: false,
159
+ port: this.config.ports?.[name],
160
+ endpoint: `http://localhost:${this.config.ports?.[name]}`,
161
+ dependencies: def.depends,
162
+ canDisable: true,
163
+ });
164
+ });
165
+
166
+ // POST /__admin/services/:name/enable — enables a service
167
+ app.post("/__admin/services/:name/enable", async (req, res) => {
168
+ const result = await this.enableService(req.params.name);
169
+ res.status(result.success ? 200 : 400).json(result);
170
+ });
171
+
172
+ // POST /__admin/services/:name/disable — disables a service
173
+ app.post("/__admin/services/:name/disable", async (req, res) => {
174
+ const result = await this.disableService(req.params.name);
175
+ res.status(result.success ? 200 : 400).json(result);
176
+ });
177
+ }
178
+
179
+ startManagementServer() {
180
+ return new Promise((resolve, reject) => {
181
+ const port = this.config.adminPort || 9999;
182
+ this.managementServer = this.managementApp.listen(port, () => {
183
+ logger.info(`🔧 Management API rodando em http://localhost:${port}`);
184
+ resolve();
185
+ });
186
+ this.managementServer.on("error", reject);
187
+ });
188
+ }
189
+
190
+ buildServiceRegistry() {
191
+ return [
192
+ //Armazenamento & BD
193
+ { name: "dynamodb", class: DynamoDBService, depends: [], category: 'Armazenamento & Banco de Dados' },
194
+ { name: "s3", class: S3Service, depends: [], category: 'Armazenamento & Banco de Dados' },
195
+ { name: "athena", class: AthenaService, depends: [], category: 'Armazenamento & Banco de Dados' },
196
+
197
+ //Computação
198
+ { name: "lambda", class: LambdaService, depends: [], category: 'Computação' },
199
+ /* { name: "ecs", class: ECSService, depends: [] , category:'Computação'},*/
200
+
201
+ { name: "cognito", class: CognitoService, depends: ["lambda"], category: 'Segurança & Identidade' },
202
+ { name: "sts", class: STSService, depends: [], category: 'Segurança & Identidade' },
203
+ { name: "kms", class: KMSService, depends: [], category: 'Segurança & Identidade' },
204
+ { name: "secret-manager", class: SecretManagerService, depends: [], category: 'Segurança & Identidade' },
205
+ { name: "parameter-store", class: ParameterStoreService, depends: [], category: 'Segurança & Identidade' },
206
+
207
+ //Mensageria
208
+ { name: "sqs", class: SQSService, depends: ["lambda"], category: 'Mensageria & Integração' },
209
+ { name: "sns", class: SNSService, depends: [], category: 'Mensageria & Integração' },
210
+ { name: "eventbridge", class: EventBridgeService, depends: [], category: 'Mensageria & Integração' },
211
+
212
+ //Networking
213
+ { name: "apigateway", class: APIGatewayService, depends: ["lambda", "cognito"], category: 'Networking' },
214
+
215
+ //Observabilidade & Conformidade
216
+ { name: "cloudwatch", class: CloudWatchService, depends: [], category: 'Observabilidade & Conformidade' },
217
+ { name: "cloudtrail", class: CloudTrailService, depends: [], category: 'Observabilidade & Conformidade' },
218
+ { name: "xray", class: XRayService, depends: [], category: 'Observabilidade & Conformidade' },
219
+ { name: "config", class: ConfigService, depends: [], category: 'Observabilidade & Conformidade' },
220
+ { name: "cloudformation", class: CloudFormationService, depends: [], category: 'Observabilidade & Conformidade' },
221
+
96
222
  ];
223
+ }
224
+
225
+ async initializeServices() {
226
+ const serviceOrder = this.buildServiceRegistry();
97
227
 
98
228
  for (const serviceDef of serviceOrder) {
99
229
  if (this.config.services[serviceDef.name]) {
@@ -143,6 +273,11 @@ class Server {
143
273
  const stopPromises = [...this.services].reverse().map((service) => service.stop());
144
274
  await Promise.all(stopPromises);
145
275
 
276
+ if (this.managementServer) {
277
+ await new Promise((resolve) => this.managementServer.close(resolve));
278
+ this.managementServer = null;
279
+ }
280
+
146
281
  this.running = false;
147
282
  logger.success("✅ Todos os serviços foram parados");
148
283
  } catch (error) {
@@ -177,6 +312,107 @@ class Server {
177
312
  return this.servicesMap.get(name);
178
313
  }
179
314
 
315
+ async enableService(name) {
316
+ // Check if already running
317
+ if (this.servicesMap.has(name)) {
318
+ return { success: false, error: "Service already running" };
319
+ }
320
+
321
+ // Look up in registry
322
+ const registry = this.buildServiceRegistry();
323
+ const serviceDef = registry.find((s) => s.name === name);
324
+ if (!serviceDef) {
325
+ return { success: false, error: "Unknown service" };
326
+ }
327
+
328
+ // Validate all dependencies are running
329
+ for (const dep of serviceDef.depends) {
330
+ if (!this.servicesMap.has(dep)) {
331
+ return { success: false, error: `Dependency not running: ${dep}` };
332
+ }
333
+ }
334
+
335
+ try {
336
+ const service = new serviceDef.class(this.config);
337
+ await service.initialize();
338
+ await service.start();
339
+
340
+ this.services.push(service);
341
+ this.servicesMap.set(name, service);
342
+
343
+ if (typeof service.injectDependencies === "function") {
344
+ service.injectDependencies(this);
345
+ }
346
+
347
+ // Build ServiceStatus for the response
348
+ const canDisable = !registry.some(
349
+ (other) =>
350
+ other.depends.includes(name) && this.servicesMap.has(other.name)
351
+ );
352
+ const rawStatus =
353
+ typeof service.getStatus === "function"
354
+ ? service.getStatus()
355
+ : { name, running: true, port: service.port };
356
+
357
+ const serviceStatus = {
358
+ name,
359
+ running: true,
360
+ enabled: true,
361
+ port: rawStatus.port || this.config.ports?.[name],
362
+ endpoint:
363
+ rawStatus.endpoint ||
364
+ `http://localhost:${rawStatus.port || this.config.ports?.[name]}`,
365
+ dependencies: serviceDef.depends,
366
+ canDisable,
367
+ ...rawStatus,
368
+ };
369
+
370
+ return { success: true, service: serviceStatus };
371
+ } catch (error) {
372
+ return { success: false, error: error.message };
373
+ }
374
+ }
375
+
376
+ async disableService(name) {
377
+ // Check if running
378
+ const service = this.servicesMap.get(name);
379
+ if (!service) {
380
+ return { success: false, error: "Service not running" };
381
+ }
382
+
383
+ // Check reverse dependencies
384
+ const registry = this.buildServiceRegistry();
385
+ for (const other of registry) {
386
+ if (other.depends.includes(name) && this.servicesMap.has(other.name)) {
387
+ return {
388
+ success: false,
389
+ error: `Cannot disable: ${other.name} depends on it`,
390
+ };
391
+ }
392
+ }
393
+
394
+ try {
395
+ await service.stop();
396
+ this.services = this.services.filter((s) => s !== service);
397
+ this.servicesMap.delete(name);
398
+
399
+ return {
400
+ success: true,
401
+ service: {
402
+ name,
403
+ running: false,
404
+ enabled: false,
405
+ port: this.config.ports?.[name],
406
+ endpoint: `http://localhost:${this.config.ports?.[name]}`,
407
+ dependencies: registry.find((d) => d.name === name)?.depends || [],
408
+ canDisable: true,
409
+ },
410
+ };
411
+ } catch (error) {
412
+ return { success: false, error: error.message };
413
+ }
414
+ }
415
+
180
416
  printStatus() {
181
417
  logger.info("\n" + "=".repeat(60));
182
418
  logger.info("📊 Status dos Serviços:");
@@ -209,6 +209,18 @@ class APIGatewayServer {
209
209
  }
210
210
  });
211
211
 
212
+ this.app.patch('/restapis/:apiId', (req, res) => {
213
+ try {
214
+ const result = this.simulator.updateRestApi({
215
+ ...req.body,
216
+ restApiId: req.params.apiId
217
+ });
218
+ res.json(result);
219
+ } catch (error) {
220
+ res.status(400).json({ error: error.message });
221
+ }
222
+ });
223
+
212
224
  this.app.delete('/restapis/:apiId', (req, res) => {
213
225
  try {
214
226
  this.simulator.deleteRestApi({ restApiId: req.params.apiId });
@@ -509,6 +521,31 @@ class APIGatewayServer {
509
521
  }
510
522
  });
511
523
 
524
+ this.app.post('/__admin/apis/:apiId/endpoints', (req, res) => {
525
+ try {
526
+ const result = this.simulator.putEndpoint({
527
+ ...req.body,
528
+ restApiId: req.params.apiId
529
+ });
530
+ res.json(result);
531
+ } catch (error) {
532
+ res.status(400).json({ error: error.message });
533
+ }
534
+ });
535
+
536
+ this.app.delete('/__admin/apis/:apiId/endpoints', (req, res) => {
537
+ try {
538
+ const result = this.simulator.deleteEndpoint({
539
+ restApiId: req.params.apiId,
540
+ path: req.query.path,
541
+ method: req.query.method
542
+ });
543
+ res.json(result);
544
+ } catch (error) {
545
+ res.status(400).json({ error: error.message });
546
+ }
547
+ });
548
+
512
549
  this.app.get('/__admin/apikeys', (req, res) => {
513
550
  const keys = Array.from(this.simulator.apiKeys.values()).map(k => ({
514
551
  id: k.id,