local-serverless-stack 0.0.13 → 0.1.1

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +6 -6
  3. package/bin/cli.js +54 -29
  4. package/dist/server/index.js +11 -1
  5. package/dist/server/index.js.map +1 -1
  6. package/dist/server/routes/buckets.d.ts +3 -0
  7. package/dist/server/routes/buckets.d.ts.map +1 -0
  8. package/dist/server/routes/buckets.js +128 -0
  9. package/dist/server/routes/buckets.js.map +1 -0
  10. package/dist/server/routes/config.d.ts +3 -0
  11. package/dist/server/routes/config.d.ts.map +1 -0
  12. package/dist/server/routes/config.js +35 -0
  13. package/dist/server/routes/config.js.map +1 -0
  14. package/dist/server/routes/queues.d.ts.map +1 -1
  15. package/dist/server/routes/queues.js +89 -19
  16. package/dist/server/routes/queues.js.map +1 -1
  17. package/dist/server/routes/resources.d.ts.map +1 -1
  18. package/dist/server/routes/resources.js +51 -0
  19. package/dist/server/routes/resources.js.map +1 -1
  20. package/dist/server/routes/services.d.ts.map +1 -1
  21. package/dist/server/routes/services.js +27 -4
  22. package/dist/server/routes/services.js.map +1 -1
  23. package/dist/server/services/cloudformation-parser.d.ts +19 -1
  24. package/dist/server/services/cloudformation-parser.d.ts.map +1 -1
  25. package/dist/server/services/cloudformation-parser.js +55 -1
  26. package/dist/server/services/cloudformation-parser.js.map +1 -1
  27. package/dist/server/services/config-manager.js +1 -1
  28. package/dist/server/services/config-manager.js.map +1 -1
  29. package/dist/server/services/localstack-manager.d.ts.map +1 -1
  30. package/dist/server/services/localstack-manager.js +13 -4
  31. package/dist/server/services/localstack-manager.js.map +1 -1
  32. package/dist/server/services/queue-inspector.d.ts +46 -4
  33. package/dist/server/services/queue-inspector.d.ts.map +1 -1
  34. package/dist/server/services/queue-inspector.js +126 -22
  35. package/dist/server/services/queue-inspector.js.map +1 -1
  36. package/dist/server/services/resource-provisioner.d.ts +12 -1
  37. package/dist/server/services/resource-provisioner.d.ts.map +1 -1
  38. package/dist/server/services/resource-provisioner.js +265 -44
  39. package/dist/server/services/resource-provisioner.js.map +1 -1
  40. package/dist/server/services/s3-explorer.d.ts +60 -0
  41. package/dist/server/services/s3-explorer.d.ts.map +1 -0
  42. package/dist/server/services/s3-explorer.js +170 -0
  43. package/dist/server/services/s3-explorer.js.map +1 -0
  44. package/dist/ui/assets/BucketDetailPage-Hg4Gg3jC.js +1 -0
  45. package/dist/ui/assets/BucketsPage-BHkg7TQp.js +1 -0
  46. package/dist/ui/assets/DynamoPage-CH_TgPh_.js +1 -0
  47. package/dist/ui/assets/DynamoTablePage-BPvl5y6_.js +3 -0
  48. package/dist/ui/assets/OverviewPage-CwLnw4VW.css +1 -0
  49. package/dist/ui/assets/OverviewPage-nhomNDqx.js +1 -0
  50. package/dist/ui/assets/QueueDetailPage-DHDPgRDc.js +1 -0
  51. package/dist/ui/assets/QueuesPage-D5YcEbUU.js +1 -0
  52. package/dist/ui/assets/ServiceDetailPage-DxJ0t7HQ.js +2 -0
  53. package/dist/ui/assets/ServicesPage-DWDaaOz9.js +2 -0
  54. package/dist/ui/assets/index-D3WwZ4lY.js +36 -0
  55. package/dist/ui/assets/{index-BZ8sqKTK.css → index-EYnsoJOq.css} +1 -1
  56. package/dist/ui/index.html +2 -2
  57. package/package.json +2 -1
  58. package/dist/ui/assets/index-iqHgjD5V.js +0 -30
