local-serverless-stack 0.1.1 → 0.2.0
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/CHANGELOG.md +36 -1
- package/README.md +6 -4
- package/bin/cli.js +323 -44
- package/dist/server/dev/dynamo-proxy.d.ts.map +1 -1
- package/dist/server/dev/dynamo-proxy.js +4 -0
- package/dist/server/dev/dynamo-proxy.js.map +1 -1
- package/dist/server/routes/queues.d.ts.map +1 -1
- package/dist/server/routes/queues.js +81 -0
- package/dist/server/routes/queues.js.map +1 -1
- package/dist/server/routes/seeds.d.ts.map +1 -1
- package/dist/server/routes/seeds.js +6 -1
- package/dist/server/routes/seeds.js.map +1 -1
- package/dist/server/services/config-manager.d.ts +2 -0
- package/dist/server/services/config-manager.d.ts.map +1 -1
- package/dist/server/services/config-manager.js +10 -0
- package/dist/server/services/config-manager.js.map +1 -1
- package/dist/server/services/queue-inspector.d.ts +68 -0
- package/dist/server/services/queue-inspector.d.ts.map +1 -1
- package/dist/server/services/queue-inspector.js +182 -4
- package/dist/server/services/queue-inspector.js.map +1 -1
- package/dist/server/services/resource-provisioner.d.ts.map +1 -1
- package/dist/server/services/resource-provisioner.js +1 -0
- package/dist/server/services/resource-provisioner.js.map +1 -1
- package/dist/server/services/seed-manager.d.ts +2 -0
- package/dist/server/services/seed-manager.d.ts.map +1 -1
- package/dist/server/services/seed-manager.js +40 -0
- package/dist/server/services/seed-manager.js.map +1 -1
- package/package.json +10 -8
- package/packages/serverless-plugin/README.md +4 -4
- package/packages/serverless-plugin/dist/index.js +7 -0
- package/packages/serverless-plugin/dist/index.js.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-06-04
|
|
9
|
+
|
|
10
|
+
Testability features for downstream e2e suites: an isolated test instance and a deterministic queue-drain wait. Plugin `serverless-lss` bumped to `0.1.0` (it gained `LSS_DASHBOARD_PORT` support — install both).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`--config <path>` flag for `start|stop|status|logs`**: loads config from the given file, taking precedence over the cwd/home search (`lss.config.json` / `.lssrc`). A missing or unparseable explicit file warns and falls back to the search rather than hard-exiting, so `stop`/`status`/`logs` never orphan a running instance. `bin/cli.js` resolves the path to absolute once per invocation (also accepted via the `LSS_CONFIG` env var) and hands it to the spawned server as `LSS_CONFIG_PATH`, so the orchestrator's `ConfigManager` reads the identical file — keeping the two config loaders in agreement on port/seedsDir/region/mode.
|
|
14
|
+
- **`stateDir` config field**: directory where an instance keeps its PID/log (the PID file doubles as the lock). When set (e.g. `".lss-e2e"`), `runtimePaths()` places state there so an isolated instance — typically an e2e test stack started with `--config` — can be started and stopped without ever touching the dev instance. When omitted, behavior is unchanged: PID/log live in the OS temp dir scoped by `serverPort`. `ConfigManager` gained the field plus a `getStateDir()` getter (resolved relative to the working directory).
|
|
15
|
+
- **`POST /api/queues/:name/await-idle`**: blocking endpoint that polls a queue's counters (forcing a fresh read every ~250 ms rather than waiting on the 5 s background poller) and resolves `200 { queue, available, inFlight, processed, drained: true }` once the queue is idle (`available === 0 && inFlight === 0`, and `processed >= sinceProcessed` when that body field is supplied), or `408 { …, drained: false }` on timeout. Body: `{ timeoutMs?: number = 15000 (clamped 100–120000), sinceProcessed?: number }`. Lets a test deterministically wait for an SQS consumer to drain before asserting persistence. Accepts the logical queue name (e.g. `activity-save.fifo`).
|
|
16
|
+
- **Queue hold/intercept primitives** (`POST /api/queues/:name/hold`, `GET /api/queues/:name/captured`, `POST /api/queues/:name/release`): `hold` disables the queue's consumer event source mapping(s) (`UpdateEventSourceMapping Enabled: false`) and starts capturing; `captured` drains the queue into an in-memory buffer and returns `[{ messageId, body, attributes, messageAttributes, receivedAt }]`; `release` re-enables the mapping(s) and re-dispatches the captured messages. Lets a test assert a producer's enqueued payload without running the consumer. Best-effort: hold state is in-memory (lost on restart), messages already consumed before hold can't be recalled, and LocalStack applies the `Enabled` toggle asynchronously.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **`serverless-lss` plugin honors `LSS_DASHBOARD_PORT`**: when set (and `ORCHESTRATOR_URL` is not), the plugin registers the service at `http://localhost:${LSS_DASHBOARD_PORT}`. Precedence is `ORCHESTRATOR_URL` (full URL) > `LSS_DASHBOARD_PORT` (port) > `custom.orchestrator.orchestratorUrl` > default `http://localhost:3100`. This lets the same `serverless.yml` register against an isolated test orchestrator at runtime without editing the file.
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- **Two separated test types**: `npm run test:unit` (default `npm test`) is hermetic, runs in CI, and enforces a **100% coverage gate** (statements/branches/functions/lines) over the unit-testable server code — `src/server/services/**` (except the Docker-driven `localstack-manager.ts`), `src/server/routes/**`, `src/server/dev/**`, `packages/serverless-plugin/src/**` and `bin/cli.js` (`index.ts` is excluded as it bootstraps the server at import). `npm run test:integration` boots a real isolated LSS + LocalStack and validates the promised features end-to-end. Added `aws-sdk-client-mock` + `supertest` as dev deps; split `tests/setup.ts` into shared matchers + per-type setup; `bin/cli.js` now guards its dispatch behind `require.main === module` and exports its helpers for in-process testing. ~750 unit tests.
|
|
23
|
+
- **`docs/FEATURES.md`**: a single inventory of the project's promised features (CLI, HTTP API, resource provisioning, plugin, seeds, queue primitives, config/isolation), doubling as the integration suite's checklist. The integration suite provisions `examples/sample-microservice` and asserts each capability; it runs locally and in a CI job gated on a `LOCALSTACK_AUTH_TOKEN` secret.
|
|
24
|
+
|
|
25
|
+
## [0.1.2] - 2026-05-26
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **`npx lss seed:clear` now requires interactive confirmation**: before issuing any `DeleteRequest`, the CLI lists the exact LocalStack tables it would wipe, prints the LocalStack URL, makes the "this never touches AWS" guarantee explicit, and waits for the user to type `confirmar` (case-sensitive, exact match after trim). Anything else cancels with `🚫 Cancelado — nenhuma alteração feita.` and no clear call is made. `--yes` / `-y` skips the prompt for CI use.
|
|
29
|
+
- **Defensive endpoint guard in `SeedManager`**: `clearTable` and `clearAllSeeded` now refuse to run unless the resolved DynamoDB endpoint hostname is on a hardcoded local allowlist (`localhost`, `127.0.0.1`, `::1`, `0.0.0.0`, `host.docker.internal`, `localstack`, `lss-localstack`, `lss-localstack-<port>`, `*.localhost`). If a future refactor ever pointed `LocalStackManager.getConfig()` at a real AWS endpoint, the guard would throw `Refusing destructive operation: endpoint "..." is not a recognized local LocalStack host. seed:clear may ONLY run against LocalStack — never against AWS.` before any AWS SDK call. Architecture already pinned writes to LocalStack via fake credentials; this is belt-and-braces.
|
|
30
|
+
- **Seed/clear mismatch diagnostic**: `GET /api/seeds` now also returns `liveTables: string[]` (every DynamoDB table currently in LocalStack). When `npx lss seed` skips tables or `npx lss seed:clear` finds no live targets, the CLI prints a two-column diagnostic — seed files inspected on one side, live LocalStack tables on the other — with the hint "Os nomes dos arquivos de seed precisam bater EXATAMENTE com o `TableName` no CloudFormation." This makes seed-name/table-name typos obvious instead of "nothing happened, why?". When the live-tables list is empty, the CLI falls back to the older "run `npx lss start` + `npx serverless deploy` first" hint.
|
|
31
|
+
- **Test infrastructure**: new `tests/unit/seed-manager-guard.test.ts` (27 tests covering the endpoint allowlist matrix, IPv6 bracket handling, malformed URLs, and that `clearTable`/`clearAllSeeded` invoke the guard) and `tests/unit/cli-seed.test.ts` (21 tests spawning `bin/cli.js` against an in-process HTTP stub of the orchestrator — covers confirmation flow, `--yes`/`-y` bypass, "no live tables" diagnostic, name-mismatch diagnostic, and `formatError` robustness against 500-with-empty-body / 500-with-non-JSON / `{error: ""}` responses).
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- **`bin/cli.js` error handling never leaves the user with an empty message**: new `formatError(e)` and `buildHttpError(res, data)` helpers walk a fallback chain (`e.message` → `e.code` → `e.name` → body snippet → HTTP status text → "erro desconhecido (sem detalhes)") so a response 500 with no body, a socket reset during orchestrator startup, or an `Error` with empty `.message` all produce a useful CLI line instead of `❌ Não consegui listar as tabelas antes de limpar:` with a blank suffix. Applied to every `console.error` path in `seed`, `seed:clear`, and the underlying `getJson`/`postJson` helpers.
|
|
35
|
+
- **`npx lss seed` hint when tables are missing in LocalStack**: previously printed a generic "tabelas foram puladas" footer. Now it fetches `liveTables` and shows the same mismatch diagnostic as `seed:clear`, so the user sees whether the cause is "I haven't deployed yet" (no live tables) or "my seed file name is wrong" (live tables exist but don't match).
|
|
36
|
+
- **`firstPositional()` arg parser in the CLI**: commands that accept an optional table name (`seed`, `seed:clear`) now skip args starting with `-`. Without this, `npx lss seed:clear --yes` was interpreting `--yes` as the table name.
|
|
37
|
+
- **`SeedManager.assertLocalEndpoint` normalizes IPv6 brackets** so URLs like `http://[::1]:4566` (which `new URL().hostname` reports as `[::1]`) match the `::1` entry in the allowlist instead of being rejected as non-local.
|
|
38
|
+
- **`jest.config.js` `moduleNameMapper`**: strips `.js` from relative imports during tests so the server's NodeNext-style ESM imports (`import { Foo } from './foo.js'`) resolve to TypeScript sources under ts-jest. Without this, no unit test could import from `src/server/`.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- **`examples/pro-sample-microservice/seeds/`**: renamed `sample-microservice-Users.json` → `pro-sample-microservice-Users.json` and `sample-microservice-Orders.json` → `pro-sample-microservice-Orders.json` (via `git mv` to preserve history). The files had been copied from `sample-microservice` but never renamed, so the seed prefix didn't match the example's actual `${self:service}-*` `TableName`s in `serverless.yml` — `npx lss seed` and `npx lss seed:clear` always found `tableExists: false` and silently did nothing.
|
|
42
|
+
|
|
8
43
|
## [0.1.1] - 2026-05-21
|
|
9
44
|
|
|
10
45
|
### Added
|
|
@@ -310,7 +345,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
310
345
|
- Requires Docker for LocalStack
|
|
311
346
|
- Tested on Linux (Ubuntu/Debian)
|
|
312
347
|
- Node.js >= 18 required
|
|
313
|
-
- Serverless Framework
|
|
348
|
+
- Serverless Framework 3.40.0 required
|
|
314
349
|
|
|
315
350
|
### Breaking Changes
|
|
316
351
|
None (initial release)
|
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ LSS provides a unified local development environment for serverless microservice
|
|
|
17
17
|
- **Process management**: Start/stop microservices from the orchestrator
|
|
18
18
|
- **CLI Tool**: Simple commands to manage the orchestrator (start/stop/status/logs)
|
|
19
19
|
|
|
20
|
+
See [docs/FEATURES.md](docs/FEATURES.md) for the complete feature inventory and how each capability is tested.
|
|
21
|
+
|
|
20
22
|
## Architecture
|
|
21
23
|
|
|
22
24
|
```
|
|
@@ -50,7 +52,7 @@ local-serverless-stack/
|
|
|
50
52
|
|
|
51
53
|
- Node.js >= 18
|
|
52
54
|
- Docker (for LocalStack)
|
|
53
|
-
- Serverless Framework
|
|
55
|
+
- Serverless Framework 3.40.0
|
|
54
56
|
|
|
55
57
|
### Installation
|
|
56
58
|
|
|
@@ -74,7 +76,7 @@ The `npm link` command makes the `lss` CLI available globally via `npx`.
|
|
|
74
76
|
To automatically register your microservices with LSS, install the Serverless plugin:
|
|
75
77
|
|
|
76
78
|
```bash
|
|
77
|
-
npm install --save-dev
|
|
79
|
+
npm install --save-dev serverless-lss
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
See the [plugin documentation](packages/serverless-plugin/README.md) for configuration details.
|
|
@@ -146,7 +148,7 @@ npm link local-serverless-stack
|
|
|
146
148
|
In each microservice directory:
|
|
147
149
|
|
|
148
150
|
```bash
|
|
149
|
-
npm link
|
|
151
|
+
npm link serverless-lss
|
|
150
152
|
```
|
|
151
153
|
|
|
152
154
|
### 3. Configure serverless.yml
|
|
@@ -159,7 +161,7 @@ plugins:
|
|
|
159
161
|
- serverless-esbuild
|
|
160
162
|
- serverless-offline
|
|
161
163
|
- serverless-localstack
|
|
162
|
-
-
|
|
164
|
+
- serverless-lss # Add this line
|
|
163
165
|
|
|
164
166
|
custom:
|
|
165
167
|
orchestrator:
|
package/bin/cli.js
CHANGED
|
@@ -11,10 +11,24 @@ const os = require('os');
|
|
|
11
11
|
const DEFAULT_PID_FILE = path.join(os.tmpdir(), 'lss-orchestrator.pid');
|
|
12
12
|
const DEFAULT_LOG_FILE = path.join(os.tmpdir(), 'lss-orchestrator.log');
|
|
13
13
|
|
|
14
|
-
// PID/log file paths
|
|
15
|
-
//
|
|
14
|
+
// PID/log file paths for the instance addressed by this invocation.
|
|
15
|
+
// - When the config provides a `stateDir`, PID/log live inside it. This gives
|
|
16
|
+
// an explicitly isolated instance (e.g. an e2e test stack) its own state so
|
|
17
|
+
// `lss stop --config <path>` targets it and never the dev instance.
|
|
18
|
+
// - Otherwise the paths are scoped to the cwd's serverPort, so multiple
|
|
19
|
+
// examples sitting in different folders don't trample each other.
|
|
16
20
|
function runtimePaths() {
|
|
17
21
|
const cfg = getConfig(loadConfig());
|
|
22
|
+
|
|
23
|
+
if (cfg.stateDir) {
|
|
24
|
+
const dir = path.resolve(process.cwd(), cfg.stateDir);
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
26
|
+
return {
|
|
27
|
+
pidFile: path.join(dir, 'orchestrator.pid'),
|
|
28
|
+
logFile: path.join(dir, 'orchestrator.log'),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
const port = cfg.serverPort;
|
|
19
33
|
// Keep the legacy global path when the default port is in use so existing
|
|
20
34
|
// installations don't lose their running process across an upgrade.
|
|
@@ -28,9 +42,24 @@ function runtimePaths() {
|
|
|
28
42
|
}
|
|
29
43
|
|
|
30
44
|
/**
|
|
31
|
-
* Load configuration
|
|
45
|
+
* Load configuration. An explicit `--config <path>` (or LSS_CONFIG env) wins over
|
|
46
|
+
* the cwd/home search so an isolated instance can point at its own config file.
|
|
47
|
+
* A missing or unparseable explicit file warns and falls back to the search,
|
|
48
|
+
* rather than hard-exiting, so stop/status/logs never orphan a running instance.
|
|
32
49
|
*/
|
|
33
50
|
function loadConfig() {
|
|
51
|
+
if (EXPLICIT_CONFIG) {
|
|
52
|
+
if (fs.existsSync(EXPLICIT_CONFIG)) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(EXPLICIT_CONFIG, 'utf-8'));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn(`⚠️ Failed to parse config file ${EXPLICIT_CONFIG}`);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
console.warn(`⚠️ Config file not found: ${EXPLICIT_CONFIG}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
const candidates = [
|
|
35
64
|
path.join(process.cwd(), 'lss.config.json'),
|
|
36
65
|
path.join(process.cwd(), '.lssrc'),
|
|
@@ -66,6 +95,7 @@ function getConfig(config) {
|
|
|
66
95
|
localstackVersion: config.localstackVersion || 'latest',
|
|
67
96
|
localstackImage: config.localstackImage,
|
|
68
97
|
localstackAuthToken: config.localstackAuthToken,
|
|
98
|
+
stateDir: config.stateDir,
|
|
69
99
|
};
|
|
70
100
|
}
|
|
71
101
|
|
|
@@ -79,6 +109,15 @@ function getArgValue(name) {
|
|
|
79
109
|
return inline ? inline.slice(prefix.length) : undefined;
|
|
80
110
|
}
|
|
81
111
|
|
|
112
|
+
// Resolved once per invocation: an explicit config file from `--config <path>`
|
|
113
|
+
// (or the LSS_CONFIG env var), resolved to an absolute path so it's found
|
|
114
|
+
// regardless of cwd. Every argument-less loadConfig() call honors it because
|
|
115
|
+
// process.argv is fixed for the lifetime of the invocation.
|
|
116
|
+
const EXPLICIT_CONFIG = (() => {
|
|
117
|
+
const v = getArgValue('--config') || process.env.LSS_CONFIG;
|
|
118
|
+
return v ? path.resolve(v) : undefined;
|
|
119
|
+
})();
|
|
120
|
+
|
|
82
121
|
// Resolve orchestrator path - works both in development and when installed via npm
|
|
83
122
|
function getOrchestratorPath() {
|
|
84
123
|
// Try to find the orchestrator relative to this script
|
|
@@ -151,24 +190,30 @@ function startOrchestrator() {
|
|
|
151
190
|
|
|
152
191
|
// Build environment variables from config
|
|
153
192
|
const env = { ...process.env };
|
|
193
|
+
/* istanbul ignore else: getConfig() always defaults serverPort to 3100, so the else is unreachable */
|
|
154
194
|
if (cfg.serverPort) {
|
|
155
195
|
env.PORT = cfg.serverPort;
|
|
156
196
|
}
|
|
197
|
+
/* istanbul ignore else: getConfig() always defaults localstackPort to 4566, so the else is unreachable */
|
|
157
198
|
if (cfg.localstackPort) {
|
|
158
199
|
env.LSS_LOCALSTACK_PORT = cfg.localstackPort;
|
|
159
200
|
}
|
|
160
201
|
if (enableDynamoProxy) {
|
|
161
202
|
env.LSS_ENABLE_DYNAMO_PROXY = 'true';
|
|
162
203
|
}
|
|
204
|
+
/* istanbul ignore else: getConfig() always defaults dynamoProxyPort to 8000, so the else is unreachable */
|
|
163
205
|
if (cfg.dynamoProxyPort) {
|
|
164
206
|
env.LSS_DYNAMO_PROXY_PORT = cfg.dynamoProxyPort;
|
|
165
207
|
}
|
|
208
|
+
/* istanbul ignore else: mode resolves to cfg.mode which getConfig() defaults to 'managed', so the else is unreachable */
|
|
166
209
|
if (mode) {
|
|
167
210
|
env.LSS_LOCALSTACK_MODE = mode;
|
|
168
211
|
}
|
|
212
|
+
/* istanbul ignore else: edition resolves to localstackEdition which getConfig() defaults to 'community', so the else is unreachable */
|
|
169
213
|
if (edition) {
|
|
170
214
|
env.LSS_LOCALSTACK_EDITION = edition;
|
|
171
215
|
}
|
|
216
|
+
/* istanbul ignore else: getConfig() always defaults localstackVersion to 'latest', so the else is unreachable */
|
|
172
217
|
if (cfg.localstackVersion) {
|
|
173
218
|
env.LSS_LOCALSTACK_VERSION = cfg.localstackVersion;
|
|
174
219
|
}
|
|
@@ -178,7 +223,13 @@ function startOrchestrator() {
|
|
|
178
223
|
if (authToken) {
|
|
179
224
|
env.LOCALSTACK_AUTH_TOKEN = authToken;
|
|
180
225
|
}
|
|
181
|
-
|
|
226
|
+
// Hand the same config file to the server so its ConfigManager reads the
|
|
227
|
+
// identical serverPort/localstackPort/seedsDir/region/mode (not just the
|
|
228
|
+
// hand-translated subset above). Keeps the two config loaders in agreement.
|
|
229
|
+
if (EXPLICIT_CONFIG) {
|
|
230
|
+
env.LSS_CONFIG_PATH = EXPLICIT_CONFIG;
|
|
231
|
+
}
|
|
232
|
+
|
|
182
233
|
const child = spawn('node', [orchestratorPath], {
|
|
183
234
|
detached: true,
|
|
184
235
|
stdio: ['ignore', logFd, logFd],
|
|
@@ -263,6 +314,30 @@ function getServerPort() {
|
|
|
263
314
|
return cfg.serverPort;
|
|
264
315
|
}
|
|
265
316
|
|
|
317
|
+
// Produce a usable error string for the user. Errors from the http stack
|
|
318
|
+
// occasionally carry an empty `.message` (e.g. socket resets during the
|
|
319
|
+
// orchestrator startup window) — fall back through every signal we have so
|
|
320
|
+
// `formatError` is guaranteed not to return an empty string.
|
|
321
|
+
function formatError(e) {
|
|
322
|
+
if (!e) return 'erro desconhecido (sem detalhes)';
|
|
323
|
+
if (typeof e === 'string') return e.trim() || 'erro desconhecido (sem detalhes)';
|
|
324
|
+
if (e.message && String(e.message).trim()) return String(e.message).trim();
|
|
325
|
+
if (e.code) return `erro de I/O (${e.code})`;
|
|
326
|
+
if (e.name) return e.name;
|
|
327
|
+
const s = String(e);
|
|
328
|
+
return s && s !== '[object Object]' ? s : 'erro desconhecido (sem detalhes)';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function buildHttpError(res, data) {
|
|
332
|
+
let parsed;
|
|
333
|
+
try { parsed = data ? JSON.parse(data) : {}; } catch { parsed = null; }
|
|
334
|
+
const fromBody = parsed && (parsed.error || parsed.message);
|
|
335
|
+
if (fromBody && String(fromBody).trim()) return new Error(String(fromBody).trim());
|
|
336
|
+
const snippet = data && data.length < 300 ? data.trim() : '';
|
|
337
|
+
const statusText = res.statusMessage ? `${res.statusCode} ${res.statusMessage}` : `${res.statusCode}`;
|
|
338
|
+
return new Error(snippet ? `HTTP ${statusText}: ${snippet}` : `HTTP ${statusText} (sem corpo de erro)`);
|
|
339
|
+
}
|
|
340
|
+
|
|
266
341
|
function postJson(path, body) {
|
|
267
342
|
return new Promise((resolve, reject) => {
|
|
268
343
|
const http = require('http');
|
|
@@ -282,17 +357,17 @@ function postJson(path, body) {
|
|
|
282
357
|
let data = '';
|
|
283
358
|
res.on('data', chunk => (data += chunk));
|
|
284
359
|
res.on('end', () => {
|
|
285
|
-
let parsed;
|
|
286
|
-
try { parsed = data ? JSON.parse(data) : {}; } catch { parsed = { raw: data }; }
|
|
287
360
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
361
|
+
let parsed;
|
|
362
|
+
try { parsed = data ? JSON.parse(data) : {}; } catch { parsed = { raw: data }; }
|
|
288
363
|
resolve(parsed);
|
|
289
364
|
} else {
|
|
290
|
-
reject(
|
|
365
|
+
reject(buildHttpError(res, data));
|
|
291
366
|
}
|
|
292
367
|
});
|
|
293
368
|
},
|
|
294
369
|
);
|
|
295
|
-
req.on('error', reject);
|
|
370
|
+
req.on('error', err => reject(err && err.message ? err : new Error(`falha na conexão HTTP com o orchestrator: ${formatError(err)}`)));
|
|
296
371
|
req.write(payload);
|
|
297
372
|
req.end();
|
|
298
373
|
});
|
|
@@ -307,13 +382,53 @@ function ensureRunningOrExit() {
|
|
|
307
382
|
}
|
|
308
383
|
|
|
309
384
|
function printSeedRunResults(results) {
|
|
385
|
+
let missingTables = 0;
|
|
310
386
|
for (const r of results) {
|
|
311
387
|
if (r.skipped) {
|
|
312
388
|
console.log(` ⚠ ${r.tableName}: skipped (${r.reason})`);
|
|
389
|
+
if (r.reason && r.reason.includes('does not exist in LocalStack')) {
|
|
390
|
+
missingTables++;
|
|
391
|
+
}
|
|
313
392
|
} else {
|
|
314
393
|
console.log(` ✓ ${r.tableName}: ${r.inserted} item(s) inserted`);
|
|
315
394
|
}
|
|
316
395
|
}
|
|
396
|
+
return { missingTables };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Show the user both sides of the comparison: the seed files we found, and
|
|
400
|
+
// the DynamoDB tables actually living in LocalStack. This is the difference
|
|
401
|
+
// between "I didn't deploy yet" (no live tables at all) and "my seed file
|
|
402
|
+
// name doesn't match the CFN TableName" (live tables exist, just not those).
|
|
403
|
+
function printSeedMismatchDiagnostic({ entries, liveTables, focusTable }) {
|
|
404
|
+
const seedNames = entries.map(e => e.tableName);
|
|
405
|
+
const focusList = focusTable ? [focusTable] : seedNames;
|
|
406
|
+
|
|
407
|
+
console.log('');
|
|
408
|
+
if (focusTable) {
|
|
409
|
+
console.log(`📂 Arquivo de seed inspecionado: ${focusTable}.json`);
|
|
410
|
+
} else if (focusList.length > 0) {
|
|
411
|
+
console.log('📂 Arquivos de seed inspecionados (tabela esperada):');
|
|
412
|
+
for (const name of focusList) console.log(` - ${name}`);
|
|
413
|
+
} else {
|
|
414
|
+
console.log('📂 Nenhum arquivo *.json encontrado no seedsDir.');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (liveTables && liveTables.length > 0) {
|
|
418
|
+
console.log('');
|
|
419
|
+
console.log(`🗂️ Tabelas vivas no LocalStack (${liveTables.length}):`);
|
|
420
|
+
for (const name of liveTables) console.log(` - ${name}`);
|
|
421
|
+
console.log('');
|
|
422
|
+
console.log('💡 Os nomes dos arquivos de seed precisam bater EXATAMENTE com o `TableName` no CloudFormation.');
|
|
423
|
+
console.log(' Confira se há prefixo/sufixo divergente entre o arquivo e a tabela.');
|
|
424
|
+
} else {
|
|
425
|
+
console.log('');
|
|
426
|
+
console.log('🗂️ Nenhuma tabela viva no LocalStack ainda.');
|
|
427
|
+
console.log('💡 Provavelmente o stack ainda não foi provisionado. Tente:');
|
|
428
|
+
console.log(' npx lss start # garante LocalStack rodando');
|
|
429
|
+
console.log(' npx serverless deploy # cria as tabelas');
|
|
430
|
+
console.log(' E rode `npx lss seed` novamente.');
|
|
431
|
+
}
|
|
317
432
|
}
|
|
318
433
|
|
|
319
434
|
function printSeedClearResults(results) {
|
|
@@ -331,21 +446,130 @@ async function runSeed(tableName) {
|
|
|
331
446
|
try {
|
|
332
447
|
console.log(tableName ? `🌱 Seeding ${tableName}...` : '🌱 Seeding all tables with seed files...');
|
|
333
448
|
const res = await postJson('/api/seeds/run', tableName ? { tableName } : {});
|
|
334
|
-
printSeedRunResults(res.results || []);
|
|
449
|
+
const { missingTables } = printSeedRunResults(res.results || []);
|
|
450
|
+
if (missingTables > 0) {
|
|
451
|
+
try {
|
|
452
|
+
const list = await getJson('/api/seeds');
|
|
453
|
+
printSeedMismatchDiagnostic({
|
|
454
|
+
entries: (list.entries || []).filter(e => !e.tableExists),
|
|
455
|
+
liveTables: list.liveTables || [],
|
|
456
|
+
focusTable: tableName,
|
|
457
|
+
});
|
|
458
|
+
} catch (diagErr) {
|
|
459
|
+
// Diagnostic is best-effort; don't fail the seed because the hint failed.
|
|
460
|
+
console.log(`(não consegui detalhar tabelas vivas: ${formatError(diagErr)})`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
335
463
|
} catch (e) {
|
|
336
|
-
console.error('❌ Seed failed:', e
|
|
464
|
+
console.error('❌ Seed failed:', formatError(e));
|
|
337
465
|
process.exit(1);
|
|
338
466
|
}
|
|
339
467
|
}
|
|
340
468
|
|
|
469
|
+
function getJson(path) {
|
|
470
|
+
return new Promise((resolve, reject) => {
|
|
471
|
+
const http = require('http');
|
|
472
|
+
const req = http.request(
|
|
473
|
+
{
|
|
474
|
+
hostname: 'localhost',
|
|
475
|
+
port: getServerPort(),
|
|
476
|
+
path,
|
|
477
|
+
method: 'GET',
|
|
478
|
+
},
|
|
479
|
+
res => {
|
|
480
|
+
let data = '';
|
|
481
|
+
res.on('data', chunk => (data += chunk));
|
|
482
|
+
res.on('end', () => {
|
|
483
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
484
|
+
let parsed;
|
|
485
|
+
try { parsed = data ? JSON.parse(data) : {}; } catch { parsed = { raw: data }; }
|
|
486
|
+
resolve(parsed);
|
|
487
|
+
} else {
|
|
488
|
+
reject(buildHttpError(res, data));
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
);
|
|
493
|
+
req.on('error', err => reject(err && err.message ? err : new Error(`falha na conexão HTTP com o orchestrator: ${formatError(err)}`)));
|
|
494
|
+
req.end();
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function promptConfirmation(expectedWord) {
|
|
499
|
+
return new Promise(resolve => {
|
|
500
|
+
const readline = require('readline');
|
|
501
|
+
const rl = readline.createInterface({
|
|
502
|
+
input: process.stdin,
|
|
503
|
+
output: process.stdout,
|
|
504
|
+
});
|
|
505
|
+
rl.question(`Digite "${expectedWord}" para prosseguir (ou qualquer outra coisa para cancelar): `, answer => {
|
|
506
|
+
rl.close();
|
|
507
|
+
resolve(answer.trim() === expectedWord);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
341
512
|
async function clearSeed(tableName) {
|
|
342
513
|
ensureRunningOrExit();
|
|
514
|
+
|
|
515
|
+
const skipConfirm = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
516
|
+
const cfg = getConfig(loadConfig());
|
|
517
|
+
|
|
518
|
+
// Show the user exactly what's about to be wiped before they confirm.
|
|
519
|
+
// Hitting the orchestrator's GET /api/seeds also implicitly proves we're
|
|
520
|
+
// talking to LocalStack and not AWS — the orchestrator only ever connects
|
|
521
|
+
// to the configured local LocalStack endpoint.
|
|
522
|
+
let scopeDescription;
|
|
523
|
+
try {
|
|
524
|
+
const list = await getJson('/api/seeds');
|
|
525
|
+
const entries = list.entries || [];
|
|
526
|
+
const liveTables = list.liveTables || [];
|
|
527
|
+
const targets = tableName ? entries.filter(e => e.tableName === tableName) : entries;
|
|
528
|
+
const liveTargets = targets.filter(e => e.tableExists);
|
|
529
|
+
|
|
530
|
+
if (liveTargets.length === 0) {
|
|
531
|
+
if (tableName) {
|
|
532
|
+
console.log(`⚠️ Nenhuma tabela "${tableName}" existente no LocalStack para limpar.`);
|
|
533
|
+
} else if (entries.length === 0) {
|
|
534
|
+
console.log('⚠️ Nenhum arquivo de seed (*.json) encontrado no seedsDir — nada para limpar.');
|
|
535
|
+
} else {
|
|
536
|
+
console.log(`⚠️ ${entries.length} arquivo(s) de seed encontrados, mas NENHUMA das tabelas correspondentes existe no LocalStack.`);
|
|
537
|
+
}
|
|
538
|
+
printSeedMismatchDiagnostic({ entries, liveTables, focusTable: tableName });
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
scopeDescription = tableName
|
|
543
|
+
? `a tabela "${tableName}"`
|
|
544
|
+
: `${liveTargets.length} tabela(s): ${liveTargets.map(e => e.tableName).join(', ')}`;
|
|
545
|
+
|
|
546
|
+
console.log('');
|
|
547
|
+
console.log('⚠️ ATENÇÃO: operação destrutiva');
|
|
548
|
+
console.log(` Alvo: ${scopeDescription}`);
|
|
549
|
+
console.log(` LocalStack: http://localhost:${cfg.localstackPort}`);
|
|
550
|
+
console.log(' Esta ação NÃO toca em nenhuma conta AWS — apenas o LocalStack acima.');
|
|
551
|
+
console.log('');
|
|
552
|
+
} catch (e) {
|
|
553
|
+
console.error('❌ Não consegui listar as tabelas antes de limpar:', formatError(e));
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (!skipConfirm) {
|
|
558
|
+
const ok = await promptConfirmation('confirmar');
|
|
559
|
+
if (!ok) {
|
|
560
|
+
console.log('🚫 Cancelado — nenhuma alteração feita.');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
console.log('↳ --yes informado, pulando confirmação interativa.');
|
|
565
|
+
}
|
|
566
|
+
|
|
343
567
|
try {
|
|
344
568
|
console.log(tableName ? `🧹 Clearing ${tableName}...` : '🧹 Clearing all seeded tables...');
|
|
345
569
|
const res = await postJson('/api/seeds/clear', tableName ? { tableName } : {});
|
|
346
570
|
printSeedClearResults(res.results || []);
|
|
347
571
|
} catch (e) {
|
|
348
|
-
console.error('❌ Clear failed:', e
|
|
572
|
+
console.error('❌ Clear failed:', formatError(e));
|
|
349
573
|
process.exit(1);
|
|
350
574
|
}
|
|
351
575
|
}
|
|
@@ -364,14 +588,20 @@ Commands:
|
|
|
364
588
|
seed [table] Apply seed file(s) from seedsDir into DynamoDB
|
|
365
589
|
(no args = all matching tables)
|
|
366
590
|
seed:clear [table] Delete all items from the given table (or all
|
|
367
|
-
tables with a seed file when no arg is given)
|
|
591
|
+
tables with a seed file when no arg is given).
|
|
592
|
+
Pede confirmação interativa (digitar "confirmar")
|
|
593
|
+
antes de qualquer escrita.
|
|
368
594
|
help Show this help message
|
|
369
595
|
|
|
370
596
|
Options:
|
|
597
|
+
--config <path> Load config from this file (precedes the cwd/home search).
|
|
598
|
+
Applies to start/stop/status/logs so an isolated instance
|
|
599
|
+
can be addressed without cd-ing into its folder.
|
|
371
600
|
--enable-dynamo-proxy Enable DynamoDB proxy on port 8000 (for start command)
|
|
372
601
|
--external Connect to a LocalStack already running, do not spawn a container
|
|
373
602
|
--pro Use the LocalStack Pro image (requires LOCALSTACK_AUTH_TOKEN)
|
|
374
603
|
--localstack-token <token> Pass a LOCALSTACK_AUTH_TOKEN to the container
|
|
604
|
+
--yes, -y Skip interactive confirmation on seed:clear (use only in CI)
|
|
375
605
|
|
|
376
606
|
Environment:
|
|
377
607
|
LOCALSTACK_AUTH_TOKEN Token forwarded to LocalStack (Pro and >=2026.5 community)
|
|
@@ -391,9 +621,14 @@ Configuration:
|
|
|
391
621
|
"region": "us-east-1",
|
|
392
622
|
"services": ["dynamodb", "sqs", "sns", "lambda"],
|
|
393
623
|
"persistence": true,
|
|
394
|
-
"debug": false
|
|
624
|
+
"debug": false,
|
|
625
|
+
"stateDir": ".lss"
|
|
395
626
|
}
|
|
396
627
|
|
|
628
|
+
stateDir (optional): directory for this instance's PID/log files. Set it (e.g.
|
|
629
|
+
".lss-e2e") together with --config to run a fully isolated instance alongside
|
|
630
|
+
the dev one. When omitted, PID/log live in the OS temp dir scoped by serverPort.
|
|
631
|
+
|
|
397
632
|
For the Serverless Plugin, add to serverless.yml:
|
|
398
633
|
custom:
|
|
399
634
|
orchestrator:
|
|
@@ -411,7 +646,8 @@ Examples:
|
|
|
411
646
|
npx lss logs # View logs
|
|
412
647
|
npx lss seed # Seed every table that has a {name}.json file
|
|
413
648
|
npx lss seed users # Seed only the "users" table
|
|
414
|
-
npx lss seed:clear users # Delete all items from "users"
|
|
649
|
+
npx lss seed:clear users # Delete all items from "users" (com confirmação)
|
|
650
|
+
npx lss seed:clear users --yes # Mesma coisa, sem prompt (CI)
|
|
415
651
|
`);
|
|
416
652
|
}
|
|
417
653
|
|
|
@@ -430,34 +666,77 @@ function showLogs() {
|
|
|
430
666
|
console.log(lastLines);
|
|
431
667
|
}
|
|
432
668
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
669
|
+
// First positional arg after the command (e.g. table name for `seed`/`seed:clear`).
|
|
670
|
+
// Skip anything that looks like a flag so `seed:clear --yes` doesn't pass
|
|
671
|
+
// "--yes" as the table name.
|
|
672
|
+
function firstPositional() {
|
|
673
|
+
for (let i = 3; i < process.argv.length; i++) {
|
|
674
|
+
const arg = process.argv[i];
|
|
675
|
+
if (!arg.startsWith('-')) return arg;
|
|
676
|
+
}
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Exported so the pure/in-process helpers can be unit-tested by requiring this
|
|
681
|
+
// module. The command dispatch below only runs when the file is executed
|
|
682
|
+
// directly (the `lss` bin), never when required, so requiring it has no side
|
|
683
|
+
// effects.
|
|
684
|
+
module.exports = {
|
|
685
|
+
loadConfig,
|
|
686
|
+
getConfig,
|
|
687
|
+
getArgValue,
|
|
688
|
+
runtimePaths,
|
|
689
|
+
getOrchestratorPath,
|
|
690
|
+
formatError,
|
|
691
|
+
buildHttpError,
|
|
692
|
+
firstPositional,
|
|
693
|
+
getServerPort,
|
|
694
|
+
printSeedRunResults,
|
|
695
|
+
printSeedMismatchDiagnostic,
|
|
696
|
+
printSeedClearResults,
|
|
697
|
+
getJson,
|
|
698
|
+
postJson,
|
|
699
|
+
ensureRunningOrExit,
|
|
700
|
+
promptConfirmation,
|
|
701
|
+
startOrchestrator,
|
|
702
|
+
stopOrchestrator,
|
|
703
|
+
showStatus,
|
|
704
|
+
showLogs,
|
|
705
|
+
runSeed,
|
|
706
|
+
clearSeed,
|
|
707
|
+
showHelp,
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
/* istanbul ignore next: CLI dispatch runs only when executed directly, not when required in tests */
|
|
711
|
+
if (require.main === module) {
|
|
712
|
+
const command = process.argv[2];
|
|
713
|
+
switch (command) {
|
|
714
|
+
case 'start':
|
|
715
|
+
startOrchestrator();
|
|
716
|
+
break;
|
|
717
|
+
case 'stop':
|
|
718
|
+
stopOrchestrator();
|
|
719
|
+
break;
|
|
720
|
+
case 'status':
|
|
721
|
+
showStatus();
|
|
722
|
+
break;
|
|
723
|
+
case 'logs':
|
|
724
|
+
showLogs();
|
|
725
|
+
break;
|
|
726
|
+
case 'seed':
|
|
727
|
+
runSeed(firstPositional());
|
|
728
|
+
break;
|
|
729
|
+
case 'seed:clear':
|
|
730
|
+
clearSeed(firstPositional());
|
|
731
|
+
break;
|
|
732
|
+
case 'help':
|
|
733
|
+
case '--help':
|
|
734
|
+
case '-h':
|
|
735
|
+
showHelp();
|
|
736
|
+
break;
|
|
737
|
+
default:
|
|
738
|
+
console.log('❌ Unknown command:', command);
|
|
739
|
+
showHelp();
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
463
742
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamo-proxy.d.ts","sourceRoot":"","sources":["../../../src/server/dev/dynamo-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAIxB,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,SAAO,
|
|
1
|
+
{"version":3,"file":"dynamo-proxy.d.ts","sourceRoot":"","sources":["../../../src/server/dev/dynamo-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAIxB,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,SAAO,wEA4CnE"}
|
|
@@ -4,23 +4,27 @@ import http from 'http';
|
|
|
4
4
|
export function startDynamoProxy(targetEndpoint, port = 8000) {
|
|
5
5
|
const targetBase = targetEndpoint.replace(/\/$/, '');
|
|
6
6
|
const server = http.createServer((req, res) => {
|
|
7
|
+
/* istanbul ignore next: Node's http server always populates req.url; defensive guard only */
|
|
7
8
|
if (!req.url) {
|
|
8
9
|
res.statusCode = 400;
|
|
9
10
|
res.end('Bad Request');
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
13
|
const url = `${targetBase}${req.url}`;
|
|
14
|
+
/* istanbul ignore next: req.headers is always populated by Node's http server */
|
|
13
15
|
const headers = { ...(req.headers || {}) };
|
|
14
16
|
delete headers['host'];
|
|
15
17
|
const upstream = http.request(url, {
|
|
16
18
|
method: req.method,
|
|
17
19
|
headers,
|
|
18
20
|
}, upstreamRes => {
|
|
21
|
+
/* istanbul ignore next: a real upstream response always carries a statusCode */
|
|
19
22
|
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers);
|
|
20
23
|
upstreamRes.pipe(res);
|
|
21
24
|
});
|
|
22
25
|
upstream.on('error', err => {
|
|
23
26
|
console.error('Proxy error:', err);
|
|
27
|
+
/* istanbul ignore next: headers are not yet sent when the upstream connection fails */
|
|
24
28
|
if (!res.headersSent)
|
|
25
29
|
res.writeHead(502);
|
|
26
30
|
res.end('Bad Gateway');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamo-proxy.js","sourceRoot":"","sources":["../../../src/server/dev/dynamo-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,uGAAuG;AACvG,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,cAAsB,EAAE,IAAI,GAAG,IAAI;IAClE,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,UAAU,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAA8B,CAAC;QACvE,OAAQ,OAAe,CAAC,MAAM,CAAC,CAAC;QAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAC3B,GAAG,EACH;YACE,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO;SACR,EACD,WAAW,CAAC,EAAE;YACZ,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,UAAU,IAAI,GAAG,EAAE,WAAW,CAAC,OAAmC,CAAC,CAAC;YAC9F,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC,CACF,CAAC;QAEF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YACzB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACzC,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACvB,OAAO,CAAC,GAAG,CAAC,kDAAkD,IAAI,OAAO,UAAU,EAAE,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
1
|
+
{"version":3,"file":"dynamo-proxy.js","sourceRoot":"","sources":["../../../src/server/dev/dynamo-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,uGAAuG;AACvG,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,cAAsB,EAAE,IAAI,GAAG,IAAI;IAClE,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,6FAA6F;QAC7F,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,UAAU,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACtC,iFAAiF;QACjF,MAAM,OAAO,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAA8B,CAAC;QACvE,OAAQ,OAAe,CAAC,MAAM,CAAC,CAAC;QAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAC3B,GAAG,EACH;YACE,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO;SACR,EACD,WAAW,CAAC,EAAE;YACZ,gFAAgF;YAChF,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,UAAU,IAAI,GAAG,EAAE,WAAW,CAAC,OAAmC,CAAC,CAAC;YAC9F,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC,CACF,CAAC;QAEF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YACzB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;YACnC,uFAAuF;YACvF,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACzC,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACvB,OAAO,CAAC,GAAG,CAAC,kDAAkD,IAAI,OAAO,UAAU,EAAE,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queues.d.ts","sourceRoot":"","sources":["../../../src/server/routes/queues.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"queues.d.ts","sourceRoot":"","sources":["../../../src/server/routes/queues.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAwMxB,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC"}
|