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 +21 -0
- package/README.md +356 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +632 -0
- package/dist/index.js.map +1 -0
- package/package.json +91 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|