package/CHANGELOG.md CHANGED
@@ -5,6 +5,108 @@ 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.1.1] - 2026-05-21
9
+
10
+ ### Added
11
+ - **DynamoDB tables: search filter**: the DynamoDB tab now has a text input in the table-list header that filters rows by table name or owning service as you type, so finding a table no longer requires `Ctrl+F`. When a query matches nothing, an empty state shows the active query.
12
+
13
+ ## [0.1.0] - 2026-05-21
14
+
15
+ ### Added
16
+ - **S3 bucket support, end-to-end**: LSS now parses `AWS::S3::Bucket` from CloudFormation, provisions buckets in LocalStack, and (when present) wires `NotificationConfiguration.LambdaConfigurations` to a Lambda proxy that forwards into `serverless-offline`. Versioning, `Filter.S3Key.Rules` (prefix/suffix), and multiple lambda configurations per bucket are honored.
17
+ - **New service** `src/server/services/s3-explorer.ts` exposing `listBuckets`, `getBucket`, `listObjects`, `getObject`, `headObject`, `putObject`, `deleteObject`. Per-region client cache mirrors `QueueInspector`/`DynamoExplorer`. Client uses `forcePathStyle: true` so LocalStack receives the bucket name in the URL path rather than as a virtual-host subdomain.
18
+ - **New REST routes** under `/api/buckets`:
19
+ - `GET /api/buckets` — list buckets with enriched metadata (`objectCount`, `totalSize`, `region`, `createdAt`).
20
+ - `GET /api/buckets/:name` — describe a bucket (versioning, notifications count, object/size totals, region).
21
+ - `GET /api/buckets/:name/objects` — list objects with `prefix`, `delimiter`, `maxKeys`, `continuationToken` query params.
22
+ - `GET /api/buckets/:name/objects/content?key=...&download=0|1` — stream an object back. Sets `Content-Type` from the object's stored mime; `?download=1` flips to an attachment disposition. Used by the UI for preview and download.
23
+ - `POST /api/buckets/:name/objects` — upload `{ key, body, contentType?, encoding? }` (encoding `base64` flips the body decoder for binary uploads).
24
+ - `DELETE /api/buckets/:name/objects?key=...` — delete a single object.
25
+ - **New dashboard tab "S3"** in `App.vue` between Queues and DynamoDB.
26
+ - **`/buckets`** — lazy-loaded `BucketsPage.vue` wraps `BucketsView.vue`. Shows three stats (buckets, objects, total size), a service-owner column (links back to `/services/:name`), versioning + notifications counters, and refreshes every 10s.
27
+ - **`/buckets/:name`** — lazy-loaded `BucketDetailPage.vue` wraps `BucketDetail.vue`. Shows the four bucket stats, an **Upload object** card (key, optional content-type, free-form body) that POSTs through `/api/buckets/:name/objects`, and an **Objects** table with prefix filter, per-row open-in-tab preview, download, and delete (behind a confirm).
28
+ - **`@aws-sdk/client-s3`** added to the main package.
29
+ - **`/api/resources`** now returns `{ tables, queues, topics, buckets }` and **`/api/resources/owners`** returns a `buckets` array of `{ name, service }` pairs.
30
+ - **`GET /api/services`** `resourceBreakdown` now includes `buckets` count (rendered as `🪣` chip in `ServicesList.vue`).
31
+ - **Overview page** got a fifth stat column for S3 buckets and the **S3 Buckets** entry in "What's covered" is now `Supported` and links to `/buckets`.
32
+ - **Service detail page** got a Buckets stat and a clickable bucket list that navigates to `/buckets/:name`.
33
+ - `S3Resource` interface in `cloudformation-parser.ts` carries `versioningEnabled` and an array of `S3NotificationConfig { functionRef, events, filterPrefix?, filterSuffix? }`.
34
+
35
+ - **`pro-sample-microservice` example**: same surface as `sample-microservice` (Users/Sessions/Orders tables, OrderProcessing queue, OrderEvents topic, uploads bucket with S3 → Lambda notification, full handler set) but pointed at `localstack/localstack-pro:latest`. Lives on its own ports (LSS `3111`, LocalStack `4571`, offline `3010 / 3011`) so it can run side by side with the community example and an external LocalStack on `4566`. Ships with `.env.example` documenting the required `LOCALSTACK_AUTH_TOKEN` and npm scripts wrapped in `dotenv-cli` so secrets reach both LSS and `serverless-offline` without polluting the shell.
36
+
37
+ - **`.env` support in `sample-microservice`**: matching `.env.example` (with a note that recent community images ≥ 2026.5 also require a token), `.env` / `.env.local` added to `.gitignore`, and every npm script (`lss:start|stop|status|logs|seed*`, `package`, `offline`, `deploy`) wrapped in `dotenv -- ...` via the new `dotenv-cli` devDep.
38
+
39
+ - **`sample-microservice` S3 handlers**: `aws.js` now exports an `S3Client` (with `forcePathStyle: true`), and three new handlers exercise the round-trip:
40
+ - `uploadFile.js` — `POST /uploads` → puts an object under `incoming/{filename}` (supports `encoding: "base64"`).
41
+ - `listUploads.js` — `GET /uploads?prefix=` → lists objects with metadata.
42
+ - `onUpload.js` — bound to the bucket's `s3:ObjectCreated:*` notification under the `incoming/` prefix; reads the object back and writes a synthetic row into the `Orders` table (`userId=s3-upload`) so the trigger is visible from the DynamoDB explorer.
43
+ - `serverless.yml` declares the bucket, the notification (with `Filter.S3Key.Rules` prefix), and adds the three corresponding `functions:`. Env var `UPLOADS_BUCKET` injected for the handlers.
44
+
45
+ ### Changed
46
+ - **`bin/cli.js` PID and log files are now scoped to `serverPort`**: `/tmp/lss-orchestrator-{serverPort}.{pid,log}` instead of the previous global `/tmp/lss-orchestrator.{pid,log}`. The legacy global path is still used when `serverPort` is the default `3100`, so existing single-project setups keep working. This removes the old "PID file is global across all working directories" gotcha — multiple LSS instances (one per example/project) can now coexist on the same machine.
47
+ - **`LocalStackManager` container and volume are scoped to the LocalStack port**: container name is `lss-localstack` for the default `4566` and `lss-localstack-{port}` otherwise; the persistence volume follows the same scheme (`{containerName}-data`). Two examples no longer collide on either the Docker container name or the persistence volume.
48
+ - **Removed the hardcoded `-p 4571:4571` extra port binding** from the LocalStack Docker invocation. It clashed with examples whose LocalStack port is `4571`, and the legacy "edge" port it exposed is obsolete in modern LocalStack (everything flows through the primary port).
49
+ - **`sample-microservice` ports moved off the defaults**: LSS dashboard now on `3110` (was `3101`), LocalStack now on `4570` (was `4566`). Avoids colliding with an external LocalStack on `4566`. The `serverless-offline` ports (`3000`/`3001`) are unchanged.
50
+ - **`sample-microservice` package.json**: added `dotenv-cli` to `devDependencies`; every script wrapped in `dotenv -- ...`. Updated description to mention S3.
51
+ - **Resource breakdown / owner types** in `src/ui/src/services/api.ts` gained the `buckets` fields. The `ServiceResource.type` union now includes `'s3'`, `ResourceBreakdown` includes `buckets: number`, and `ResourceOwnersResponse` includes `buckets: ResourceOwner[]`. Existing `.catch(() => ({...}))` fallbacks in `QueuesView.vue` and `DynamoTablesList.vue` were widened to include the new field.
52
+
53
+ ### Fixed
54
+ - **`resolveLambdaName` now reads `FunctionName` straight from the parsed CFN** instead of hand-crafting `${service}-api-${shortName}`. The old heuristic only worked when the user's stage happened to be `api`; for any other stage (e.g. the default `dev`) the LocalStack proxy would forward to a name `serverless-offline` doesn't recognize and the invocation 404'd with `Function does not exist`. Both the LocalStack proxy and its invoke URL now use the same fully-qualified name (`pro-sample-microservice-dev-onUpload`) that `serverless-offline` exposes on its `lambdaPort`. The old `resolveLambdaFunctionName` helper was removed; `shortFunctionName` is kept only as a last-resort fallback when a logical id isn't present in the parsed CFN.
55
+ - **Event source ARN resolution uses CFN `logicalId` instead of kebab-casing**: `Fn::GetAtt: [OrderProcessingQueue, Arn]` now looks up `OrderProcessingQueue` directly in a logical-id → resource map populated from the parsed template, then derives the real ARN from the resource's actual `QueueName` / `TableName` / `TopicName`. The previous kebab-case match (`order-processing-queue` vs `service-OrderProcessing`) silently failed whenever the user set an explicit `QueueName`/`TableName`, leaving SQS and DynamoDB stream consumers completely unwired.
56
+ - **DynamoDB stream ARN is now fetched via `DescribeTable.LatestStreamArn`** (with up to 5 × 300ms retries while the stream provisions), rather than hand-crafting `arn:aws:dynamodb:...:table/X/stream/NEW_AND_OLD_IMAGES` — the real ARN has a timestamp suffix and the hand-crafted one was always rejected with `Stream not found`.
57
+ - **`CreateEventSourceMappingCommand` now sets `StartingPosition: 'TRIM_HORIZON'` for stream sources** (any resolved ARN starting with `arn:aws:dynamodb:` or `arn:aws:kinesis:`). DynamoDB Streams and Kinesis require this field; SQS forbids it, so the flag is `undefined` for SQS mappings.
58
+ - **CloudFormation parser detects DynamoDB streams via `StreamViewType`** (the field that actually lives in `AWS::DynamoDB::Table.StreamSpecification`) instead of the non-existent `StreamEnabled` boolean. Before, every parsed table came back with `streamEnabled: false`, which meant LSS created the table without streams even when CFN asked for them.
59
+ - **S3 `PutBucketNotificationConfiguration` now passes `SkipDestinationValidation: true`**. LocalStack Pro implements the same synchronous test-invoke validation real S3 does, and was rejecting our config with `Unable to validate the following destination configurations` because the `lambda:InvokeFunction` permission for `s3.amazonaws.com` hadn't fully propagated by the time we wrote the notification. The flag tells S3 to trust the permission we just added rather than probing the destination. (Community LocalStack was lax about this; Pro isn't.)
60
+ - **`ServerlessDeploymentBucket` is now skipped at parse time**. Serverless Framework injects this `AWS::S3::Bucket` for its own deployment artifacts; its name lives behind CloudFormation pseudo-parameters LSS doesn't resolve, so every registration was logging `Failed to provision s3:ServerlessDeploymentBucket: The specified bucket is not valid`. It isn't useful for local dev and is now filtered before provisioning ever runs.
61
+ - **`provisionResources` rebuilds the logical-id map on each call**, so the fixes above don't leak resource lookups across services when the same `ResourceProvisioner` singleton is reused for multiple registrations.
62
+
63
+ ### Documentation
64
+ - **`README.md`**: feature list now mentions S3, LocalStack `SERVICES` example updated to `dynamodb,sqs,sns,s3,lambda`, and the "CLI Implementation Details" section reflects the per-port PID/log paths.
65
+ - **`docs/CONFIGURATION.md`**: default `services` array now includes `s3`, examples updated.
66
+ - **`examples/sample-microservice/README.md`**: replaced the "PID file is global" warning with the new ports table; added an S3 section to the HTTP examples and the "Things to play with in the UI" tour.
67
+ - **`examples/pro-sample-microservice/README.md`**: brand-new walkthrough — prerequisites, token setup, port table, run/reset commands, diff summary against the community example.
68
+ - **Example configs** (`lss.config.json`, `lss.config.json.example`) now list `s3` in the default services array.
69
+
70
+ ## [0.0.15] - 2026-05-21
71
+
72
+ ### Added
73
+ - **Queue detail page with AWS-style send/receive workflow**: clicking a queue in `/queues` now opens `/queues/:name` (lazy-loaded, follows the same routed-page + tabs pattern as the DynamoDB explorer) instead of the old read-only modal. The new page exposes three tabs:
74
+ - **Send & receive** — the headline upgrade. Send arbitrary messages (body, optional `DelaySeconds`, message attributes with `String`/`Number`/`Binary` types). For FIFO queues the form auto-switches to `MessageGroupId` + optional `MessageDeduplicationId` and hides the `DelaySeconds` field (FIFO rejects it). Poll the queue with configurable `MaxNumberOfMessages` (1–10), `VisibilityTimeout`, and `WaitTimeSeconds` (long polling, 0–20). Received messages appear in a table with body preview, message ID, `SentTimestamp`, attribute count, and expandable JSON pretty-print of the full body plus AWS-side attributes (sender, receive count, etc.) and message attributes. Each row supports **Copy body** to clipboard and **Delete** by `ReceiptHandle`. A **Purge queue** action lives under the poll panel, behind a `TConfirmDialog` (warns about the AWS 60s rate limit).
75
+ - **Consumers** — the existing Lambda event-source mapping list, promoted to a dedicated tab.
76
+ - **Attributes** — Identity (queue URL + ARN), Configuration (FIFO, visibility timeout, retention, delayed counter, created/last-polled timestamps), and a Throughput card with the processed-share progress bar and the **Reset processed counter** action that used to live in the modal footer.
77
+ - **`POST /api/queues/:name/messages`**: send a message. Body is `{ body, delaySeconds?, messageAttributes?: [{ name, type, value }], messageGroupId?, messageDeduplicationId? }`. Validates `delaySeconds` is 0–900, ignores it for FIFO queues, and auto-injects `messageGroupId: 'default'` when the queue is FIFO and the field is omitted.
78
+ - **`POST /api/queues/:name/messages/receive`**: poll. Body is `{ maxNumberOfMessages?, visibilityTimeout?, waitTimeSeconds? }` (clamped server-side to 1–10 and 0–20). Returns `{ messages: SqsMessage[] }` with `messageId`, `receiptHandle`, `body`, `md5OfBody`, AWS `attributes` (`SentTimestamp`, `ApproximateReceiveCount`, etc.) and `messageAttributes`.
79
+ - **`POST /api/queues/:name/messages/delete`**: delete a single message by `receiptHandle`.
80
+ - **`POST /api/queues/:name/purge`**: PurgeQueue passthrough.
81
+ - API client helpers `sendQueueMessage`, `receiveQueueMessages`, `deleteQueueMessage`, `purgeQueue` in `src/ui/src/services/api.ts`, plus the supporting `SendQueueMessageInput` / `ReceiveQueueMessagesInput` / `SqsMessage` / `SqsMessageAttributeInput` types.
82
+
83
+ ### Changed
84
+ - `QueueInspector` now keeps a per-region cache of `SQSClient` and `LambdaClient` instances (same pattern `DynamoExplorer` already uses). `listQueues`, `getQueue`, and the new send/receive/delete/purge methods all accept an optional `region` argument, and every `/api/queues/*` route forwards `?region=` through. Background metric polling still uses the default region.
85
+ - `QueuesView.vue` row click and the trailing **Details** button now navigate to `/queues/:name` instead of opening the in-page modal. The modal, the local `selectedQueueName` state, and the modal-only "Reset processed counter" button were removed from the list view; the reset action lives on the new Attributes tab.
86
+
87
+ ## [0.0.14] - 2026-05-20
88
+
89
+ ### Added
90
+ - **Full-screen dashboard with URL-based navigation**: the Vue dashboard now uses `vue-router` instead of a single in-memory tab state. Every section is a real URL — `/`, `/services`, `/services/:name`, `/queues`, `/dynamo`, `/dynamo/:name` — so views are bookmarkable, shareable, and the browser back/forward buttons work as expected. DynamoDB table sub-tabs (Items / Indexes / Settings / Seed) are persisted via `?tab=...` so deep links land on the exact sub-view. Pages are code-split (lazy-loaded) so the initial bundle stays small.
91
+ - **Landing-style Overview page**: replaces the old "Resources" tab. Shows a project pitch hero, a live "Server status" card (LocalStack running, Dynamo Proxy enabled/listening, Auto-package on/off, Persistence on/off), an "LESC configuration" card (default region, server port, enabled LocalStack services, seeds dir, active config file path), four totalizers (services running/total, tables, queues, topics) and a "What's covered" panel listing supported resource types — `✓ SNS Topics`, `✓ SQS Queues`, `✓ DynamoDB Tables`, `⏳ S3 Buckets` (planned).
92
+ - **`/services/:name` detail page**: lifecycle controls (Start / Stop / Logs / Delete / Refresh), metadata block (path, region, invoke port, PID, last updated), and resources grouped by type (Lambda functions, DynamoDB tables, SQS queues, SNS topics, event-source mappings). Each declared DynamoDB table or SQS queue is a clickable tag that navigates to its detail view.
93
+ - **"Service" column on the Queues and DynamoDB lists**: each row now shows which microservice declared that queue/table, with a tag that links back to the service detail page. Resources not declared by any registered service render as `unmanaged` / `—`. Powered by a new `GET /api/resources/owners` endpoint that joins the cached CloudFormation templates and filters by region.
94
+ - **Seeds embedded in the DynamoDB explorer**: the standalone "Seeds" tab is gone. Tables that exist only as a seed file (no live table yet) now appear as **ghost rows** in the DynamoDB tables list (reduced opacity, `Not created` badge, "Register service to provision" hint). When a table has a matching seed file, its detail view gets a new **"Seed" sub-tab** with three actions: **Apply** (insert seed items), **Redo** (purge + re-apply, behind a confirmation), and **Purge** (delete every item in the table). Each destructive action goes through a `TConfirmDialog`.
95
+ - **`GET /api/config`**: exposes the runtime LSS configuration snapshot to the UI — `serverPort`, `localstack` (mode/endpoint/port/edition/version/image and `hasAuthToken: boolean`), `dynamoProxy.enabled/port`, `region`, `services`, `persistence`, `debug`, `seedsDir`, `autoPackage`, `packageCommand`, `packageTimeoutMs`, and `configPath`. The actual LocalStack auth token is never returned, only whether one is set.
96
+ - **`GET /api/resources/owners`**: returns `{ tables, queues, topics }` where each entry is `{ name, service }`, computed by parsing each cached CloudFormation template. Respects `?region=` and filters owner mappings to services registered in the requested region.
97
+ - `GET /api/services` now includes `resourceBreakdown: { lambdas, tables, queues, topics }` so the Services table can render per-type chips (`λ`/`🗄`/`📨`/`📣`) instead of a single opaque resource count.
98
+ - `GET /api/health` response now includes a `dynamoProxy: { enabled, running, port }` block so the navbar and Overview can show whether the proxy is actually listening, not just whether it was configured.
99
+
100
+ ### Changed
101
+ - **Dashboard uses the full viewport width**. The old `<TContainer size="xl">` cap (~1200px) is gone — wide monitors no longer waste half the screen on margins. A sticky secondary nav bar sits under the navbar with `RouterLink`s for the top-level sections.
102
+ - The DynamoDB tables view was refactored from a 2-column card grid to a denser **table layout** to accommodate the new Service and Seed columns and the ghost-row affordance.
103
+ - The DynamoDB Proxy status is now surfaced as a soft badge in the navbar (when enabled) alongside the existing LocalStack status badge.
104
+ - `Service` typing in the UI moved from inline `interface Service` to the shared `ServiceSummary` / `ServiceDetail` / `ResourceBreakdown` / `ResourceOwner` types in `src/ui/src/services/api.ts`, and the API client gained `getConfig()`, `listResourceOwners()` helpers.
105
+
106
+ ### Removed
107
+ - The standalone **"Seeds" tab** and its top-level dashboard entry. All seed actions are now reached from inside the relevant DynamoDB table's detail view, and unprovisioned seeds are visible as ghost rows in the DynamoDB tables list.
108
+ - `src/ui/src/components/ResourcesOverview.vue`, `src/ui/src/components/SeedsPanel.vue`, and `src/ui/src/components/dynamo/DynamoTab.vue` — replaced by the new routed pages (`pages/OverviewPage.vue`, `pages/DynamoPage.vue` + `pages/DynamoTablePage.vue`) and the new `components/dynamo/DynamoSeedPanel.vue`.
109
+
8
110
  ## [0.0.13] - 2026-05-19
