serverless-plugin-module-registry 1.0.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 DevSquad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # Serverless Module Registry Plugin
2
+
3
+ A Serverless Framework plugin that scans module registry files, discovers endpoints, and stores module feature mappings in DynamoDB for cross-module discovery and access control.
4
+
5
+ ## 🎯 Purpose
6
+
7
+ This plugin enables:
8
+ - **Module Discovery**: Automatically catalog all modules and their features
9
+ - **Endpoint Mapping**: Track which API endpoints belong to which features
10
+ - **Access Control**: Store IAM policies for cross-module permission management
11
+ - **Service Registry**: Provide a central registry for module-to-module communication
12
+
13
+ ## πŸ“¦ Installation
14
+
15
+ ```bash
16
+ npm install --save-dev serverless-plugin-module-registry
17
+ ```
18
+
19
+ ## πŸš€ Usage
20
+
21
+ ### Basic Configuration
22
+
23
+ Add the plugin to your `serverless.yml`:
24
+
25
+ ```yaml
26
+ plugins:
27
+ - '@hyperdrive.bot/serverless-composer' # Must be loaded first
28
+ - 'serverless-plugin-module-registry' # Load after composer
29
+
30
+ custom:
31
+ moduleRegistry:
32
+ tableName: ModuleRegistry # Optional: Custom table name
33
+ region: us-east-1 # Optional: Custom region
34
+ skipDynamoDB: false # Optional: Skip DynamoDB operations
35
+ ```
36
+
37
+ ### Module Registry Structure
38
+
39
+ Each module should have a `registry/` folder:
40
+
41
+ ```
42
+ serverless/modules/my-module/
43
+ β”œβ”€β”€ functions/ # Existing serverless functions
44
+ β”œβ”€β”€ resources/ # Existing serverless resources
45
+ └── registry/ # NEW: Registry definitions
46
+ β”œβ”€β”€ module.yml # Module metadata
47
+ └── features/ # Feature definitions
48
+ β”œβ”€β”€ user-management.yml
49
+ └── notification-system.yml
50
+ ```
51
+
52
+ #### Module Metadata (`registry/module.yml`)
53
+
54
+ ```yaml
55
+ name: "My Module"
56
+ version: "1.0.0"
57
+ description: "Sample module for testing"
58
+ maintainer: "DevSquad Team"
59
+ tags:
60
+ - "sample"
61
+ - "testing"
62
+ ```
63
+
64
+ #### Feature Definition (`registry/features/user-management.yml`)
65
+
66
+ ```yaml
67
+ name: "User Management"
68
+ description: "CRUD operations for user lifecycle management"
69
+ version: "1.2.0"
70
+
71
+ endpoints:
72
+ - "POST/users"
73
+ - "GET/users"
74
+ - "GET/users/*"
75
+ - "PUT/users/*"
76
+ - "DELETE/users/*"
77
+
78
+ customPolicies:
79
+ - Effect: Allow
80
+ Action:
81
+ - "execute-api:Invoke"
82
+ Resource:
83
+ - "arn:aws:execute-api:*:*:*/*/POST/users"
84
+ - "arn:aws:execute-api:*:*:*/*/GET/users"
85
+ - "arn:aws:execute-api:*:*:*/*/GET/users/*"
86
+ - Effect: Allow
87
+ Action:
88
+ - "dynamodb:PutItem"
89
+ - "dynamodb:GetItem"
90
+ - "dynamodb:Query"
91
+ Resource:
92
+ - "arn:aws:dynamodb:*:*:table/Users"
93
+ ```
94
+
95
+ ## πŸ—„οΈ DynamoDB Table Schema
96
+
97
+ The plugin creates an **intermodular** `ModuleRegistry` table using **AWS SDK v3** (not CloudFormation) with the following structure:
98
+
99
+ ### πŸ”„ **Why SDK v3 Instead of CloudFormation?**
100
+
101
+ **βœ… Intermodular Independence**: The table exists independently of any specific module's deployment lifecycle
102
+
103
+ **βœ… Cross-Module Sharing**: Multiple modules can write to the same registry without ownership conflicts
104
+
105
+ **βœ… Deployment Flexibility**: The table persists even if individual modules are removed or redeployed
106
+
107
+ **βœ… No Stack Dependencies**: No CloudFormation stack owns the table, preventing accidental deletion
108
+
109
+ ### πŸ“‹ **Table Management**
110
+ - **Creation**: Automatic during first deployment with registry data
111
+ - **Naming**: `{service}-{stage}-module-registry` (e.g., `ds-api-live-module-registry`)
112
+ - **Compliance**: Follows @dynamodb-tables.md standards (point-in-time recovery, tags)
113
+ - **Lifecycle**: Independent of individual module deployments
114
+
115
+ ### Primary Key
116
+ - **PK**: `MODULE#{moduleName}` (e.g., `MODULE#sign`)
117
+ - **SK**: `FEATURE#{featureName}` (e.g., `FEATURE#user-management`)
118
+
119
+ ### Global Secondary Index (GSI1)
120
+ - **GSI1PK**: `MODULES` (for listing all modules)
121
+ - **GSI1SK**: `MODULE#{moduleName}` (for grouping by module)
122
+
123
+ ### Data Structure
124
+ ```json
125
+ {
126
+ "PK": "MODULE#sign",
127
+ "SK": "FEATURE#user-management",
128
+ "GSI1PK": "MODULES",
129
+ "GSI1SK": "MODULE#sign",
130
+ "moduleName": "sign",
131
+ "featureName": "user-management",
132
+ "description": "CRUD operations for user lifecycle",
133
+ "version": "1.2.0",
134
+ "endpoints": [
135
+ "POST/users",
136
+ "GET/users",
137
+ "GET/users/*"
138
+ ],
139
+ "customPolicies": [...],
140
+ "lastUpdated": "2025-01-01T00:00:00Z"
141
+ }
142
+ ```
143
+
144
+ ## πŸ”„ How It Works
145
+
146
+ 1. **After Composer**: Plugin hooks after composer processes modules
147
+ 2. **Registry Scanning**: Scans each module's `registry/` folder
148
+ 3. **Feature Processing**: Reads and validates feature definitions
149
+ 4. **Table Management**: Creates intermodular DynamoDB table via AWS SDK v3 (if doesn't exist)
150
+ 5. **Standards Compliance**: Enables point-in-time recovery and applies compliance tags
151
+ 6. **Data Storage**: Batch writes registry data to DynamoDB with optimized structure
152
+
153
+ ## πŸ“‹ Access Patterns
154
+
155
+ The stored data supports these query patterns:
156
+
157
+ ### List All Modules
158
+ ```javascript
159
+ // Query GSI1 with GSI1PK = "MODULES"
160
+ const modules = await dynamoClient.query({
161
+ IndexName: 'GSI1',
162
+ KeyConditionExpression: 'GSI1PK = :pk',
163
+ ExpressionAttributeValues: {
164
+ ':pk': 'MODULES'
165
+ }
166
+ })
167
+ ```
168
+
169
+ ### Get Module Features
170
+ ```javascript
171
+ // Query main table with PK = "MODULE#{name}"
172
+ const features = await dynamoClient.query({
173
+ KeyConditionExpression: 'PK = :pk',
174
+ ExpressionAttributeValues: {
175
+ ':pk': `MODULE#${moduleName}`
176
+ }
177
+ })
178
+ ```
179
+
180
+ ### Get Specific Feature
181
+ ```javascript
182
+ // Get item with PK + SK
183
+ const feature = await dynamoClient.getItem({
184
+ Key: {
185
+ PK: `MODULE#${moduleName}`,
186
+ SK: `FEATURE#${featureName}`
187
+ }
188
+ })
189
+ ```
190
+
191
+ ## βš™οΈ Configuration Options
192
+
193
+ | Option | Type | Default | Description |
194
+ |--------|------|---------|-------------|
195
+ | `tableName` | string | `{service}-{stage}-module-registry` | DynamoDB table name |
196
+ | `region` | string | `provider.region` | AWS region for DynamoDB |
197
+ | `skipDynamoDB` | boolean | `false` | Skip DynamoDB operations |
198
+ | `strict` | boolean | `false` | Enforce registry folders on all modules |
199
+
200
+ ### πŸ”’ Strict Mode (`strict: true`)
201
+
202
+ Controls whether the plugin should enforce registry compliance:
203
+
204
+ **`strict: false` (Default - Graceful Mode)**
205
+ ```yaml
206
+ custom:
207
+ moduleRegistry:
208
+ strict: false # Graceful: skip modules without registry folders
209
+ ```
210
+ - βœ… **Graceful Migration**: Modules without registry folders are silently skipped
211
+ - βœ… **Backwards Compatible**: Existing modules continue to work unchanged
212
+ - βœ… **Gradual Adoption**: Teams can add registry folders at their own pace
213
+ - βœ… **Production Safe**: No deployment failures
214
+
215
+ **`strict: true` (Enforcement Mode)**
216
+ ```yaml
217
+ custom:
218
+ moduleRegistry:
219
+ strict: true # Strict: error if modules are missing registry folders
220
+ ```
221
+ - 🚨 **Enforces Compliance**: Deployment fails if modules don't have registry folders
222
+ - πŸ”’ **Quality Gate**: Ensures all modules participate in the registry
223
+ - πŸ“‹ **Documentation Requirement**: Forces teams to document their module features
224
+ - ⚑ **Use After Migration**: Enable once all modules have been updated
225
+
226
+ ### 🎯 When to Use Each Mode
227
+
228
+ **Use `strict: false` when:**
229
+ - πŸš€ **Initial Rollout**: Adding the plugin to existing systems
230
+ - πŸ”„ **Migration Phase**: Teams are gradually adding registry folders
231
+ - πŸ§ͺ **Testing Phase**: Experimenting with the registry system
232
+ - πŸ—οΈ **Development**: Working with modules that aren't fully documented yet
233
+
234
+ **Use `strict: true` when:**
235
+ - βœ… **Full Adoption**: All modules have been migrated to use registry folders
236
+ - πŸ”’ **Governance**: You want to enforce registry compliance as a quality gate
237
+ - πŸ“‹ **Documentation Standards**: Registry definitions are required for all modules
238
+ - πŸš€ **Production Systems**: Where complete module documentation is mandatory
239
+
240
+ ## πŸ› Debugging
241
+
242
+ The plugin provides detailed logging with `[module-registry]` prefix:
243
+
244
+ ```bash
245
+ serverless deploy --verbose
246
+ ```
247
+
248
+ Example output:
249
+ ```
250
+ [module-registry] πŸ” Scanning modules for registry definitions...
251
+ [module-registry] Found 3 modules: sample, sign, agents
252
+ [module-registry] πŸ“‹ Processing registry for module: sample
253
+ [module-registry] Found 2 feature definitions
254
+ [module-registry] βœ“ user-management: 5 endpoints
255
+ [module-registry] βœ“ notification-system: 9 endpoints
256
+ [module-registry] βœ… Registry processing complete. Found 4 features across 3 modules
257
+ ```
258
+
259
+ ## 🀝 Integration with Handlers - Direct Import Pattern
260
+
261
+ **No need for separate service files!** Import service functions directly from the plugin:
262
+
263
+ ### TypeScript/ES6 Imports
264
+ ```typescript
265
+ // In your handlers
266
+ import {
267
+ listAllModules,
268
+ getModuleFeatures,
269
+ getFeatureDetails,
270
+ createModuleRegistryLogger,
271
+ type ModuleInfo,
272
+ type FeatureInfo
273
+ } from 'serverless-plugin-module-registry'
274
+
275
+ const logger = createModuleRegistryLogger('my-handler')
276
+
277
+ export const myHandler = async (event: any) => {
278
+ const modules = await listAllModules()
279
+ logger.info(`Found ${modules.length} modules`)
280
+ return { modules }
281
+ }
282
+ ```
283
+
284
+ ### CommonJS/Node.js Require
285
+ ```javascript
286
+ // In your handlers
287
+ const {
288
+ listAllModules,
289
+ getModuleFeatures,
290
+ createModuleRegistryLogger
291
+ } = require('serverless-plugin-module-registry')
292
+
293
+ const logger = createModuleRegistryLogger('my-handler')
294
+
295
+ exports.myHandler = async (event) => {
296
+ const modules = await listAllModules()
297
+ logger.info(`Found ${modules.length} modules`)
298
+ return { modules }
299
+ }
300
+ ```
301
+
302
+ ### Available Service Functions
303
+
304
+ | Function | Description | Returns |
305
+ |----------|-------------|---------|
306
+ | `listAllModules()` | List all deployed modules | `Promise<ModuleInfo[]>` |
307
+ | `getModuleFeatures(moduleName)` | Get features for a module | `Promise<FeatureInfo[]>` |
308
+ | `getFeatureDetails(moduleName, featureName)` | Get feature details | `Promise<FeatureDetails \| null>` |
309
+ | `getModuleMetadata(moduleName)` | Get module metadata only | `Promise<ModuleInfo \| null>` |
310
+ | `getAllEndpoints()` | Get all endpoints across modules | `Promise<EndpointInfo[]>` |
311
+ | `createModuleRegistryLogger(context)` | Create logger for service functions | `Logger` |
312
+
313
+ ### Example Handler Implementation
314
+
315
+ ```javascript
316
+ // src/handlers/core/module-registry/listModules.js
317
+ const { http } = require('../../middlewares/http')
318
+ const { listAllModules, createModuleRegistryLogger } = require('serverless-plugin-module-registry')
319
+
320
+ const logger = createModuleRegistryLogger('list-modules-handler')
321
+
322
+ export const listModules = http(async (event) => {
323
+ logger.info('Listing all deployed modules', {
324
+ userSub: event.requestContext.authorizer?.claims?.sub
325
+ })
326
+
327
+ try {
328
+ const modules = await listAllModules()
329
+
330
+ logger.info(`Successfully retrieved ${modules.length} modules`)
331
+
332
+ return {
333
+ statusCode: 200,
334
+ body: {
335
+ modules,
336
+ count: modules.length,
337
+ timestamp: new Date().toISOString()
338
+ }
339
+ }
340
+ } catch (error) {
341
+ logger.error('Error listing modules:', error.message)
342
+
343
+ return {
344
+ statusCode: 500,
345
+ body: {
346
+ error: 'Failed to list modules',
347
+ message: error.message
348
+ }
349
+ }
350
+ }
351
+ })
352
+ ```
353
+
354
+ ## πŸ“„ License
355
+
356
+ MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,116 @@
1
+ interface ModuleInfo {
2
+ moduleName: string;
3
+ description: string;
4
+ version: string;
5
+ maintainer?: string;
6
+ tags: string[];
7
+ lastUpdated: string;
8
+ }
9
+ interface FeatureInfo {
10
+ featureName: string;
11
+ description: string;
12
+ version: string;
13
+ endpoints: string[];
14
+ customPolicies: any[];
15
+ endpointCount: number;
16
+ lastUpdated: string;
17
+ }
18
+ interface FeatureDetails extends FeatureInfo {
19
+ moduleName: string;
20
+ policyCount: number;
21
+ }
22
+ interface EndpointInfo {
23
+ endpoint: string;
24
+ moduleName: string;
25
+ featureName: string;
26
+ featureDescription: string;
27
+ version: string;
28
+ }
29
+ /**
30
+ * Lists all deployed modules with basic metadata
31
+ * @returns {Promise<ModuleInfo[]>} Array of module objects with clean data (no DDB internals)
32
+ */
33
+ declare const listAllModules: () => Promise<ModuleInfo[]>;
34
+ /**
35
+ * Gets all features for a specific module
36
+ * @param {string} moduleName - The module name (e.g., 'sign')
37
+ * @returns {Promise<FeatureInfo[]>} Array of feature objects with endpoints
38
+ */
39
+ declare const getModuleFeatures: (moduleName: string) => Promise<FeatureInfo[]>;
40
+ /**
41
+ * Gets detailed information about a specific feature
42
+ * @param {string} moduleName - The module name
43
+ * @param {string} featureName - The feature name
44
+ * @returns {Promise<FeatureDetails|null>} Feature details with endpoints and policies
45
+ */
46
+ declare const getFeatureDetails: (moduleName: string, featureName: string) => Promise<FeatureDetails | null>;
47
+ /**
48
+ * Gets module metadata (basic info without features)
49
+ * @param {string} moduleName - The module name
50
+ * @returns {Promise<ModuleInfo|null>} Module metadata or null if not found
51
+ */
52
+ declare const getModuleMetadata: (moduleName: string) => Promise<ModuleInfo | null>;
53
+ /**
54
+ * Gets endpoint summary across all modules for discovery
55
+ * @returns {Promise<EndpointInfo[]>} Array of endpoints with module/feature context
56
+ */
57
+ declare const getAllEndpoints: () => Promise<EndpointInfo[]>;
58
+ /**
59
+ * Utility function to create logger compatible with service-style.md
60
+ * This can be used in handlers that import the service functions
61
+ */
62
+ declare const createModuleRegistryLogger: (context?: string) => {
63
+ info: (message: string, data?: any) => void;
64
+ error: (message: string, data?: any) => void;
65
+ warn: (message: string, data?: any) => void;
66
+ };
67
+
68
+ declare class ServerlessModuleRegistryPlugin {
69
+ serverless: any;
70
+ options: Record<string, any>;
71
+ hooks: Record<string, () => Promise<void>>;
72
+ private config;
73
+ private servicePath;
74
+ private moduleEntries;
75
+ private featureEntries;
76
+ constructor(serverless: any, options: Record<string, any>);
77
+ /**
78
+ * Generate table name following ARN prefix pattern
79
+ */
80
+ private generateTableName;
81
+ /**
82
+ * Main processing function - scans all modules for registry files
83
+ */
84
+ private processModuleRegistry;
85
+ /**
86
+ * Process a single module's registry files
87
+ */
88
+ private processModule;
89
+ /**
90
+ * Read module metadata from module.yml
91
+ */
92
+ private readModuleMetadata;
93
+ /**
94
+ * Process a single feature file
95
+ */
96
+ private processFeatureFile;
97
+ /**
98
+ * Ensure DynamoDB table exists using AWS SDK v3, then update registry data
99
+ * This creates the table independently of any module's CloudFormation stack
100
+ */
101
+ private ensureTableAndUpdateData;
102
+ /**
103
+ * Update registry data in DynamoDB after deployment
104
+ */
105
+ private updateRegistryData;
106
+ /**
107
+ * Utility function to chunk array into smaller arrays
108
+ */
109
+ private chunkArray;
110
+ /**
111
+ * Enhanced logging with color and prefix
112
+ */
113
+ private log;
114
+ }
115
+
116
+ export { type EndpointInfo, type FeatureDetails, type FeatureInfo, type ModuleInfo, ServerlessModuleRegistryPlugin, createModuleRegistryLogger, ServerlessModuleRegistryPlugin as default, getAllEndpoints, getFeatureDetails, getModuleFeatures, getModuleMetadata, listAllModules };
package/dist/index.js ADDED
@@ -0,0 +1,632 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ServerlessModuleRegistryPlugin: () => ServerlessModuleRegistryPlugin,
34
+ createModuleRegistryLogger: () => createModuleRegistryLogger,
35
+ default: () => index_default,
36
+ getAllEndpoints: () => getAllEndpoints,
37
+ getFeatureDetails: () => getFeatureDetails,
38
+ getModuleFeatures: () => getModuleFeatures,
39
+ getModuleMetadata: () => getModuleMetadata,
40
+ listAllModules: () => listAllModules
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+ var import_fs = __toESM(require("fs"));
44
+ var import_path = __toESM(require("path"));
45
+ var import_js_yaml = __toESM(require("js-yaml"));
46
+ var import_glob = require("glob");
47
+ var import_chalk = __toESM(require("chalk"));
48
+
49
+ // src/service.ts
50
+ var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
51
+ var import_util_dynamodb = require("@aws-sdk/util-dynamodb");
52
+ var getDynamoClient = () => {
53
+ return new import_client_dynamodb.DynamoDBClient({
54
+ region: process.env.AWS_REGION || "us-east-1"
55
+ });
56
+ };
57
+ var getTableName = () => {
58
+ if (process.env.MODULE_REGISTRY_TABLE_NAME) {
59
+ return process.env.MODULE_REGISTRY_TABLE_NAME;
60
+ }
61
+ const service = process.env.SERVICE_NAME || "api";
62
+ const stage = process.env.STAGE || process.env.NODE_ENV || "live";
63
+ const defaultPrefix = `${service}-${stage}`;
64
+ return process.env.ARN_PREFIX ? `${process.env.ARN_PREFIX}-module-registry` : `${defaultPrefix}-module-registry`;
65
+ };
66
+ var GSI1_NAME = "GSI1";
67
+ var listAllModules = async () => {
68
+ const client = getDynamoClient();
69
+ try {
70
+ const command = new import_client_dynamodb.QueryCommand({
71
+ TableName: getTableName(),
72
+ IndexName: GSI1_NAME,
73
+ KeyConditionExpression: "gsi1pk = :pk",
74
+ FilterExpression: "itemType = :itemType",
75
+ ExpressionAttributeValues: {
76
+ ":pk": { S: "MODULES" },
77
+ ":itemType": { S: "module" }
78
+ }
79
+ });
80
+ const response = await client.send(command);
81
+ if (!response.Items) {
82
+ return [];
83
+ }
84
+ const modules = response.Items.map((item) => {
85
+ const data = (0, import_util_dynamodb.unmarshall)(item);
86
+ return {
87
+ moduleName: data.moduleName,
88
+ description: data.description,
89
+ version: data.version,
90
+ maintainer: data.maintainer,
91
+ tags: data.tags || [],
92
+ lastUpdated: data.lastUpdated
93
+ };
94
+ });
95
+ return modules;
96
+ } catch (error) {
97
+ throw new Error(`Failed to list modules: ${error.message}`);
98
+ }
99
+ };
100
+ var getModuleFeatures = async (moduleName) => {
101
+ if (!moduleName) {
102
+ throw new Error("Module name is required");
103
+ }
104
+ const client = getDynamoClient();
105
+ try {
106
+ const command = new import_client_dynamodb.QueryCommand({
107
+ TableName: getTableName(),
108
+ KeyConditionExpression: "pk = :pk AND begins_with(sk, :skPrefix)",
109
+ FilterExpression: "itemType = :itemType",
110
+ ExpressionAttributeValues: {
111
+ ":pk": { S: `MODULE#${moduleName}` },
112
+ ":skPrefix": { S: "FEATURE#" },
113
+ ":itemType": { S: "feature" }
114
+ }
115
+ });
116
+ const response = await client.send(command);
117
+ if (!response.Items) {
118
+ return [];
119
+ }
120
+ const features = response.Items.map((item) => {
121
+ const data = (0, import_util_dynamodb.unmarshall)(item);
122
+ return {
123
+ featureName: data.featureName,
124
+ description: data.description,
125
+ version: data.version,
126
+ endpoints: data.endpoints || [],
127
+ customPolicies: data.customPolicies || [],
128
+ endpointCount: (data.endpoints || []).length,
129
+ lastUpdated: data.lastUpdated
130
+ };
131
+ });
132
+ return features;
133
+ } catch (error) {
134
+ throw new Error(`Failed to get module features: ${error.message}`);
135
+ }
136
+ };
137
+ var getFeatureDetails = async (moduleName, featureName) => {
138
+ if (!moduleName) {
139
+ throw new Error("Module name is required");
140
+ }
141
+ if (!featureName) {
142
+ throw new Error("Feature name is required");
143
+ }
144
+ const client = getDynamoClient();
145
+ try {
146
+ const command = new import_client_dynamodb.QueryCommand({
147
+ TableName: getTableName(),
148
+ KeyConditionExpression: "pk = :pk AND sk = :sk",
149
+ ExpressionAttributeValues: {
150
+ ":pk": { S: `MODULE#${moduleName}` },
151
+ ":sk": { S: `FEATURE#${featureName}` }
152
+ }
153
+ });
154
+ const response = await client.send(command);
155
+ if (!response.Items || response.Items.length === 0) {
156
+ return null;
157
+ }
158
+ const data = (0, import_util_dynamodb.unmarshall)(response.Items[0]);
159
+ const featureDetails = {
160
+ moduleName: data.moduleName,
161
+ featureName: data.featureName,
162
+ description: data.description,
163
+ version: data.version,
164
+ endpoints: data.endpoints || [],
165
+ customPolicies: data.customPolicies || [],
166
+ endpointCount: (data.endpoints || []).length,
167
+ policyCount: (data.customPolicies || []).length,
168
+ lastUpdated: data.lastUpdated
169
+ };
170
+ return featureDetails;
171
+ } catch (error) {
172
+ throw new Error(`Failed to get feature details: ${error.message}`);
173
+ }
174
+ };
175
+ var getModuleMetadata = async (moduleName) => {
176
+ if (!moduleName) {
177
+ throw new Error("Module name is required");
178
+ }
179
+ const client = getDynamoClient();
180
+ try {
181
+ const command = new import_client_dynamodb.QueryCommand({
182
+ TableName: getTableName(),
183
+ KeyConditionExpression: "pk = :pk AND sk = :sk",
184
+ ExpressionAttributeValues: {
185
+ ":pk": { S: `MODULE#${moduleName}` },
186
+ ":sk": { S: "MODULE" }
187
+ }
188
+ });
189
+ const response = await client.send(command);
190
+ if (!response.Items || response.Items.length === 0) {
191
+ return null;
192
+ }
193
+ const data = (0, import_util_dynamodb.unmarshall)(response.Items[0]);
194
+ const moduleMetadata = {
195
+ moduleName: data.moduleName,
196
+ description: data.description,
197
+ version: data.version,
198
+ maintainer: data.maintainer,
199
+ tags: data.tags || [],
200
+ lastUpdated: data.lastUpdated
201
+ };
202
+ return moduleMetadata;
203
+ } catch (error) {
204
+ throw new Error(`Failed to get module metadata: ${error.message}`);
205
+ }
206
+ };
207
+ var getAllEndpoints = async () => {
208
+ const client = getDynamoClient();
209
+ try {
210
+ const command = new import_client_dynamodb.ScanCommand({
211
+ TableName: getTableName(),
212
+ FilterExpression: "itemType = :itemType",
213
+ ExpressionAttributeValues: {
214
+ ":itemType": { S: "feature" }
215
+ }
216
+ });
217
+ const response = await client.send(command);
218
+ if (!response.Items) {
219
+ return [];
220
+ }
221
+ const endpoints = [];
222
+ for (const item of response.Items) {
223
+ const data = (0, import_util_dynamodb.unmarshall)(item);
224
+ if (data.endpoints && Array.isArray(data.endpoints)) {
225
+ for (const endpoint of data.endpoints) {
226
+ endpoints.push({
227
+ endpoint,
228
+ moduleName: data.moduleName,
229
+ featureName: data.featureName,
230
+ featureDescription: data.description,
231
+ version: data.version
232
+ });
233
+ }
234
+ }
235
+ }
236
+ return endpoints;
237
+ } catch (error) {
238
+ throw new Error(`Failed to get all endpoints: ${error.message}`);
239
+ }
240
+ };
241
+ var createModuleRegistryLogger = (context) => {
242
+ const logContext = context || "module-registry";
243
+ return {
244
+ info: (message, data) => {
245
+ console.log(`[${logContext}] INFO: ${message}`, data ? JSON.stringify(data) : "");
246
+ },
247
+ error: (message, data) => {
248
+ console.error(`[${logContext}] ERROR: ${message}`, data ? JSON.stringify(data) : "");
249
+ },
250
+ warn: (message, data) => {
251
+ console.warn(`[${logContext}] WARN: ${message}`, data ? JSON.stringify(data) : "");
252
+ }
253
+ };
254
+ };
255
+
256
+ // src/index.ts
257
+ var ServerlessModuleRegistryPlugin = class {
258
+ constructor(serverless, options) {
259
+ this.moduleEntries = [];
260
+ this.featureEntries = [];
261
+ var _a, _b;
262
+ this.serverless = serverless;
263
+ this.options = options;
264
+ this.servicePath = this.serverless.config.servicePath || process.cwd();
265
+ const custom = ((_a = this.serverless.service.custom) == null ? void 0 : _a.moduleRegistry) || {};
266
+ const defaultTableName = this.generateTableName();
267
+ this.config = {
268
+ tableName: custom.tableName || defaultTableName,
269
+ region: custom.region || ((_b = this.serverless.service.provider) == null ? void 0 : _b.region) || "us-east-1",
270
+ skipDynamoDB: custom.skipDynamoDB || false,
271
+ strict: custom.strict || false,
272
+ ...custom
273
+ };
274
+ this.hooks = {
275
+ "after:composer:initialize": this.processModuleRegistry.bind(this),
276
+ "after:aws:deploy:deploy:updateStack": this.ensureTableAndUpdateData.bind(this)
277
+ };
278
+ this.log(`\u{1F4CA} Module Registry plugin initialized:`);
279
+ this.log(` Table: ${this.config.tableName}`);
280
+ this.log(` Region: ${this.config.region}`);
281
+ this.log(` Skip DynamoDB: ${this.config.skipDynamoDB}`);
282
+ this.log(` Strict Mode: ${this.config.strict ? "\u{1F512} ENABLED" : "\u2705 DISABLED"}`);
283
+ this.log("");
284
+ }
285
+ /**
286
+ * Generate table name following ARN prefix pattern
287
+ */
288
+ generateTableName() {
289
+ const service = this.serverless.service.service;
290
+ const provider = this.serverless.service.provider || {};
291
+ const stage = provider.stage || "dev";
292
+ return `${service}-${stage}-module-registry`;
293
+ }
294
+ /**
295
+ * Main processing function - scans all modules for registry files
296
+ */
297
+ async processModuleRegistry() {
298
+ this.log("\u{1F50D} Scanning modules for registry definitions...");
299
+ this.log(` Strict Mode: ${this.config.strict ? "\u{1F512} ENABLED - Will FAIL deployment on missing registries" : "\u2705 DISABLED - Will gracefully skip missing registries"}`);
300
+ const modulesDir = import_path.default.resolve(this.servicePath, "serverless/modules");
301
+ if (!import_fs.default.existsSync(modulesDir)) {
302
+ this.log("\u26A0\uFE0F No modules directory found, skipping registry processing");
303
+ return;
304
+ }
305
+ const moduleNames = import_fs.default.readdirSync(modulesDir).filter((name) => import_fs.default.statSync(import_path.default.join(modulesDir, name)).isDirectory());
306
+ this.log(`Found ${moduleNames.length} modules: ${moduleNames.join(", ")}`);
307
+ for (const moduleName of moduleNames) {
308
+ this.log(`
309
+ \u{1F50D} Checking module: ${moduleName}`);
310
+ await this.processModule(moduleName, modulesDir);
311
+ }
312
+ this.log(`\u2705 Registry processing complete. Found ${this.moduleEntries.length} modules, ${this.featureEntries.length} features`);
313
+ }
314
+ /**
315
+ * Process a single module's registry files
316
+ */
317
+ async processModule(moduleName, modulesDir) {
318
+ const moduleDir = import_path.default.join(modulesDir, moduleName);
319
+ const registryDir = import_path.default.join(moduleDir, "registry");
320
+ if (!import_fs.default.existsSync(registryDir)) {
321
+ if (this.config.strict) {
322
+ const errorMessage = `\u274C STRICT MODE VIOLATION: Module '${moduleName}' is missing required registry folder!
323
+ Expected: ${registryDir}
324
+ Fix: Create the registry folder or set strict: false in configuration`;
325
+ this.log("");
326
+ this.log("\u{1F6A8} DEPLOYMENT FAILED - STRICT MODE VIOLATION \u{1F6A8}");
327
+ this.log(errorMessage);
328
+ this.log("");
329
+ console.error(`[module-registry] ${errorMessage}`);
330
+ throw new Error(`Module Registry Strict Mode Violation: Module '${moduleName}' missing registry folder`);
331
+ }
332
+ this.log(`\u{1F4C1} Module '${moduleName}' has no registry folder, skipping (strict mode disabled)`);
333
+ return;
334
+ }
335
+ this.log(`\u{1F4CB} Processing registry for module: ${moduleName}`);
336
+ const moduleMetadata = await this.readModuleMetadata(registryDir);
337
+ const moduleEntry = {
338
+ moduleName,
339
+ description: moduleMetadata.description || moduleMetadata.name,
340
+ version: moduleMetadata.version,
341
+ maintainer: moduleMetadata.maintainer,
342
+ tags: moduleMetadata.tags,
343
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
344
+ };
345
+ this.moduleEntries.push(moduleEntry);
346
+ this.log(` \u2713 Module metadata: ${moduleEntry.version}`);
347
+ const featuresDir = import_path.default.join(registryDir, "features");
348
+ if (!import_fs.default.existsSync(featuresDir)) {
349
+ if (this.config.strict) {
350
+ const errorMessage = `\u274C STRICT MODE VIOLATION: Module '${moduleName}' is missing required features directory!
351
+ Expected: ${featuresDir}
352
+ Fix: Create the features directory with at least one .yml file`;
353
+ this.log("");
354
+ this.log("\u{1F6A8} DEPLOYMENT FAILED - STRICT MODE VIOLATION \u{1F6A8}");
355
+ this.log(errorMessage);
356
+ this.log("");
357
+ console.error(`[module-registry] ${errorMessage}`);
358
+ throw new Error(`Module Registry Strict Mode Violation: Module '${moduleName}' missing features directory`);
359
+ }
360
+ this.log(`\u26A0\uFE0F No features directory found in ${moduleName}/registry, skipping (strict mode disabled)`);
361
+ return;
362
+ }
363
+ const featureFiles = await (0, import_glob.glob)(import_path.default.join(featuresDir, "*.{yml,yaml}").replace(/\\/g, "/"));
364
+ this.log(` Found ${featureFiles.length} feature definitions`);
365
+ for (const featureFile of featureFiles) {
366
+ await this.processFeatureFile(featureFile, moduleName, moduleMetadata);
367
+ }
368
+ }
369
+ /**
370
+ * Read module metadata from module.yml
371
+ */
372
+ async readModuleMetadata(registryDir) {
373
+ const moduleFile = import_path.default.join(registryDir, "module.yml");
374
+ if (!import_fs.default.existsSync(moduleFile)) {
375
+ return {
376
+ name: "unknown",
377
+ version: "1.0.0",
378
+ description: "No module metadata available"
379
+ };
380
+ }
381
+ try {
382
+ const moduleData = import_js_yaml.default.load(import_fs.default.readFileSync(moduleFile, "utf8"));
383
+ return moduleData;
384
+ } catch (error) {
385
+ this.log(`\u26A0\uFE0F Error reading module metadata: ${error.message}`);
386
+ return {
387
+ name: "unknown",
388
+ version: "1.0.0",
389
+ description: "Error reading module metadata"
390
+ };
391
+ }
392
+ }
393
+ /**
394
+ * Process a single feature file
395
+ */
396
+ async processFeatureFile(featureFile, moduleName, moduleMetadata) {
397
+ const featureName = import_path.default.basename(featureFile, import_path.default.extname(featureFile));
398
+ try {
399
+ const featureData = import_js_yaml.default.load(import_fs.default.readFileSync(featureFile, "utf8"));
400
+ if (!featureData.name || !featureData.endpoints) {
401
+ this.log(`\u26A0\uFE0F Invalid feature definition in ${featureFile}`);
402
+ return;
403
+ }
404
+ const registryEntry = {
405
+ moduleName,
406
+ featureName,
407
+ description: featureData.description || featureData.name,
408
+ version: featureData.version || moduleMetadata.version,
409
+ endpoints: featureData.endpoints || [],
410
+ customPolicies: featureData.customPolicies || [],
411
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
412
+ };
413
+ this.featureEntries.push(registryEntry);
414
+ this.log(` \u2713 ${featureName}: ${registryEntry.endpoints.length} endpoints`);
415
+ } catch (error) {
416
+ this.log(`\u274C Error processing feature ${featureFile}: ${error.message}`);
417
+ }
418
+ }
419
+ /**
420
+ * Ensure DynamoDB table exists using AWS SDK v3, then update registry data
421
+ * This creates the table independently of any module's CloudFormation stack
422
+ */
423
+ async ensureTableAndUpdateData() {
424
+ var _a, _b;
425
+ const totalEntries = this.moduleEntries.length + this.featureEntries.length;
426
+ if (this.config.skipDynamoDB || totalEntries === 0) {
427
+ this.log("\u23ED\uFE0F Skipping DynamoDB operations");
428
+ return;
429
+ }
430
+ try {
431
+ const { DynamoDBClient: DynamoDBClient2, CreateTableCommand, DescribeTableCommand, UpdateContinuousBackupsCommand, TagResourceCommand } = await import("@aws-sdk/client-dynamodb");
432
+ const client = new DynamoDBClient2({ region: this.config.region });
433
+ const tableName = this.config.tableName;
434
+ let tableExists = false;
435
+ try {
436
+ await client.send(new DescribeTableCommand({ TableName: tableName }));
437
+ tableExists = true;
438
+ this.log(`\u2139\uFE0F Table '${tableName}' already exists`);
439
+ } catch (error) {
440
+ if (error.name !== "ResourceNotFoundException") {
441
+ throw error;
442
+ }
443
+ this.log(`\u{1F4CB} Table '${tableName}' does not exist, creating...`);
444
+ }
445
+ if (!tableExists) {
446
+ const createTableParams = {
447
+ TableName: tableName,
448
+ BillingMode: "PAY_PER_REQUEST",
449
+ AttributeDefinitions: [
450
+ {
451
+ AttributeName: "pk",
452
+ AttributeType: "S"
453
+ },
454
+ {
455
+ AttributeName: "sk",
456
+ AttributeType: "S"
457
+ },
458
+ {
459
+ AttributeName: "gsi1pk",
460
+ AttributeType: "S"
461
+ },
462
+ {
463
+ AttributeName: "gsi1sk",
464
+ AttributeType: "S"
465
+ }
466
+ ],
467
+ KeySchema: [
468
+ {
469
+ AttributeName: "pk",
470
+ KeyType: "HASH"
471
+ },
472
+ {
473
+ AttributeName: "sk",
474
+ KeyType: "RANGE"
475
+ }
476
+ ],
477
+ GlobalSecondaryIndexes: [
478
+ {
479
+ IndexName: "GSI1",
480
+ KeySchema: [
481
+ {
482
+ AttributeName: "gsi1pk",
483
+ KeyType: "HASH"
484
+ },
485
+ {
486
+ AttributeName: "gsi1sk",
487
+ KeyType: "RANGE"
488
+ }
489
+ ],
490
+ Projection: {
491
+ ProjectionType: "ALL"
492
+ }
493
+ }
494
+ ]
495
+ };
496
+ await client.send(new CreateTableCommand(createTableParams));
497
+ this.log(`\u2705 Created table '${tableName}'`);
498
+ this.log("\u23F3 Waiting for table to be active...");
499
+ let isActive = false;
500
+ while (!isActive) {
501
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
502
+ const response = await client.send(new DescribeTableCommand({ TableName: tableName }));
503
+ isActive = ((_a = response.Table) == null ? void 0 : _a.TableStatus) === "ACTIVE";
504
+ }
505
+ try {
506
+ await client.send(new UpdateContinuousBackupsCommand({
507
+ TableName: tableName,
508
+ PointInTimeRecoverySpecification: {
509
+ PointInTimeRecoveryEnabled: true
510
+ }
511
+ }));
512
+ this.log("\u2705 Enabled point-in-time recovery");
513
+ } catch (error) {
514
+ this.log(`\u26A0\uFE0F Could not enable point-in-time recovery: ${error.message}`);
515
+ }
516
+ try {
517
+ const describeResponse = await client.send(new DescribeTableCommand({ TableName: tableName }));
518
+ const tableArn = (_b = describeResponse.Table) == null ? void 0 : _b.TableArn;
519
+ if (tableArn) {
520
+ await client.send(new TagResourceCommand({
521
+ ResourceArn: tableArn,
522
+ Tags: [
523
+ {
524
+ Key: "Module",
525
+ Value: "module-registry"
526
+ },
527
+ {
528
+ Key: "Purpose",
529
+ Value: "StateStore"
530
+ }
531
+ ]
532
+ }));
533
+ this.log("\u2705 Added compliance tags to table");
534
+ }
535
+ } catch (error) {
536
+ this.log(`\u26A0\uFE0F Could not add tags: ${error.message}`);
537
+ }
538
+ }
539
+ await this.updateRegistryData();
540
+ } catch (error) {
541
+ this.log(`\u274C Error ensuring table and updating data: ${error.message}`);
542
+ }
543
+ }
544
+ /**
545
+ * Update registry data in DynamoDB after deployment
546
+ */
547
+ async updateRegistryData() {
548
+ const totalEntries = this.moduleEntries.length + this.featureEntries.length;
549
+ if (this.config.skipDynamoDB || totalEntries === 0) {
550
+ this.log("\u23ED\uFE0F Skipping registry data update");
551
+ return;
552
+ }
553
+ this.log(`\u{1F4DD} Updating registry data in DynamoDB (${totalEntries} entries)...`);
554
+ try {
555
+ const { DynamoDBClient: DynamoDBClient2, BatchWriteItemCommand } = await import("@aws-sdk/client-dynamodb");
556
+ const { marshall } = await import("@aws-sdk/util-dynamodb");
557
+ const client = new DynamoDBClient2({ region: this.config.region });
558
+ const allItems = [];
559
+ for (const moduleEntry of this.moduleEntries) {
560
+ allItems.push({
561
+ pk: `MODULE#${moduleEntry.moduleName}`,
562
+ sk: "MODULE",
563
+ gsi1pk: "MODULES",
564
+ gsi1sk: `MODULE#${moduleEntry.moduleName}`,
565
+ itemType: "module",
566
+ ...moduleEntry
567
+ });
568
+ }
569
+ for (const featureEntry of this.featureEntries) {
570
+ allItems.push({
571
+ pk: `MODULE#${featureEntry.moduleName}`,
572
+ sk: `FEATURE#${featureEntry.featureName}`,
573
+ gsi1pk: `MODULE#${featureEntry.moduleName}`,
574
+ gsi1sk: `FEATURE#${featureEntry.featureName}`,
575
+ itemType: "feature",
576
+ ...featureEntry
577
+ });
578
+ }
579
+ const batchSize = 25;
580
+ const batches = this.chunkArray(allItems, batchSize);
581
+ for (const batch of batches) {
582
+ const putRequests = batch.map((item) => ({
583
+ PutRequest: {
584
+ Item: marshall(item)
585
+ }
586
+ }));
587
+ const command = new BatchWriteItemCommand({
588
+ RequestItems: {
589
+ [this.config.tableName]: putRequests
590
+ }
591
+ });
592
+ await client.send(command);
593
+ }
594
+ this.log(`\u2705 Registry data updated successfully: ${this.moduleEntries.length} modules, ${this.featureEntries.length} features`);
595
+ } catch (error) {
596
+ this.log(`\u274C Error updating registry data: ${error.message}`);
597
+ }
598
+ }
599
+ /**
600
+ * Utility function to chunk array into smaller arrays
601
+ */
602
+ chunkArray(array, size) {
603
+ const chunks = [];
604
+ for (let i = 0; i < array.length; i += size) {
605
+ chunks.push(array.slice(i, i + size));
606
+ }
607
+ return chunks;
608
+ }
609
+ /**
610
+ * Enhanced logging with color and prefix
611
+ */
612
+ log(message) {
613
+ var _a, _b;
614
+ ((_b = (_a = this.serverless.cli) == null ? void 0 : _a.log) == null ? void 0 : _b.call(_a, `${import_chalk.default.blue("[module-registry]")} ${message}`)) || console.log(`${import_chalk.default.blue("[module-registry]")} ${message}`);
615
+ }
616
+ };
617
+ var index_default = ServerlessModuleRegistryPlugin;
618
+ if (typeof module !== "undefined" && module.exports) {
619
+ module.exports = ServerlessModuleRegistryPlugin;
620
+ module.exports.default = ServerlessModuleRegistryPlugin;
621
+ }
622
+ // Annotate the CommonJS export names for ESM import in node:
623
+ 0 && (module.exports = {
624
+ ServerlessModuleRegistryPlugin,
625
+ createModuleRegistryLogger,
626
+ getAllEndpoints,
627
+ getFeatureDetails,
628
+ getModuleFeatures,
629
+ getModuleMetadata,
630
+ listAllModules
631
+ });
632
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/service.ts"],"sourcesContent":["/*\n * serverless-plugin-module-registry\n * -------------------------------------------\n * A Serverless Framework plugin that scans module registry files,\n * discovers endpoints, and stores module feature mappings in DynamoDB\n * for cross-module discovery and access control.\n *\n * βœ“ Compatible with Serverless Framework v3 & v4\n * βœ“ Extends composer plugin functionality\n * βœ“ Scans registry/ folders in each module\n * βœ“ Creates/updates DynamoDB ModuleRegistry table\n */\n\nimport fs from 'fs'\nimport path from 'path'\nimport yaml from 'js-yaml'\nimport { glob } from 'glob'\nimport chalk from 'chalk'\n\ninterface ModuleRegistryConfig {\n tableName?: string\n region?: string\n skipDynamoDB?: boolean\n strict?: boolean\n}\n\ninterface RegistryFeature {\n name: string\n description: string\n version: string\n endpoints: string[]\n customPolicies?: any[]\n}\n\ninterface ModuleMetadata {\n name: string\n version: string\n description?: string\n}\n\ninterface RegistryModuleEntry {\n moduleName: string\n description: string\n version: string\n maintainer?: string\n tags?: string[]\n lastUpdated: string\n}\n\ninterface RegistryFeatureEntry {\n moduleName: string\n featureName: string\n description: string\n version: string\n endpoints: string[]\n customPolicies: any[]\n lastUpdated: string\n}\n\nclass ServerlessModuleRegistryPlugin {\n serverless: any\n options: Record<string, any>\n hooks: Record<string, () => Promise<void>>\n private config: ModuleRegistryConfig\n private servicePath: string\n private moduleEntries: RegistryModuleEntry[] = []\n private featureEntries: RegistryFeatureEntry[] = []\n\n constructor(serverless: any, options: Record<string, any>) {\n this.serverless = serverless\n this.options = options\n this.servicePath = this.serverless.config.servicePath || process.cwd()\n\n // Get plugin configuration with dynamic table name\n const custom = this.serverless.service.custom?.moduleRegistry || {}\n const defaultTableName = this.generateTableName()\n \n this.config = {\n tableName: custom.tableName || defaultTableName,\n region: custom.region || this.serverless.service.provider?.region || 'us-east-1',\n skipDynamoDB: custom.skipDynamoDB || false,\n strict: custom.strict || false,\n ...custom\n }\n\n // Register hooks\n this.hooks = {\n 'after:composer:initialize': this.processModuleRegistry.bind(this),\n 'after:aws:deploy:deploy:updateStack': this.ensureTableAndUpdateData.bind(this)\n }\n\n // Debug current configuration\n this.log(`πŸ“Š Module Registry plugin initialized:`)\n this.log(` Table: ${this.config.tableName}`)\n this.log(` Region: ${this.config.region}`)\n this.log(` Skip DynamoDB: ${this.config.skipDynamoDB}`)\n this.log(` Strict Mode: ${this.config.strict ? 'πŸ”’ ENABLED' : 'βœ… DISABLED'}`)\n this.log('')\n }\n\n /**\n * Generate table name following ARN prefix pattern\n */\n private generateTableName(): string {\n const service = this.serverless.service.service\n const provider = this.serverless.service.provider || {}\n const stage = provider.stage || 'dev'\n \n // Use same pattern as ARN prefixer: {service}-{stage}-module-registry\n return `${service}-${stage}-module-registry`\n }\n\n /**\n * Main processing function - scans all modules for registry files\n */\n private async processModuleRegistry(): Promise<void> {\n this.log('πŸ” Scanning modules for registry definitions...')\n this.log(` Strict Mode: ${this.config.strict ? 'πŸ”’ ENABLED - Will FAIL deployment on missing registries' : 'βœ… DISABLED - Will gracefully skip missing registries'}`)\n\n const modulesDir = path.resolve(this.servicePath, 'serverless/modules')\n \n if (!fs.existsSync(modulesDir)) {\n this.log('⚠️ No modules directory found, skipping registry processing')\n return\n }\n\n // Get all module directories\n const moduleNames = fs.readdirSync(modulesDir)\n .filter(name => fs.statSync(path.join(modulesDir, name)).isDirectory())\n\n this.log(`Found ${moduleNames.length} modules: ${moduleNames.join(', ')}`)\n\n // Process each module\n for (const moduleName of moduleNames) {\n this.log(`\\nπŸ” Checking module: ${moduleName}`)\n await this.processModule(moduleName, modulesDir)\n }\n\n this.log(`βœ… Registry processing complete. Found ${this.moduleEntries.length} modules, ${this.featureEntries.length} features`)\n }\n\n /**\n * Process a single module's registry files\n */\n private async processModule(moduleName: string, modulesDir: string): Promise<void> {\n const moduleDir = path.join(modulesDir, moduleName)\n const registryDir = path.join(moduleDir, 'registry')\n\n if (!fs.existsSync(registryDir)) {\n if (this.config.strict) {\n const errorMessage = `❌ STRICT MODE VIOLATION: Module '${moduleName}' is missing required registry folder!\\n Expected: ${registryDir}\\n Fix: Create the registry folder or set strict: false in configuration`\n this.log('')\n this.log('🚨 DEPLOYMENT FAILED - STRICT MODE VIOLATION 🚨')\n this.log(errorMessage)\n this.log('')\n console.error(`[module-registry] ${errorMessage}`)\n throw new Error(`Module Registry Strict Mode Violation: Module '${moduleName}' missing registry folder`)\n }\n \n this.log(`πŸ“ Module '${moduleName}' has no registry folder, skipping (strict mode disabled)`)\n return\n }\n\n this.log(`πŸ“‹ Processing registry for module: ${moduleName}`)\n\n // Read module metadata\n const moduleMetadata = await this.readModuleMetadata(registryDir)\n\n // Create module entry\n const moduleEntry: RegistryModuleEntry = {\n moduleName,\n description: moduleMetadata.description || moduleMetadata.name,\n version: moduleMetadata.version,\n maintainer: (moduleMetadata as any).maintainer,\n tags: (moduleMetadata as any).tags,\n lastUpdated: new Date().toISOString()\n }\n this.moduleEntries.push(moduleEntry)\n this.log(` βœ“ Module metadata: ${moduleEntry.version}`)\n\n // Read all feature files\n const featuresDir = path.join(registryDir, 'features')\n if (!fs.existsSync(featuresDir)) {\n if (this.config.strict) {\n const errorMessage = `❌ STRICT MODE VIOLATION: Module '${moduleName}' is missing required features directory!\\n Expected: ${featuresDir}\\n Fix: Create the features directory with at least one .yml file`\n this.log('')\n this.log('🚨 DEPLOYMENT FAILED - STRICT MODE VIOLATION 🚨')\n this.log(errorMessage)\n this.log('')\n console.error(`[module-registry] ${errorMessage}`)\n throw new Error(`Module Registry Strict Mode Violation: Module '${moduleName}' missing features directory`)\n }\n \n this.log(`⚠️ No features directory found in ${moduleName}/registry, skipping (strict mode disabled)`)\n return\n }\n\n // Find all feature YAML files\n const featureFiles = await glob(path.join(featuresDir, '*.{yml,yaml}').replace(/\\\\/g, '/'))\n \n this.log(` Found ${featureFiles.length} feature definitions`)\n\n // Process each feature file\n for (const featureFile of featureFiles) {\n await this.processFeatureFile(featureFile, moduleName, moduleMetadata)\n }\n }\n\n /**\n * Read module metadata from module.yml\n */\n private async readModuleMetadata(registryDir: string): Promise<ModuleMetadata> {\n const moduleFile = path.join(registryDir, 'module.yml')\n \n if (!fs.existsSync(moduleFile)) {\n return {\n name: 'unknown',\n version: '1.0.0',\n description: 'No module metadata available'\n }\n }\n\n try {\n const moduleData = yaml.load(fs.readFileSync(moduleFile, 'utf8')) as ModuleMetadata\n return moduleData\n } catch (error) {\n this.log(`⚠️ Error reading module metadata: ${(error as Error).message}`)\n return {\n name: 'unknown',\n version: '1.0.0', \n description: 'Error reading module metadata'\n }\n }\n }\n\n /**\n * Process a single feature file\n */\n private async processFeatureFile(featureFile: string, moduleName: string, moduleMetadata: ModuleMetadata): Promise<void> {\n const featureName = path.basename(featureFile, path.extname(featureFile))\n \n try {\n const featureData = yaml.load(fs.readFileSync(featureFile, 'utf8')) as RegistryFeature\n \n if (!featureData.name || !featureData.endpoints) {\n this.log(`⚠️ Invalid feature definition in ${featureFile}`)\n return\n }\n\n // Create registry entry\n const registryEntry: RegistryFeatureEntry = {\n moduleName,\n featureName,\n description: featureData.description || featureData.name,\n version: featureData.version || moduleMetadata.version,\n endpoints: featureData.endpoints || [],\n customPolicies: featureData.customPolicies || [],\n lastUpdated: new Date().toISOString()\n }\n\n this.featureEntries.push(registryEntry)\n \n this.log(` βœ“ ${featureName}: ${registryEntry.endpoints.length} endpoints`)\n } catch (error) {\n this.log(`❌ Error processing feature ${featureFile}: ${(error as Error).message}`)\n }\n }\n\n /**\n * Ensure DynamoDB table exists using AWS SDK v3, then update registry data\n * This creates the table independently of any module's CloudFormation stack\n */\n private async ensureTableAndUpdateData(): Promise<void> {\n const totalEntries = this.moduleEntries.length + this.featureEntries.length\n if (this.config.skipDynamoDB || totalEntries === 0) {\n this.log('⏭️ Skipping DynamoDB operations')\n return\n }\n\n try {\n const { DynamoDBClient, CreateTableCommand, DescribeTableCommand, UpdateContinuousBackupsCommand, TagResourceCommand } = await import('@aws-sdk/client-dynamodb')\n \n const client = new DynamoDBClient({ region: this.config.region })\n const tableName = this.config.tableName!\n\n // First, check if table exists\n let tableExists = false\n try {\n await client.send(new DescribeTableCommand({ TableName: tableName }))\n tableExists = true\n this.log(`ℹ️ Table '${tableName}' already exists`)\n } catch (error: any) {\n if (error.name !== 'ResourceNotFoundException') {\n throw error\n }\n this.log(`πŸ“‹ Table '${tableName}' does not exist, creating...`)\n }\n\n // Create table if it doesn't exist\n if (!tableExists) {\n const createTableParams = {\n TableName: tableName,\n BillingMode: 'PAY_PER_REQUEST' as const,\n AttributeDefinitions: [\n {\n AttributeName: 'pk',\n AttributeType: 'S' as const\n },\n {\n AttributeName: 'sk', \n AttributeType: 'S' as const\n },\n {\n AttributeName: 'gsi1pk',\n AttributeType: 'S' as const\n },\n {\n AttributeName: 'gsi1sk',\n AttributeType: 'S' as const\n }\n ],\n KeySchema: [\n {\n AttributeName: 'pk',\n KeyType: 'HASH' as const\n },\n {\n AttributeName: 'sk',\n KeyType: 'RANGE' as const\n }\n ],\n GlobalSecondaryIndexes: [\n {\n IndexName: 'GSI1',\n KeySchema: [\n {\n AttributeName: 'gsi1pk',\n KeyType: 'HASH' as const\n },\n {\n AttributeName: 'gsi1sk',\n KeyType: 'RANGE' as const\n }\n ],\n Projection: {\n ProjectionType: 'ALL' as const\n }\n }\n ]\n }\n\n await client.send(new CreateTableCommand(createTableParams))\n this.log(`βœ… Created table '${tableName}'`)\n\n // Wait for table to be active before configuring additional features\n this.log('⏳ Waiting for table to be active...')\n let isActive = false\n while (!isActive) {\n await new Promise(resolve => setTimeout(resolve, 2000))\n const response = await client.send(new DescribeTableCommand({ TableName: tableName }))\n isActive = response.Table?.TableStatus === 'ACTIVE'\n }\n\n // Enable point-in-time recovery\n try {\n await client.send(new UpdateContinuousBackupsCommand({\n TableName: tableName,\n PointInTimeRecoverySpecification: {\n PointInTimeRecoveryEnabled: true\n }\n }))\n this.log('βœ… Enabled point-in-time recovery')\n } catch (error) {\n this.log(`⚠️ Could not enable point-in-time recovery: ${(error as Error).message}`)\n }\n\n // Add tags\n try {\n // Get table ARN first\n const describeResponse = await client.send(new DescribeTableCommand({ TableName: tableName }))\n const tableArn = describeResponse.Table?.TableArn\n\n if (tableArn) {\n await client.send(new TagResourceCommand({\n ResourceArn: tableArn,\n Tags: [\n {\n Key: 'Module',\n Value: 'module-registry'\n },\n {\n Key: 'Purpose',\n Value: 'StateStore'\n }\n ]\n }))\n this.log('βœ… Added compliance tags to table')\n }\n } catch (error) {\n this.log(`⚠️ Could not add tags: ${(error as Error).message}`)\n }\n }\n\n // Now update registry data\n await this.updateRegistryData()\n\n } catch (error) {\n this.log(`❌ Error ensuring table and updating data: ${(error as Error).message}`)\n // Don't fail deployment for registry operations\n }\n }\n\n /**\n * Update registry data in DynamoDB after deployment\n */\n private async updateRegistryData(): Promise<void> {\n const totalEntries = this.moduleEntries.length + this.featureEntries.length\n if (this.config.skipDynamoDB || totalEntries === 0) {\n this.log('⏭️ Skipping registry data update')\n return\n }\n\n this.log(`πŸ“ Updating registry data in DynamoDB (${totalEntries} entries)...`)\n\n try {\n const { DynamoDBClient, BatchWriteItemCommand } = await import('@aws-sdk/client-dynamodb')\n const { marshall } = await import('@aws-sdk/util-dynamodb')\n \n const client = new DynamoDBClient({ region: this.config.region })\n\n // Combine all entries for batch processing\n const allItems: any[] = []\n\n // Add module entries\n for (const moduleEntry of this.moduleEntries) {\n allItems.push({\n pk: `MODULE#${moduleEntry.moduleName}`,\n sk: 'MODULE',\n gsi1pk: 'MODULES',\n gsi1sk: `MODULE#${moduleEntry.moduleName}`,\n itemType: 'module',\n ...moduleEntry\n })\n }\n\n // Add feature entries\n for (const featureEntry of this.featureEntries) {\n allItems.push({\n pk: `MODULE#${featureEntry.moduleName}`,\n sk: `FEATURE#${featureEntry.featureName}`,\n gsi1pk: `MODULE#${featureEntry.moduleName}`,\n gsi1sk: `FEATURE#${featureEntry.featureName}`,\n itemType: 'feature',\n ...featureEntry\n })\n }\n\n // Batch write all entries\n const batchSize = 25 // DynamoDB batch write limit\n const batches = this.chunkArray(allItems, batchSize)\n\n for (const batch of batches) {\n const putRequests = batch.map(item => ({\n PutRequest: {\n Item: marshall(item)\n }\n }))\n\n const command = new BatchWriteItemCommand({\n RequestItems: {\n [this.config.tableName!]: putRequests\n }\n })\n\n await client.send(command)\n }\n\n this.log(`βœ… Registry data updated successfully: ${this.moduleEntries.length} modules, ${this.featureEntries.length} features`)\n } catch (error) {\n this.log(`❌ Error updating registry data: ${(error as Error).message}`)\n // Don't fail deployment for registry update errors\n }\n }\n\n /**\n * Utility function to chunk array into smaller arrays\n */\n private chunkArray<T>(array: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size))\n }\n return chunks\n }\n\n /**\n * Enhanced logging with color and prefix\n */\n private log(message: string): void {\n this.serverless.cli?.log?.(`${chalk.blue('[module-registry]')} ${message}`) ||\n console.log(`${chalk.blue('[module-registry]')} ${message}`)\n }\n}\n\n// Export service functions for direct import by handlers and other modules\nexport {\n listAllModules,\n getModuleFeatures, \n getFeatureDetails,\n getModuleMetadata,\n getAllEndpoints,\n createModuleRegistryLogger,\n type ModuleInfo,\n type FeatureInfo,\n type FeatureDetails,\n type EndpointInfo\n} from './service'\n\n// Export the plugin class as both default and named export for compatibility\nexport default ServerlessModuleRegistryPlugin\nexport { ServerlessModuleRegistryPlugin }\n\n// For CommonJS compatibility with Serverless Framework v4\n// @ts-ignore - Allow CommonJS export in ESM context for backward compatibility\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = ServerlessModuleRegistryPlugin\n module.exports.default = ServerlessModuleRegistryPlugin\n}\n","/*\n * Module Registry Service Functions\n * -------------------------------------------\n * Service layer for querying module registry data from DynamoDB.\n * These functions can be imported directly from the plugin package.\n * \n * Usage:\n * import { listAllModules, getModuleFeatures } from 'serverless-plugin-module-registry'\n */\n\nimport { DynamoDBClient, QueryCommand, ScanCommand } from '@aws-sdk/client-dynamodb'\nimport { unmarshall } from '@aws-sdk/util-dynamodb'\n\n// Initialize DynamoDB client\nconst getDynamoClient = () => {\n return new DynamoDBClient({ \n region: process.env.AWS_REGION || 'us-east-1' \n })\n}\n\n// Configuration\nconst getTableName = () => {\n // Check for explicit table name first\n if (process.env.MODULE_REGISTRY_TABLE_NAME) {\n return process.env.MODULE_REGISTRY_TABLE_NAME\n }\n \n // Fallback to ARN prefix pattern (same as what the plugin uses)\n const service = process.env.SERVICE_NAME || 'api'\n const stage = process.env.STAGE || process.env.NODE_ENV || 'live'\n const defaultPrefix = `${service}-${stage}`\n \n return process.env.ARN_PREFIX \n ? `${process.env.ARN_PREFIX}-module-registry`\n : `${defaultPrefix}-module-registry`\n}\n\nconst GSI1_NAME = 'GSI1'\n\n// Types for better developer experience\nexport interface ModuleInfo {\n moduleName: string\n description: string\n version: string\n maintainer?: string\n tags: string[]\n lastUpdated: string\n}\n\nexport interface FeatureInfo {\n featureName: string\n description: string\n version: string\n endpoints: string[]\n customPolicies: any[]\n endpointCount: number\n lastUpdated: string\n}\n\nexport interface FeatureDetails extends FeatureInfo {\n moduleName: string\n policyCount: number\n}\n\nexport interface EndpointInfo {\n endpoint: string\n moduleName: string\n featureName: string\n featureDescription: string\n version: string\n}\n\n/**\n * Lists all deployed modules with basic metadata\n * @returns {Promise<ModuleInfo[]>} Array of module objects with clean data (no DDB internals)\n */\nexport const listAllModules = async (): Promise<ModuleInfo[]> => {\n const client = getDynamoClient()\n \n try {\n const command = new QueryCommand({\n TableName: getTableName(),\n IndexName: GSI1_NAME,\n KeyConditionExpression: 'gsi1pk = :pk',\n FilterExpression: 'itemType = :itemType',\n ExpressionAttributeValues: {\n ':pk': { S: 'MODULES' },\n ':itemType': { S: 'module' }\n }\n })\n\n const response = await client.send(command)\n \n if (!response.Items) {\n return []\n }\n\n // Transform DDB items to clean module objects\n const modules = response.Items.map(item => {\n const data = unmarshall(item)\n \n return {\n moduleName: data.moduleName,\n description: data.description,\n version: data.version,\n maintainer: data.maintainer,\n tags: data.tags || [],\n lastUpdated: data.lastUpdated\n }\n })\n\n return modules\n\n } catch (error) {\n throw new Error(`Failed to list modules: ${(error as Error).message}`)\n }\n}\n\n/**\n * Gets all features for a specific module\n * @param {string} moduleName - The module name (e.g., 'sign')\n * @returns {Promise<FeatureInfo[]>} Array of feature objects with endpoints\n */\nexport const getModuleFeatures = async (moduleName: string): Promise<FeatureInfo[]> => {\n if (!moduleName) {\n throw new Error('Module name is required')\n }\n\n const client = getDynamoClient()\n \n try {\n const command = new QueryCommand({\n TableName: getTableName(),\n KeyConditionExpression: 'pk = :pk AND begins_with(sk, :skPrefix)',\n FilterExpression: 'itemType = :itemType',\n ExpressionAttributeValues: {\n ':pk': { S: `MODULE#${moduleName}` },\n ':skPrefix': { S: 'FEATURE#' },\n ':itemType': { S: 'feature' }\n }\n })\n\n const response = await client.send(command)\n \n if (!response.Items) {\n return []\n }\n\n // Transform DDB items to clean feature objects\n const features = response.Items.map(item => {\n const data = unmarshall(item)\n \n return {\n featureName: data.featureName,\n description: data.description,\n version: data.version,\n endpoints: data.endpoints || [],\n customPolicies: data.customPolicies || [],\n endpointCount: (data.endpoints || []).length,\n lastUpdated: data.lastUpdated\n }\n })\n\n return features\n\n } catch (error) {\n throw new Error(`Failed to get module features: ${(error as Error).message}`)\n }\n}\n\n/**\n * Gets detailed information about a specific feature\n * @param {string} moduleName - The module name\n * @param {string} featureName - The feature name\n * @returns {Promise<FeatureDetails|null>} Feature details with endpoints and policies\n */\nexport const getFeatureDetails = async (moduleName: string, featureName: string): Promise<FeatureDetails | null> => {\n if (!moduleName) {\n throw new Error('Module name is required')\n }\n if (!featureName) {\n throw new Error('Feature name is required')\n }\n\n const client = getDynamoClient()\n \n try {\n const command = new QueryCommand({\n TableName: getTableName(),\n KeyConditionExpression: 'pk = :pk AND sk = :sk',\n ExpressionAttributeValues: {\n ':pk': { S: `MODULE#${moduleName}` },\n ':sk': { S: `FEATURE#${featureName}` }\n }\n })\n\n const response = await client.send(command)\n \n if (!response.Items || response.Items.length === 0) {\n return null\n }\n\n // Transform DDB item to clean feature object\n const data = unmarshall(response.Items[0])\n \n const featureDetails: FeatureDetails = {\n moduleName: data.moduleName,\n featureName: data.featureName,\n description: data.description,\n version: data.version,\n endpoints: data.endpoints || [],\n customPolicies: data.customPolicies || [],\n endpointCount: (data.endpoints || []).length,\n policyCount: (data.customPolicies || []).length,\n lastUpdated: data.lastUpdated\n }\n\n return featureDetails\n\n } catch (error) {\n throw new Error(`Failed to get feature details: ${(error as Error).message}`)\n }\n}\n\n/**\n * Gets module metadata (basic info without features)\n * @param {string} moduleName - The module name\n * @returns {Promise<ModuleInfo|null>} Module metadata or null if not found\n */\nexport const getModuleMetadata = async (moduleName: string): Promise<ModuleInfo | null> => {\n if (!moduleName) {\n throw new Error('Module name is required')\n }\n\n const client = getDynamoClient()\n \n try {\n const command = new QueryCommand({\n TableName: getTableName(),\n KeyConditionExpression: 'pk = :pk AND sk = :sk',\n ExpressionAttributeValues: {\n ':pk': { S: `MODULE#${moduleName}` },\n ':sk': { S: 'MODULE' }\n }\n })\n\n const response = await client.send(command)\n \n if (!response.Items || response.Items.length === 0) {\n return null\n }\n\n // Transform DDB item to clean module object\n const data = unmarshall(response.Items[0])\n \n const moduleMetadata: ModuleInfo = {\n moduleName: data.moduleName,\n description: data.description,\n version: data.version,\n maintainer: data.maintainer,\n tags: data.tags || [],\n lastUpdated: data.lastUpdated\n }\n\n return moduleMetadata\n\n } catch (error) {\n throw new Error(`Failed to get module metadata: ${(error as Error).message}`)\n }\n}\n\n/**\n * Gets endpoint summary across all modules for discovery\n * @returns {Promise<EndpointInfo[]>} Array of endpoints with module/feature context\n */\nexport const getAllEndpoints = async (): Promise<EndpointInfo[]> => {\n const client = getDynamoClient()\n \n try {\n const command = new ScanCommand({\n TableName: getTableName(),\n FilterExpression: 'itemType = :itemType',\n ExpressionAttributeValues: {\n ':itemType': { S: 'feature' }\n }\n })\n\n const response = await client.send(command)\n \n if (!response.Items) {\n return []\n }\n\n // Transform to flat endpoint list with context\n const endpoints: EndpointInfo[] = []\n \n for (const item of response.Items) {\n const data = unmarshall(item)\n \n if (data.endpoints && Array.isArray(data.endpoints)) {\n for (const endpoint of data.endpoints) {\n endpoints.push({\n endpoint,\n moduleName: data.moduleName,\n featureName: data.featureName,\n featureDescription: data.description,\n version: data.version\n })\n }\n }\n }\n\n return endpoints\n\n } catch (error) {\n throw new Error(`Failed to get all endpoints: ${(error as Error).message}`)\n }\n}\n\n/**\n * Utility function to create logger compatible with service-style.md\n * This can be used in handlers that import the service functions\n */\nexport const createModuleRegistryLogger = (context?: string) => {\n const logContext = context || 'module-registry'\n \n return {\n info: (message: string, data?: any) => {\n console.log(`[${logContext}] INFO: ${message}`, data ? JSON.stringify(data) : '')\n },\n error: (message: string, data?: any) => {\n console.error(`[${logContext}] ERROR: ${message}`, data ? JSON.stringify(data) : '')\n },\n warn: (message: string, data?: any) => {\n console.warn(`[${logContext}] WARN: ${message}`, data ? JSON.stringify(data) : '')\n }\n }\n}\n"],"mappings":"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","names":["path","fs","yaml","DynamoDBClient","chalk"]}
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "serverless-plugin-module-registry",
3
+ "version": "1.0.4",
4
+ "description": "A Serverless Framework plugin that scans module registry files and stores feature mappings in DynamoDB for cross-module discovery",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./service": {
13
+ "types": "./dist/service.d.ts",
14
+ "default": "./dist/service.js"
15
+ }
16
+ },
17
+ "keywords": [
18
+ "serverless",
19
+ "plugin",
20
+ "module",
21
+ "directory",
22
+ "aws",
23
+ "serverless-plugin",
24
+ "cloudformation",
25
+ "devops",
26
+ "infrastructure"
27
+ ],
28
+ "author": {
29
+ "name": "DevSquad",
30
+ "email": "marcelo@devsquad.email",
31
+ "url": "https://www.grupokingdom.com/devsquad"
32
+ },
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git@gitlab.com:dev_squad/repo/tooling/serverless/module-registry.git"
37
+ },
38
+ "homepage": "https://gitlab.com/dev_squad/repo/tooling/serverless/module-registry#readme",
39
+ "bugs": {
40
+ "url": "https://gitlab.com/dev_squad/repo/tooling/serverless/module-registry/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=14.0.0"
44
+ },
45
+ "files": [
46
+ "dist/**/*",
47
+ "README.md",
48
+ "LICENSE"
49
+ ],
50
+ "peerDependencies": {
51
+ "serverless": ">=2.0.0"
52
+ },
53
+ "dependencies": {
54
+ "chalk": "^4.1.2",
55
+ "glob": "^10.3.10",
56
+ "js-yaml": "^4.1.0",
57
+ "@aws-sdk/client-dynamodb": "^3.891.0",
58
+ "@aws-sdk/util-dynamodb": "^3.891.0"
59
+ },
60
+ "scripts": {
61
+ "build": "tsup",
62
+ "watch": "tsup --watch",
63
+ "clean": "rm -rf dist",
64
+ "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
65
+ "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
66
+ "test": "echo 'Tests disabled' || jest --passWithNoTests",
67
+ "test:watch": "jest --watch",
68
+ "test:coverage": "jest --coverage",
69
+ "typecheck": "tsc --noEmit",
70
+ "prepublishOnly": "npm run clean && npm run build && npm test"
71
+ },
72
+ "devDependencies": {
73
+ "@types/jest": "^30.0.0",
74
+ "@types/node": "^24.4.0",
75
+ "@types/serverless": "^3.12.27",
76
+ "@types/js-yaml": "^4.0.9",
77
+ "@types/glob": "^8.1.0",
78
+ "@typescript-eslint/eslint-plugin": "^7.3.1",
79
+ "@typescript-eslint/parser": "^7.3.1",
80
+ "eslint": "^8.57.0",
81
+ "jest": "^29.7.0",
82
+ "serverless": "^4.18.2",
83
+ "ts-jest": "^29.4.1",
84
+ "tsup": "^8.0.1",
85
+ "typescript": "^5.9.2"
86
+ },
87
+ "publishConfig": {
88
+ "access": "public",
89
+ "registry": "https://registry.npmjs.org/"
90
+ }
91
+ }