9
111
 
10
112
  ### Added
@@ -12,6 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
114
 
13
115
  ### Changed
14
116
  - `POST /api/services/register` now returns a clear `400` with an actionable message (`"CloudFormation template not found at ... Run 'serverless package' in the service directory, or enable autoPackage in lss.config.json."`) when the template is missing and `autoPackage` is disabled, instead of leaking the underlying `ENOENT` stack trace as a `500`.
117
+ - Auto-package failures now log the full stdout/stderr of the package command to the orchestrator log (`/tmp/lss-orchestrator.log`) with delimiter lines, so failures from `serverless-webpack`, stage validators, missing params, etc. are diagnosable. The HTTP response now points users to the orchestrator log for the full transcript.
15
118
  - The configuration summary printed at startup now includes the active `autoPackage` and (when enabled) `packageCommand` values.
16
119
 
17
120
  ## [0.0.12] - 2026-05-19
package/README.md CHANGED
@@ -8,7 +8,7 @@ LSS provides a unified local development environment for serverless microservice
8
8
 
9
9
  ## Features
10
10
 
11
- - **Centralized LocalStack**: Single LocalStack instance manages DynamoDB, SQS, SNS, and Lambda
11
+ - **Centralized LocalStack**: Single LocalStack instance manages DynamoDB, SQS, SNS, S3, and Lambda
12
12
  - **Auto-provisioning**: Parses CloudFormation templates from `sls package` and provisions resources automatically
13
13
  - **Event source mappings**: Automatically connects SQS queues to Lambda handlers via LocalStack
14
14
  - **Lambda proxies**: Generated proxy functions forward events to serverless-offline invoke endpoints
@@ -218,7 +218,7 @@ local-serverless-stack/
218
218
  ### LocalStack Settings
219
219
 
220
220
  LocalStack is configured with:
221
- - Services: `dynamodb,sqs,sns,lambda`
221
+ - Services: `dynamodb,sqs,sns,s3,lambda`
222
222
  - Persistence: Enabled (volume: `lss-localstack-data`)
223
223
  - Lambda executor: `local` (no Docker-in-Docker required)
224
224
  - Docker socket: Mounted from host
@@ -482,15 +482,15 @@ sequenceDiagram
482
482
  The `npx lss` CLI is implemented in `/bin/cli.js` and provides:
483
483
 
484
484
  - **Background Process Management**: Uses `spawn` with `detached: true` to run orchestrator independently
485
- - **PID File**: Stores process ID in `/tmp/lss-orchestrator.pid`
486
- - **Log File**: Redirects stdout/stderr to `/tmp/lss-orchestrator.log`
485
+ - **PID File**: Stores process ID in `/tmp/lss-orchestrator-{serverPort}.pid` (or `/tmp/lss-orchestrator.pid` when serverPort is the default 3100). The port-scoped path lets multiple LSS instances coexist — one per project — without trampling each other.
486
+ - **Log File**: Redirects stdout/stderr to `/tmp/lss-orchestrator-{serverPort}.log` (or `/tmp/lss-orchestrator.log` for the default port).
487
487
  - **Process Monitoring**: Checks if process is alive before starting/stopping
488
488
  - **Clean Shutdown**: Sends SIGTERM for graceful termination
489
489
 
490
490
  ### Files
491
491
 
492
- - **PID File**: `/tmp/lss-orchestrator.pid`
493
- - **Log File**: `/tmp/lss-orchestrator.log`
492
+ - **PID File**: `/tmp/lss-orchestrator-{serverPort}.pid`
493
+ - **Log File**: `/tmp/lss-orchestrator-{serverPort}.log`
494
494
 
495
495
  ## Troubleshooting
496
496
 
package/bin/cli.js CHANGED
@@ -5,8 +5,27 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
7
 
8
- const PID_FILE = path.join(os.tmpdir(), 'lss-orchestrator.pid');
9
- const LOG_FILE = path.join(os.tmpdir(), 'lss-orchestrator.log');
8
+ // Default paths used when no config is present. The actual paths are derived
9
+ // per-invocation by runtimePaths() below, scoped to the configured serverPort
10
+ // so multiple examples (each with their own lss.config.json) can run in parallel.
11
+ const DEFAULT_PID_FILE = path.join(os.tmpdir(), 'lss-orchestrator.pid');
12
+ const DEFAULT_LOG_FILE = path.join(os.tmpdir(), 'lss-orchestrator.log');
13
+
14
+ // PID/log file paths scoped to the cwd's serverPort. Multiple examples sitting
15
+ // in different folders no longer trample each other.
16
+ function runtimePaths() {
17
+ const cfg = getConfig(loadConfig());
18
+ const port = cfg.serverPort;
19
+ // Keep the legacy global path when the default port is in use so existing
20
+ // installations don't lose their running process across an upgrade.
21
+ if (!port || port === 3100) {
22
+ return { pidFile: DEFAULT_PID_FILE, logFile: DEFAULT_LOG_FILE };
23
+ }
24
+ return {
25
+ pidFile: path.join(os.tmpdir(), `lss-orchestrator-${port}.pid`),
26
+ logFile: path.join(os.tmpdir(), `lss-orchestrator-${port}.log`),
27
+ };
28
+ }
10
29
 
11
30
  /**
12
31
  * Load configuration from lss.config.json or .lssrc
@@ -82,8 +101,10 @@ function getOrchestratorPath() {
82
101
  }
83
102
 
84
103
  function startOrchestrator() {
85
- if (fs.existsSync(PID_FILE)) {
86
- const pid = fs.readFileSync(PID_FILE, 'utf8').trim();
104
+ const { pidFile, logFile } = runtimePaths();
105
+
106
+ if (fs.existsSync(pidFile)) {
107
+ const pid = fs.readFileSync(pidFile, 'utf8').trim();
87
108
  try {
88
109
  process.kill(pid, 0);
89
110
  const config = loadConfig();
@@ -96,7 +117,7 @@ function startOrchestrator() {
96
117
  }
97
118
  return;
98
119
  } catch (e) {
99
- fs.unlinkSync(PID_FILE);
120
+ fs.unlinkSync(pidFile);
100
121
  }
101
122
  }
102
123
 
@@ -112,7 +133,7 @@ function startOrchestrator() {
112
133
  process.exit(1);
113
134
  }
114
135
 
115
- const logFd = fs.openSync(LOG_FILE, 'a');
136
+ const logFd = fs.openSync(logFile, 'a');
116
137
 
117
138
  // Load config
118
139
  const config = loadConfig();
@@ -167,57 +188,59 @@ function startOrchestrator() {
167
188
  child.unref();
168
189
 
169
190
  fs.closeSync(logFd);
170
- fs.writeFileSync(PID_FILE, child.pid.toString());
171
-
191
+ fs.writeFileSync(pidFile, child.pid.toString());
192
+
172
193
  console.log('🚀 LSS Orchestrator started (PID:', child.pid + ')');
173
194
  console.log(`📊 Server: http://localhost:${cfg.serverPort}`);
174
195
  console.log(`🔧 LocalStack: http://localhost:${cfg.localstackPort} (mode: ${mode}, edition: ${edition})`);
175
196
  if (enableDynamoProxy) {
176
197
  console.log(`🔄 DynamoDB Proxy: http://localhost:${cfg.dynamoProxyPort} (enabled)`);
177
198
  }
178
- console.log('📝 Logs:', LOG_FILE);
179
-
199
+ console.log('📝 Logs:', logFile);
200
+
180
201
  setTimeout(() => {
181
202
  try {
182
203
  process.kill(child.pid, 0);
183
204
  console.log('✅ Service is running');
184
205
  } catch (e) {
185
- console.error('❌ Service failed to start. Check logs:', LOG_FILE);
186
- if (fs.existsSync(PID_FILE)) {
187
- fs.unlinkSync(PID_FILE);
206
+ console.error('❌ Service failed to start. Check logs:', logFile);
207
+ if (fs.existsSync(pidFile)) {
208
+ fs.unlinkSync(pidFile);
188
209
  }
189
210
  }
190
211
  }, 2000);
191
212
  }
192
213
 
193
214
  function stopOrchestrator() {
194
- if (!fs.existsSync(PID_FILE)) {
215
+ const { pidFile } = runtimePaths();
216
+ if (!fs.existsSync(pidFile)) {
195
217
  console.log('⚠️ LSS Orchestrator is not running');
196
218
  return;
197
219
  }
198
220
 
199
- const pid = fs.readFileSync(PID_FILE, 'utf8').trim();
200
-
221
+ const pid = fs.readFileSync(pidFile, 'utf8').trim();
222
+
201
223
  try {
202
224
  process.kill(pid, 'SIGTERM');
203
- fs.unlinkSync(PID_FILE);
225
+ fs.unlinkSync(pidFile);
204
226
  console.log('🛑 LSS Orchestrator stopped (PID:', pid + ')');
205
227
  } catch (e) {
206
228
  console.error('❌ Failed to stop process:', e.message);
207
- if (fs.existsSync(PID_FILE)) {
208
- fs.unlinkSync(PID_FILE);
229
+ if (fs.existsSync(pidFile)) {
230
+ fs.unlinkSync(pidFile);
209
231
  }
210
232
  }
211
233
  }
212
234
 
213
235
  function showStatus() {
214
- if (!fs.existsSync(PID_FILE)) {
236
+ const { pidFile, logFile } = runtimePaths();
237
+ if (!fs.existsSync(pidFile)) {
215
238
  console.log('⚪ LSS Orchestrator: NOT RUNNING');
216
239
  return;
217
240
  }
218
241
 
219
- const pid = fs.readFileSync(PID_FILE, 'utf8').trim();
220
-
242
+ const pid = fs.readFileSync(pidFile, 'utf8').trim();
243
+
221
244
  try {
222
245
  process.kill(pid, 0);
223
246
  const config = loadConfig();
@@ -228,10 +251,10 @@ function showStatus() {
228
251
  if (cfg.enableDynamoProxy) {
229
252
  console.log(`🔄 DynamoDB Proxy: http://localhost:${cfg.dynamoProxyPort} (enabled)`);
230
253
  }
231
- console.log('📝 Logs:', LOG_FILE);
254
+ console.log('📝 Logs:', logFile);
232
255
  } catch (e) {
233
256
  console.log('⚪ LSS Orchestrator: NOT RUNNING (stale PID file)');
234
- fs.unlinkSync(PID_FILE);
257
+ fs.unlinkSync(pidFile);
235
258
  }
236
259
  }
237
260
 
@@ -276,7 +299,8 @@ function postJson(path, body) {
276
299
  }
277
300
 
278
301
  function ensureRunningOrExit() {
279
- if (!fs.existsSync(PID_FILE)) {
302
+ const { pidFile } = runtimePaths();
303
+ if (!fs.existsSync(pidFile)) {
280
304
  console.error('❌ LSS Orchestrator is not running. Start it with: npx lss start');
281
305
  process.exit(1);
282
306
  }
@@ -392,14 +416,15 @@ Examples:
392
416
  }
393
417
 
394
418
  function showLogs() {
395
- if (!fs.existsSync(LOG_FILE)) {
419
+ const { logFile } = runtimePaths();
420
+ if (!fs.existsSync(logFile)) {
396
421
  console.log('⚠️ No logs found');
397
422
  return;
398
423
  }
399
-
400
- console.log('📝 Logs from:', LOG_FILE);
424
+
425
+ console.log('📝 Logs from:', logFile);
401
426
  console.log('---');
402
- const logs = fs.readFileSync(LOG_FILE, 'utf8');
427
+ const logs = fs.readFileSync(logFile, 'utf8');
403
428
  const lines = logs.split('\n');
404
429
  const lastLines = lines.slice(-50).join('\n');
405
430
  console.log(lastLines);
@@ -5,8 +5,10 @@ import { fileURLToPath } from 'url';
5
5
  import { servicesRouter, processManager } from './routes/services.js';
6
6
  import { resourcesRouter } from './routes/resources.js';
7
7
  import { queuesRouter } from './routes/queues.js';
8
+ import { bucketsRouter } from './routes/buckets.js';
8
9
  import { seedsRouter } from './routes/seeds.js';
9
10
  import { dynamoRouter } from './routes/dynamo.js';
11
+ import { configRouter } from './routes/config.js';
10
12
  import { LocalStackManager } from './services/localstack-manager.js';
11
13
  import { ConfigManager } from './services/config-manager.js';
12
14
  import { QueueInspector } from './services/queue-inspector.js';
@@ -16,6 +18,7 @@ const __dirname = path.dirname(__filename);
16
18
  const app = express();
17
19
  const configManager = ConfigManager.getInstance();
18
20
  const PORT = configManager.getDashboardPort();
21
+ let dynamoProxyServer = null;
19
22
  // Middleware
20
23
  app.use(cors());
21
24
  app.use(express.json());
@@ -23,13 +26,20 @@ app.use(express.json());
23
26
  app.use('/api/services', servicesRouter);
24
27
  app.use('/api/resources', resourcesRouter);
25
28
  app.use('/api/queues', queuesRouter);
29
+ app.use('/api/buckets', bucketsRouter);
26
30
  app.use('/api/seeds', seedsRouter);
27
31
  app.use('/api/dynamo', dynamoRouter);
32
+ app.use('/api/config', configRouter);
28
33
  // Health check
29
34
  app.get('/api/health', (_req, res) => {
30
35
  res.json({
31
36
  status: 'ok',
32
37
  localstack: LocalStackManager.getInstance().isRunning(),
38
+ dynamoProxy: {
39
+ enabled: configManager.isEnableDynamoProxy(),
40
+ running: Boolean(dynamoProxyServer?.listening),
41
+ port: configManager.getDynamoProxyPort(),
42
+ },
33
43
  });
34
44
  });
35
45
  // Serve frontend build (fallback for SPA)
@@ -54,7 +64,7 @@ async function start() {
54
64
  // Optional DynamoDB proxy
55
65
  if (configManager.isEnableDynamoProxy()) {
56
66
  const proxyPort = configManager.getDynamoProxyPort();
57
- startDynamoProxy(localstack.getEndpoint(), proxyPort);
67
+ dynamoProxyServer = startDynamoProxy(localstack.getEndpoint(), proxyPort);
58
68
  }
59
69
  // Print configuration summary
60
70
  configManager.printSummary();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export { router as bucketsRouter };
3
+ //# sourceMappingURL=buckets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buckets.d.ts","sourceRoot":"","sources":["../../../src/server/routes/buckets.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA+HxB,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,CAAC"}
@@ -0,0 +1,128 @@
1
+ import { Router } from 'express';
2
+ import { S3Explorer } from '../services/s3-explorer.js';
3
+ const router = Router();
4
+ const explorer = S3Explorer.getInstance();
5
+ // S3 bucket name rules are stricter than DNS, but the only constraints we enforce here
6
+ // are anti-traversal — actual validation happens server-side at LocalStack.
7
+ function isValidBucketName(name) {
8
+ return (typeof name === 'string' &&
9
+ name.length > 0 &&
10
+ name.length <= 63 &&
11
+ !name.includes('/') &&
12
+ !name.includes('..') &&
13
+ !name.includes('\\'));
14
+ }
15
+ function isValidObjectKey(key) {
16
+ return typeof key === 'string' && key.length > 0 && key.length <= 1024 && !key.includes('..');
17
+ }
18
+ function fail(res, error, status = 500) {
19
+ const message = error instanceof Error ? error.message : 'Unknown error';
20
+ return res.status(status).json({ error: message });
21
+ }
22
+ function getRegion(req) {
23
+ const r = req.query.region;
24
+ return typeof r === 'string' && r ? r : undefined;
25
+ }
26
+ router.get('/', async (req, res) => {
27
+ try {
28
+ const buckets = await explorer.listBuckets(getRegion(req));
29
+ res.json(buckets);
30
+ }
31
+ catch (error) {
32
+ fail(res, error);
33
+ }
34
+ });
35
+ router.get('/:name', async (req, res) => {
36
+ const name = req.params.name;
37
+ if (!isValidBucketName(name))
38
+ return fail(res, new Error('Invalid bucket name'), 400);
39
+ try {
40
+ const bucket = await explorer.getBucket(name, getRegion(req));
41
+ if (!bucket)
42
+ return res.status(404).json({ error: 'Bucket not found' });
43
+ return res.json(bucket);
44
+ }
45
+ catch (error) {
46
+ return fail(res, error);
47
+ }
48
+ });
49
+ router.get('/:name/objects', async (req, res) => {
50
+ const name = req.params.name;
51
+ if (!isValidBucketName(name))
52
+ return fail(res, new Error('Invalid bucket name'), 400);
53
+ const prefix = typeof req.query.prefix === 'string' ? req.query.prefix : undefined;
54
+ const continuationToken = typeof req.query.continuationToken === 'string' ? req.query.continuationToken : undefined;
55
+ const delimiter = typeof req.query.delimiter === 'string' ? req.query.delimiter : undefined;
56
+ const maxKeysRaw = req.query.maxKeys;
57
+ const maxKeys = typeof maxKeysRaw === 'string' ? parseInt(maxKeysRaw, 10) : undefined;
58
+ try {
59
+ const result = await explorer.listObjects(name, { prefix, continuationToken, maxKeys, delimiter }, getRegion(req));
60
+ return res.json(result);
61
+ }
62
+ catch (error) {
63
+ return fail(res, error);
64
+ }
65
+ });
66
+ // Fetch the raw object body. Used by the UI to preview text or download files.
67
+ router.get('/:name/objects/content', async (req, res) => {
68
+ const name = req.params.name;
69
+ if (!isValidBucketName(name))
70
+ return fail(res, new Error('Invalid bucket name'), 400);
71
+ const key = req.query.key;
72
+ if (!isValidObjectKey(key))
73
+ return fail(res, new Error('Invalid object key'), 400);
74
+ try {
75
+ const obj = await explorer.getObject(name, key, getRegion(req));
76
+ if (!obj)
77
+ return res.status(404).json({ error: 'Object not found' });
78
+ // ?download=1 forces an attachment disposition; otherwise inline view.
79
+ const wantDownload = req.query.download === '1' || req.query.download === 'true';
80
+ if (obj.contentType)
81
+ res.setHeader('Content-Type', obj.contentType);
82
+ if (wantDownload) {
83
+ const fname = key.split('/').pop() || 'download';
84
+ res.setHeader('Content-Disposition', `attachment; filename="${fname}"`);
85
+ }
86
+ res.setHeader('Content-Length', String(obj.size));
87
+ return res.send(obj.body);
88
+ }
89
+ catch (error) {
90
+ return fail(res, error);
91
+ }
92
+ });
93
+ router.post('/:name/objects', async (req, res) => {
94
+ const name = req.params.name;
95
+ if (!isValidBucketName(name))
96
+ return fail(res, new Error('Invalid bucket name'), 400);
97
+ const { key, body, contentType, encoding } = req.body ?? {};
98
+ if (!isValidObjectKey(key))
99
+ return fail(res, new Error('key (string) is required'), 400);
100
+ if (typeof body !== 'string')
101
+ return fail(res, new Error('body (string) is required'), 400);
102
+ // encoding="base64" lets the UI upload binary content via a JSON body.
103
+ const payload = encoding === 'base64' ? Buffer.from(body, 'base64') : body;
104
+ try {
105
+ await explorer.putObject(name, { key, body: payload, contentType }, getRegion(req));
106
+ return res.json({ success: true });
107
+ }
108
+ catch (error) {
109
+ return fail(res, error, 400);
110
+ }
111
+ });
112
+ router.delete('/:name/objects', async (req, res) => {
113
+ const name = req.params.name;
114
+ if (!isValidBucketName(name))
115
+ return fail(res, new Error('Invalid bucket name'), 400);
116
+ const key = req.query.key;
117
+ if (!isValidObjectKey(key))
118
+ return fail(res, new Error('Invalid object key'), 400);
119
+ try {
120
+ await explorer.deleteObject(name, key, getRegion(req));
121
+ return res.json({ success: true });
122
+ }
123
+ catch (error) {
124
+ return fail(res, error, 400);
125
+ }
126
+ });
127
+ export { router as bucketsRouter };
128
+ //# sourceMappingURL=buckets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buckets.js","sourceRoot":"","sources":["../../../src/server/routes/buckets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export { router as configRouter };
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/server/routes/config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAiCxB,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { Router } from 'express';
2
+ import { ConfigManager } from '../services/config-manager.js';
3
+ const router = Router();
4
+ // Public-safe LSS configuration snapshot for the UI overview.
5
+ // Never expose the LocalStack auth token — only whether it's set.
6
+ router.get('/', (_req, res) => {
7
+ const cm = ConfigManager.getInstance();
8
+ res.json({
9
+ serverPort: cm.getServerPort(),
10
+ localstack: {
11
+ mode: cm.getMode(),
12
+ endpoint: cm.getLocalStackEndpoint(),
13
+ port: cm.getLocalStackPort(),
14
+ edition: cm.getLocalStackEdition(),
15
+ version: cm.getLocalStackVersion(),
16
+ image: cm.getLocalStackImage(),
17
+ hasAuthToken: Boolean(cm.getLocalStackAuthToken()),
18
+ },
19
+ dynamoProxy: {
20
+ enabled: cm.isEnableDynamoProxy(),
21
+ port: cm.getDynamoProxyPort(),
22
+ },
23
+ region: cm.getRegion(),
24
+ services: cm.getServices(),
25
+ persistence: cm.isPersistence(),
26
+ debug: cm.isDebug(),
27
+ seedsDir: cm.getSeedsDir(),
28
+ autoPackage: cm.isAutoPackage(),
29
+ packageCommand: cm.getPackageCommand(),
30
+ packageTimeoutMs: cm.getPackageTimeoutMs(),
31
+ configPath: cm.getConfigPath(),
32
+ });
33
+ });
34
+ export { router as configRouter };
35
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/server/routes/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;AAExB,8DAA8D;AAC9D,kEAAkE;AAClE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAa,EAAE,GAAa,EAAE,EAAE;IAC/C,MAAM,EAAE,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;IACvC,GAAG,CAAC,IAAI,CAAC;QACP,UAAU,EAAE,EAAE,CAAC,aAAa,EAAE;QAC9B,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE;YAClB,QAAQ,EAAE,EAAE,CAAC,qBAAqB,EAAE;YACpC,IAAI,EAAE,EAAE,CAAC,iBAAiB,EAAE;YAC5B,OAAO,EAAE,EAAE,CAAC,oBAAoB,EAAE;YAClC,OAAO,EAAE,EAAE,CAAC,oBAAoB,EAAE;YAClC,KAAK,EAAE,EAAE,CAAC,kBAAkB,EAAE;YAC9B,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC,sBAAsB,EAAE,CAAC;SACnD;QACD,WAAW,EAAE;YACX,OAAO,EAAE,EAAE,CAAC,mBAAmB,EAAE;YACjC,IAAI,EAAE,EAAE,CAAC,kBAAkB,EAAE;SAC9B;QACD,MAAM,EAAE,EAAE,CAAC,SAAS,EAAE;QACtB,QAAQ,EAAE,EAAE,CAAC,WAAW,EAAE;QAC1B,WAAW,EAAE,EAAE,CAAC,aAAa,EAAE;QAC/B,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE;QACnB,QAAQ,EAAE,EAAE,CAAC,WAAW,EAAE;QAC1B,WAAW,EAAE,EAAE,CAAC,aAAa,EAAE;QAC/B,cAAc,EAAE,EAAE,CAAC,iBAAiB,EAAE;QACtC,gBAAgB,EAAE,EAAE,CAAC,mBAAmB,EAAE;QAC1C,UAAU,EAAE,EAAE,CAAC,aAAa,EAAE;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,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;AA4CxB,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"queues.d.ts","sourceRoot":"","sources":["../../../src/server/routes/queues.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAyHxB,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC"}