@adminide-stack/marketplace-module-server 12.0.4-alpha.13 → 12.0.4-alpha.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +321 -0
- package/lib/containers/module.d.ts +7 -0
- package/lib/containers/module.d.ts.map +1 -1
- package/lib/containers/module.js +11 -5
- package/lib/containers/module.js.map +1 -1
- package/lib/dataloaders/index.d.ts +1 -0
- package/lib/dataloaders/index.d.ts.map +1 -0
- package/lib/demo/test-graphql-examples.d.ts +73 -0
- package/lib/demo/test-graphql-examples.d.ts.map +1 -0
- package/lib/graphql/resolvers/form-templates-resolver.d.ts +220 -0
- package/lib/graphql/resolvers/form-templates-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/form-templates-resolver.js +170 -0
- package/lib/graphql/resolvers/form-templates-resolver.js.map +1 -0
- package/lib/graphql/resolvers/gallery-resolver.d.ts +15 -0
- package/lib/graphql/resolvers/gallery-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/gallery-resolver.js +35 -0
- package/lib/graphql/resolvers/gallery-resolver.js.map +1 -0
- package/lib/graphql/resolvers/index.d.ts +247 -1
- package/lib/graphql/resolvers/index.d.ts.map +1 -1
- package/lib/graphql/resolvers/index.js +1 -0
- package/lib/graphql/resolvers/index.js.map +1 -0
- package/lib/graphql/resolvers/installed-extension-resolver.d.ts +5 -0
- package/lib/graphql/resolvers/installed-extension-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/installed-extension-resolver.js +276 -0
- package/lib/graphql/resolvers/installed-extension-resolver.js.map +1 -0
- package/lib/graphql/resolvers/marketplace-form-resolver.d.ts +13 -0
- package/lib/graphql/resolvers/marketplace-form-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/marketplace-form-resolver.js +90 -0
- package/lib/graphql/resolvers/marketplace-form-resolver.js.map +1 -0
- package/lib/graphql/resolvers/publisher-analytics-resolver.d.ts +14 -0
- package/lib/graphql/resolvers/publisher-analytics-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/publisher-analytics-resolver.js +221 -0
- package/lib/graphql/resolvers/publisher-analytics-resolver.js.map +1 -0
- package/lib/graphql/resolvers/publisher-resolver.d.ts +5 -0
- package/lib/graphql/resolvers/publisher-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/publisher-resolver.js +176 -0
- package/lib/graphql/resolvers/publisher-resolver.js.map +1 -0
- package/lib/graphql/resolvers/registry-extension-resolver.d.ts +5 -0
- package/lib/graphql/resolvers/registry-extension-resolver.d.ts.map +1 -0
- package/lib/graphql/resolvers/registry-extension-resolver.js +46 -0
- package/lib/graphql/resolvers/registry-extension-resolver.js.map +1 -0
- package/lib/graphql/schemas/extension-pricing.graphql +546 -0
- package/lib/graphql/schemas/extension-pricing.graphql.js +1 -0
- package/lib/graphql/schemas/extension-pricing.graphql.js.map +1 -0
- package/lib/graphql/schemas/extension-registry.graphql +104 -0
- package/lib/graphql/schemas/extension-registry.graphql.js +1 -0
- package/lib/graphql/schemas/extension-registry.graphql.js.map +1 -0
- package/lib/graphql/schemas/form-templates.graphql +269 -0
- package/lib/graphql/schemas/form-templates.graphql.js +1 -0
- package/lib/graphql/schemas/form-templates.graphql.js.map +1 -0
- package/lib/graphql/schemas/gallery-schema.graphql +247 -0
- package/lib/graphql/schemas/gallery-schema.graphql.js +1 -0
- package/lib/graphql/schemas/gallery-schema.graphql.js.map +1 -0
- package/lib/graphql/schemas/index.d.ts.map +1 -1
- package/lib/graphql/schemas/index.js +1 -4
- package/lib/graphql/schemas/index.js.map +1 -1
- package/lib/graphql/schemas/installed-extension.graphql +295 -0
- package/lib/graphql/schemas/installed-extension.graphql.js +1 -0
- package/lib/graphql/schemas/installed-extension.graphql.js.map +1 -0
- package/lib/graphql/schemas/publisher-analytics.graphql +305 -0
- package/lib/graphql/schemas/publisher-analytics.graphql.js +1 -0
- package/lib/graphql/schemas/publisher-analytics.graphql.js.map +1 -0
- package/lib/graphql/schemas/publisher.graphql +376 -0
- package/lib/graphql/schemas/publisher.graphql.js +1 -0
- package/lib/graphql/schemas/publisher.graphql.js.map +1 -0
- package/lib/graphql/schemas/service.graphql +181 -0
- package/lib/graphql/schemas/service.graphql.js +1 -0
- package/lib/graphql/schemas/service.graphql.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/module.d.ts.map +1 -1
- package/lib/module.js +9 -23
- package/lib/module.js.map +1 -1
- package/lib/plugins/extension-moleculer-service.d.ts +86 -0
- package/lib/plugins/extension-moleculer-service.d.ts.map +1 -0
- package/lib/plugins/index.d.ts +2 -0
- package/lib/plugins/index.d.ts.map +1 -0
- package/lib/services/extension-gallery-repository.d.ts +17 -0
- package/lib/services/extension-gallery-repository.d.ts.map +1 -0
- package/lib/services/extension-gallery-repository.js +192 -0
- package/lib/services/extension-gallery-repository.js.map +1 -0
- package/lib/services/extension-gallery-service-new.d.ts +39 -0
- package/lib/services/extension-gallery-service-new.d.ts.map +1 -0
- package/lib/services/extension-gallery-service.d.ts +30 -0
- package/lib/services/extension-gallery-service.d.ts.map +1 -0
- package/lib/services/extension-gallery-service.js +311 -0
- package/lib/services/extension-gallery-service.js.map +1 -0
- package/lib/services/index.d.ts +6 -1
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/installed-extension-service-ext.d.ts +14 -0
- package/lib/services/installed-extension-service-ext.d.ts.map +1 -0
- package/lib/services/installed-extension-service-ext.js +401 -0
- package/lib/services/installed-extension-service-ext.js.map +1 -0
- package/lib/services/installed-extension-service.d.ts +96 -0
- package/lib/services/installed-extension-service.d.ts.map +1 -0
- package/lib/services/installed-extension-service.js +521 -0
- package/lib/services/installed-extension-service.js.map +1 -0
- package/lib/services/installed-extension-service.test.d.ts +1 -0
- package/lib/services/installed-extension-service.test.d.ts.map +1 -0
- package/lib/services/publisher-analytics-service.d.ts +128 -0
- package/lib/services/publisher-analytics-service.d.ts.map +1 -0
- package/lib/services/publisher-event-service.d.ts +48 -0
- package/lib/services/publisher-event-service.d.ts.map +1 -0
- package/lib/services/publisher-event-service.js +296 -0
- package/lib/services/publisher-event-service.js.map +1 -0
- package/lib/services/publisher-service-context.d.ts +1 -0
- package/lib/services/publisher-service-context.d.ts.map +1 -0
- package/lib/services/publisher-service.d.ts +60 -0
- package/lib/services/publisher-service.d.ts.map +1 -0
- package/lib/services/publisher-service.js +134 -0
- package/lib/services/publisher-service.js.map +1 -0
- package/lib/store/index.d.ts +1 -1
- package/lib/store/index.d.ts.map +1 -1
- package/lib/store/models/index.d.ts +2 -1
- package/lib/store/models/index.d.ts.map +1 -1
- package/lib/store/models/installed-extension-model.d.ts +4 -0
- package/lib/store/models/installed-extension-model.d.ts.map +1 -0
- package/lib/store/models/installed-extension-model.js +260 -0
- package/lib/store/models/installed-extension-model.js.map +1 -0
- package/lib/store/models/publisher-event-model.d.ts +11 -0
- package/lib/store/models/publisher-event-model.d.ts.map +1 -0
- package/lib/store/models/publisher-model.d.ts +5 -0
- package/lib/store/models/publisher-model.d.ts.map +1 -0
- package/lib/store/models/publisher-model.js +103 -0
- package/lib/store/models/publisher-model.js.map +1 -0
- package/lib/store/models/publisher-stats-model.d.ts +1 -0
- package/lib/store/models/publisher-stats-model.d.ts.map +1 -0
- package/lib/store/repositories/index.d.ts +3 -0
- package/lib/store/repositories/index.d.ts.map +1 -0
- package/lib/store/repositories/installed-extension-repository.d.ts +73 -0
- package/lib/store/repositories/installed-extension-repository.d.ts.map +1 -0
- package/lib/store/repositories/installed-extension-repository.js +442 -0
- package/lib/store/repositories/installed-extension-repository.js.map +1 -0
- package/lib/store/repositories/publisher-analytics-repository.d.ts +1 -0
- package/lib/store/repositories/publisher-analytics-repository.d.ts.map +1 -0
- package/lib/store/repositories/publisher-repository.d.ts +19 -0
- package/lib/store/repositories/publisher-repository.d.ts.map +1 -0
- package/lib/store/repositories/publisher-repository.js +87 -0
- package/lib/store/repositories/publisher-repository.js.map +1 -0
- package/lib/templates/constants/DB_COLL_NAMES.ts.template +5 -0
- package/lib/templates/constants/SERVER_TYPES.ts.template +10 -4
- package/lib/templates/repositories/ExtensionGalleryRepository.ts.template +44 -0
- package/lib/templates/repositories/InstalledExtensionRepository.ts.template +94 -0
- package/lib/templates/repositories/MarketplacePublisherRepository.ts.template +24 -0
- package/lib/templates/repositories/RegistryExtensionRepository.ts.template +10 -15
- package/lib/templates/services/ExtensionGalleryDataLoader.ts.template +2 -0
- package/lib/templates/services/ExtensionGalleryService.ts.template +79 -0
- package/lib/templates/services/InstalledExtensionDataLoader.ts.template +2 -0
- package/lib/templates/services/InstalledExtensionService.ts.template +181 -0
- package/lib/templates/services/MarketplacePublisherService.ts.template +49 -0
- package/lib/templates/services/PublisherEventService.ts.template +56 -0
- package/lib/templates/services/RegistryExtensionService.ts.template +62 -18
- package/lib/tests/extension-integration.test.d.ts +1 -0
- package/lib/tests/extension-integration.test.d.ts.map +1 -0
- package/lib/tests/install-extension-graphql.test.d.ts +2 -0
- package/lib/tests/install-extension-graphql.test.d.ts.map +1 -0
- package/lib/tests/test-extension-services.d.ts +1 -0
- package/lib/tests/test-extension-services.d.ts.map +1 -0
- package/lib/utils/publisherValidation.d.ts +23 -0
- package/lib/utils/publisherValidation.d.ts.map +1 -0
- package/lib/utils/publisherValidation.js +144 -0
- package/lib/utils/publisherValidation.js.map +1 -0
- package/package.json +15 -7
- package/lib/graphql/resolvers/resolvers.d.ts +0 -2
- package/lib/graphql/resolvers/resolvers.d.ts.map +0 -1
- package/lib/graphql/resolvers/resolvers.js +0 -167
- package/lib/graphql/resolvers/resolvers.js.map +0 -1
- package/lib/graphql/schemas/extension.graphql +0 -57
- package/lib/graphql/schemas/extension.graphql.js +0 -1
- package/lib/graphql/schemas/extension.graphql.js.map +0 -1
- package/lib/services/extension-service.d.ts +0 -54
- package/lib/services/extension-service.d.ts.map +0 -1
- package/lib/services/extension-service.js +0 -42
- package/lib/services/extension-service.js.map +0 -1
- package/lib/store/models/registry-extension-model.d.ts +0 -10
- package/lib/store/models/registry-extension-model.d.ts.map +0 -1
- package/lib/store/models/registry-extension-model.js +0 -62
- package/lib/store/models/registry-extension-model.js.map +0 -1
- package/lib/store/repository/index.d.ts +0 -2
- package/lib/store/repository/index.d.ts.map +0 -1
- package/lib/store/repository/registry-extension-repository.d.ts +0 -31
- package/lib/store/repository/registry-extension-repository.d.ts.map +0 -1
- package/lib/store/repository/registry-extension-repository.js +0 -135
- package/lib/store/repository/registry-extension-repository.js.map +0 -1
package/Readme.md
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Extension Marketplace Server
|
|
2
|
+
|
|
3
|
+
This module handles the extension marketplace functionality, including extension registry management, installation lifecycle, and publisher actions.
|
|
4
|
+
|
|
5
|
+
## Extension Lifecycle Management
|
|
6
|
+
|
|
7
|
+
### Overview
|
|
8
|
+
|
|
9
|
+
Publishers should generally **not** be able to completely unregister extensions that users have already installed, but there are nuanced approaches to handle extension lifecycle scenarios while balancing publisher needs, user expectations, and platform security.
|
|
10
|
+
|
|
11
|
+
## Recommended Approach: Soft Deprecation vs Hard Removal
|
|
12
|
+
|
|
13
|
+
### 1. Publisher Actions Available
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
// In extensionRegistry collection
|
|
17
|
+
{
|
|
18
|
+
"_id": ObjectId("67c2c30f860196612c58b082"),
|
|
19
|
+
"extensionID": "stackflow1/builder-extension",
|
|
20
|
+
"publisher": "stackflow1",
|
|
21
|
+
"status": "active", // active, deprecated, suspended, removed
|
|
22
|
+
"deprecation": {
|
|
23
|
+
"isDeprecated": false,
|
|
24
|
+
"deprecatedAt": null,
|
|
25
|
+
"reason": null, // "security", "maintenance", "replaced", "violation"
|
|
26
|
+
"replacementExtension": null, // Alternative extension ID
|
|
27
|
+
"endOfLifeDate": null, // When it will stop working
|
|
28
|
+
"migrationGuide": null // URL to migration instructions
|
|
29
|
+
},
|
|
30
|
+
"publisherActions": {
|
|
31
|
+
"canDeprecate": true,
|
|
32
|
+
"canSuspendNewInstalls": true,
|
|
33
|
+
"canRemoveFromMarketplace": true,
|
|
34
|
+
"canForceUninstall": false // Requires admin approval
|
|
35
|
+
},
|
|
36
|
+
"versions": [
|
|
37
|
+
{
|
|
38
|
+
"version": "1.0.0",
|
|
39
|
+
"status": "active", // active, deprecated, yanked, security-hold
|
|
40
|
+
"yankedAt": null,
|
|
41
|
+
"yankedReason": null
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Enhanced installedExtensions with Lifecycle Management
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
// In installedExtensions collection
|
|
51
|
+
{
|
|
52
|
+
"_id": ObjectId("67c2c30f860196612c58b080"),
|
|
53
|
+
"tenantId": "default",
|
|
54
|
+
"registryRef": ObjectId("67c2c30f860196612c58b082"),
|
|
55
|
+
"extensionID": "stackflow1/builder-extension",
|
|
56
|
+
"installedVersion": "1.0.0",
|
|
57
|
+
"status": "installed", // installed, deprecated-installed, suspended, orphaned
|
|
58
|
+
"lifecycle": {
|
|
59
|
+
"registryStatus": "active", // Cached from registry
|
|
60
|
+
"isOrphaned": false, // True if extension removed from registry
|
|
61
|
+
"deprecationWarningShown": false,
|
|
62
|
+
"autoUpdateBlocked": false,
|
|
63
|
+
"lastRegistryCheck": ISODate("2025-03-01T11:00:00.000+0000")
|
|
64
|
+
},
|
|
65
|
+
"policies": {
|
|
66
|
+
"allowOrphanedExecution": true, // Admin policy
|
|
67
|
+
"requireSecurityUpdates": true,
|
|
68
|
+
"autoRemoveDeprecated": false,
|
|
69
|
+
"deprecationGracePeriod": 90 // days
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Publisher Actions and Their Effects
|
|
75
|
+
|
|
76
|
+
### 1. **Deprecate Extension** (Recommended)
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
// Publisher action: Deprecate
|
|
80
|
+
db.extensionRegistry.updateOne(
|
|
81
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
82
|
+
{
|
|
83
|
+
$set: {
|
|
84
|
+
status: 'deprecated',
|
|
85
|
+
deprecation: {
|
|
86
|
+
isDeprecated: true,
|
|
87
|
+
deprecatedAt: new Date(),
|
|
88
|
+
reason: 'maintenance',
|
|
89
|
+
replacementExtension: 'stackflow1/builder-v2',
|
|
90
|
+
endOfLifeDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
|
|
91
|
+
migrationGuide: 'https://docs.example.com/migration-guide',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Effect on installed extensions: Still works, but users get warnings
|
|
98
|
+
db.installedExtensions.updateMany(
|
|
99
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
100
|
+
{
|
|
101
|
+
$set: {
|
|
102
|
+
status: 'deprecated-installed',
|
|
103
|
+
'lifecycle.registryStatus': 'deprecated',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 2. **Suspend New Installations**
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
// Publisher action: Suspend new installs (existing continue to work)
|
|
113
|
+
db.extensionRegistry.updateOne(
|
|
114
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
115
|
+
{
|
|
116
|
+
$set: {
|
|
117
|
+
status: 'suspended',
|
|
118
|
+
suspension: {
|
|
119
|
+
suspendedAt: new Date(),
|
|
120
|
+
reason: 'security-review',
|
|
121
|
+
allowExistingInstalls: true,
|
|
122
|
+
estimatedResolution: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3. **Remove from Marketplace** (Registry remains for installed extensions)
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// Publisher action: Remove from marketplace but keep registry entry
|
|
133
|
+
db.extensionRegistry.updateOne(
|
|
134
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
135
|
+
{
|
|
136
|
+
$set: {
|
|
137
|
+
status: 'removed',
|
|
138
|
+
marketplaceVisibility: 'hidden',
|
|
139
|
+
removal: {
|
|
140
|
+
removedAt: new Date(),
|
|
141
|
+
reason: 'discontinued',
|
|
142
|
+
keepForInstalledUsers: true,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Installed extensions become "orphaned" but continue to work
|
|
149
|
+
db.installedExtensions.updateMany(
|
|
150
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
151
|
+
{
|
|
152
|
+
$set: {
|
|
153
|
+
status: 'orphaned',
|
|
154
|
+
'lifecycle.isOrphaned': true,
|
|
155
|
+
'lifecycle.registryStatus': 'removed',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4. **Emergency Removal** (Admin-only, rare cases)
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// Admin/Platform action only (security issues, legal violations)
|
|
165
|
+
db.extensionRegistry.updateOne(
|
|
166
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
167
|
+
{
|
|
168
|
+
$set: {
|
|
169
|
+
status: 'emergency-removed',
|
|
170
|
+
emergencyRemoval: {
|
|
171
|
+
removedAt: new Date(),
|
|
172
|
+
removedBy: 'platform-admin',
|
|
173
|
+
reason: 'security-vulnerability',
|
|
174
|
+
forceUninstall: true,
|
|
175
|
+
notificationSent: true,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Force disable all installations
|
|
182
|
+
db.installedExtensions.updateMany(
|
|
183
|
+
{ extensionID: 'stackflow1/builder-extension' },
|
|
184
|
+
{
|
|
185
|
+
$set: {
|
|
186
|
+
status: 'force-disabled',
|
|
187
|
+
'settings.systemEnabled': false,
|
|
188
|
+
'settings.effectiveEnabled': false,
|
|
189
|
+
'runtime.activationState': 'disabled-by-admin',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## User Experience for Different Scenarios
|
|
196
|
+
|
|
197
|
+
### 1. Deprecated Extension
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// User sees warning but extension continues to work
|
|
201
|
+
interface DeprecationWarning {
|
|
202
|
+
type: 'deprecation';
|
|
203
|
+
message: 'Builder Extension is deprecated and will stop working on March 1, 2026';
|
|
204
|
+
actions: [
|
|
205
|
+
{ label: 'View Migration Guide'; url: 'https://docs.example.com/migration' },
|
|
206
|
+
{ label: 'Install Replacement'; extensionID: 'stackflow1/builder-v2' },
|
|
207
|
+
{ label: 'Dismiss'; action: 'dismiss' },
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 2. Suspended Extension
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// User sees info but extension continues to work
|
|
216
|
+
interface SuspensionNotice {
|
|
217
|
+
type: 'suspension';
|
|
218
|
+
message: 'Builder Extension is under security review. Updates suspended temporarily.';
|
|
219
|
+
estimatedResolution: '2025-03-08T00:00:00Z';
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 3. Orphaned Extension (Removed from marketplace)
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// User sees warning about no more updates
|
|
227
|
+
interface OrphanedWarning {
|
|
228
|
+
type: 'orphaned';
|
|
229
|
+
message: 'Builder Extension is no longer maintained. No updates will be available.';
|
|
230
|
+
actions: [
|
|
231
|
+
{ label: 'Find Alternatives'; action: 'browse-alternatives' },
|
|
232
|
+
{ label: 'Keep Using'; action: 'accept-risk' },
|
|
233
|
+
{ label: 'Uninstall'; action: 'uninstall' },
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Administrative Policies
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
// In tenant/organization settings
|
|
242
|
+
{
|
|
243
|
+
"extensionPolicies": {
|
|
244
|
+
"orphanedExtensions": {
|
|
245
|
+
"allowExecution": true,
|
|
246
|
+
"showWarnings": true,
|
|
247
|
+
"autoRemoveAfterDays": null
|
|
248
|
+
},
|
|
249
|
+
"deprecatedExtensions": {
|
|
250
|
+
"allowNewActivations": true,
|
|
251
|
+
"gracePeriodDays": 90,
|
|
252
|
+
"forceRemovalAfterEOL": false
|
|
253
|
+
},
|
|
254
|
+
"suspendedExtensions": {
|
|
255
|
+
"allowContinuedUse": true,
|
|
256
|
+
"disableAfterDays": null
|
|
257
|
+
},
|
|
258
|
+
"securityPolicies": {
|
|
259
|
+
"respectEmergencyDisables": true,
|
|
260
|
+
"allowManualOverride": false
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Query Examples
|
|
267
|
+
|
|
268
|
+
### Get Extensions Needing User Attention
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
db.installedExtensions.aggregate([
|
|
272
|
+
{
|
|
273
|
+
$match: {
|
|
274
|
+
tenantId: 'default',
|
|
275
|
+
'lifecycle.registryStatus': { $in: ['deprecated', 'suspended', 'removed'] },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
$lookup: {
|
|
280
|
+
from: 'extensionRegistry',
|
|
281
|
+
localField: 'registryRef',
|
|
282
|
+
foreignField: '_id',
|
|
283
|
+
as: 'registry',
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
$project: {
|
|
288
|
+
extensionID: 1,
|
|
289
|
+
status: 1,
|
|
290
|
+
lifecycle: 1,
|
|
291
|
+
deprecation: '$registry.deprecation',
|
|
292
|
+
suspension: '$registry.suspension',
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Best Practices
|
|
299
|
+
|
|
300
|
+
### Publishers Should:
|
|
301
|
+
|
|
302
|
+
- Use deprecation for planned discontinuation
|
|
303
|
+
- Provide migration guides and alternatives
|
|
304
|
+
- Give reasonable notice periods (6-12 months)
|
|
305
|
+
- Only request emergency removal for serious issues
|
|
306
|
+
|
|
307
|
+
### Platform Should:
|
|
308
|
+
|
|
309
|
+
- Require admin approval for emergency removals
|
|
310
|
+
- Maintain registry entries for installed extensions
|
|
311
|
+
- Provide clear user notifications
|
|
312
|
+
- Allow organizational policies to override defaults
|
|
313
|
+
|
|
314
|
+
### Users Should:
|
|
315
|
+
|
|
316
|
+
- Receive clear warnings about extension status changes
|
|
317
|
+
- Have time to migrate to alternatives
|
|
318
|
+
- Retain control over their installed extensions
|
|
319
|
+
- Be able to accept risks of using orphaned extensions
|
|
320
|
+
|
|
321
|
+
This approach balances publisher needs, user expectations, and platform security while avoiding the disruption of completely removing extensions that users depend on.
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import { interfaces } from 'inversify';
|
|
2
|
+
import { IInstalledExtensionService, IMarketplacePublisherService, IPublisherEventService } from 'common/server';
|
|
2
3
|
export declare const extensionModule: (settings: any) => interfaces.ContainerModule;
|
|
4
|
+
declare const createServiceFunc: (container: interfaces.Container) => {
|
|
5
|
+
installedExtensionService: IInstalledExtensionService;
|
|
6
|
+
publisherService: IMarketplacePublisherService;
|
|
7
|
+
publisherEventService: IPublisherEventService;
|
|
8
|
+
};
|
|
9
|
+
export { createServiceFunc };
|
|
3
10
|
//# sourceMappingURL=module.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/containers/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/containers/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,UAAU,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAEH,0BAA0B,EAG1B,4BAA4B,EAC5B,sBAAsB,EACzB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,eAAe,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,UAAU,CAAC,eAiCtD,CAAC;AAGP,QAAA,MAAM,iBAAiB,GAAI,WAAW,UAAU,CAAC,SAAS;;;;CAIxD,CAAC;AAEH,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
package/lib/containers/module.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {ContainerModule}from'inversify';import {SERVER_TYPES}from'common/server';import {
|
|
1
|
+
import {ContainerModule}from'inversify';import {SERVER_TYPES}from'common/server';import'../services/installed-extension-service.js';import {InstalledExtensionServiceExt}from'../services/installed-extension-service-ext.js';import {MarketplacePublisherService}from'../services/publisher-service.js';import {PublisherEventService}from'../services/publisher-event-service.js';import'../services/extension-gallery-service.js';import'../services/extension-gallery-repository.js';import'../store/models/installed-extension-model.js';import'../store/models/publisher-model.js';import {InstalledExtensionRepository}from'../store/repositories/installed-extension-repository.js';import {MarketplacePublisherRepository}from'../store/repositories/publisher-repository.js';const extensionModule = settings => new ContainerModule(bind => {
|
|
2
2
|
bind(SERVER_TYPES.IExtensionMongoConnection).toConstantValue(settings.mongoConnection);
|
|
3
|
-
// Extension Service
|
|
4
|
-
bind(SERVER_TYPES.
|
|
5
|
-
// Extension Repository
|
|
6
|
-
bind(SERVER_TYPES.
|
|
3
|
+
// Installed Extension Service
|
|
4
|
+
bind(SERVER_TYPES.IInstalledExtensionService).to(InstalledExtensionServiceExt).inSingletonScope().whenTargetIsDefault();
|
|
5
|
+
// Installed Extension Repository
|
|
6
|
+
bind(SERVER_TYPES.IInstalledExtensionRepository).to(InstalledExtensionRepository).inSingletonScope().whenTargetIsDefault();
|
|
7
|
+
// Publisher Service
|
|
8
|
+
bind(SERVER_TYPES.IMarketplacePublisherService).to(MarketplacePublisherService).inSingletonScope().whenTargetIsDefault();
|
|
9
|
+
// Publisher Repository
|
|
10
|
+
bind(SERVER_TYPES.IMarketplacePublisherRepository).to(MarketplacePublisherRepository).inSingletonScope().whenTargetIsDefault();
|
|
11
|
+
// Publisher Event Service
|
|
12
|
+
bind(SERVER_TYPES.IPublisherEventService).to(PublisherEventService).inSingletonScope().whenTargetIsDefault();
|
|
7
13
|
});export{extensionModule};//# sourceMappingURL=module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.js","sources":["../../src/containers/module.ts"],"sourcesContent":[null],"names":[],"mappings":";;;EAoDG,IAAA,CAAA,YAAA,CAAA,0BAAA,CAAA,CAAA,EAAA,CAAA,4BAAA,CAAA,CAAA,gBAAA,EAAA,CAAA,mBAAA,EAAA;AAEH;;;;;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dataloaders/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example GraphQL calls for testing the installExtension mutation
|
|
3
|
+
* with the new orgName parameter support
|
|
4
|
+
*/
|
|
5
|
+
declare const basicInstallExample: {
|
|
6
|
+
query: string;
|
|
7
|
+
variables: {
|
|
8
|
+
input: {
|
|
9
|
+
extensionSlug: string;
|
|
10
|
+
version: string;
|
|
11
|
+
settings: {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
declare const orgInstallExample: {
|
|
18
|
+
query: string;
|
|
19
|
+
variables: {
|
|
20
|
+
input: {
|
|
21
|
+
extensionSlug: string;
|
|
22
|
+
version: string;
|
|
23
|
+
orgName: string;
|
|
24
|
+
settings: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
userConfig: {
|
|
27
|
+
theme: string;
|
|
28
|
+
notifications: boolean;
|
|
29
|
+
autoSave: boolean;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
policies: {
|
|
33
|
+
autoUpdate: boolean;
|
|
34
|
+
allowBeta: boolean;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
declare const queryExtensionsExample: {
|
|
40
|
+
query: string;
|
|
41
|
+
variables: {
|
|
42
|
+
orgName: string;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
declare const expectedResponse: {
|
|
47
|
+
data: {
|
|
48
|
+
installExtension: {
|
|
49
|
+
success: boolean;
|
|
50
|
+
message: string;
|
|
51
|
+
extension: {
|
|
52
|
+
id: string;
|
|
53
|
+
status: string;
|
|
54
|
+
installedVersion: string;
|
|
55
|
+
extension: {
|
|
56
|
+
extensionSlug: string;
|
|
57
|
+
name: string;
|
|
58
|
+
version: string;
|
|
59
|
+
};
|
|
60
|
+
settings: {
|
|
61
|
+
effectiveEnabled: boolean;
|
|
62
|
+
userConfig: {
|
|
63
|
+
theme: string;
|
|
64
|
+
notifications: boolean;
|
|
65
|
+
autoSave: boolean;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export { basicInstallExample, orgInstallExample, queryExtensionsExample, expectedResponse };
|
|
73
|
+
//# sourceMappingURL=test-graphql-examples.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-graphql-examples.d.ts","sourceRoot":"","sources":["../../src/demo/test-graphql-examples.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,QAAA,MAAM,mBAAmB;;;;;;;;;;;CAgCxB,CAAC;AAQF,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;CA0CtB,CAAC;AAOF,QAAA,MAAM,sBAAsB;;;;;;CAuB3B,CAAC;AAOF,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;CAyBrB,CAAC;AAqBF,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
type Context = {
|
|
2
|
+
user?: {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
type FormTemplatesInput = {
|
|
9
|
+
search?: string;
|
|
10
|
+
category?: string;
|
|
11
|
+
publisherId?: string;
|
|
12
|
+
installed?: boolean;
|
|
13
|
+
limit?: number;
|
|
14
|
+
offset?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const formTemplateResolvers: () => {
|
|
17
|
+
Query: {
|
|
18
|
+
marketplaceFormTemplates: (_: unknown, { input }: {
|
|
19
|
+
input?: FormTemplatesInput;
|
|
20
|
+
}, context: Context) => Promise<{
|
|
21
|
+
nodes: {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
category: string;
|
|
26
|
+
version: string;
|
|
27
|
+
author: string;
|
|
28
|
+
publisherId: string;
|
|
29
|
+
tags: string[];
|
|
30
|
+
schema: string;
|
|
31
|
+
uiSchema: string;
|
|
32
|
+
downloads: number;
|
|
33
|
+
rating: number;
|
|
34
|
+
reviewCount: number;
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
updatedAt: Date;
|
|
37
|
+
installed: boolean;
|
|
38
|
+
preview: any;
|
|
39
|
+
publisher: {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
email: string;
|
|
43
|
+
};
|
|
44
|
+
}[];
|
|
45
|
+
totalCount: number;
|
|
46
|
+
pageInfo: {
|
|
47
|
+
hasNextPage: boolean;
|
|
48
|
+
hasPreviousPage: boolean;
|
|
49
|
+
startCursor: string;
|
|
50
|
+
endCursor: string;
|
|
51
|
+
};
|
|
52
|
+
}>;
|
|
53
|
+
marketplaceFormTemplate: (_: unknown, { id }: {
|
|
54
|
+
id: string;
|
|
55
|
+
}, context: Context) => Promise<{
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
description: string;
|
|
59
|
+
category: string;
|
|
60
|
+
version: string;
|
|
61
|
+
author: string;
|
|
62
|
+
publisherId: string;
|
|
63
|
+
tags: string[];
|
|
64
|
+
schema: string;
|
|
65
|
+
uiSchema: string;
|
|
66
|
+
downloads: number;
|
|
67
|
+
rating: number;
|
|
68
|
+
reviewCount: number;
|
|
69
|
+
createdAt: Date;
|
|
70
|
+
updatedAt: Date;
|
|
71
|
+
installed: boolean;
|
|
72
|
+
preview: any;
|
|
73
|
+
publisher: {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
email: string;
|
|
77
|
+
};
|
|
78
|
+
}>;
|
|
79
|
+
marketplaceInstalledFormTemplates: () => Promise<{
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
description: string;
|
|
83
|
+
category: string;
|
|
84
|
+
version: string;
|
|
85
|
+
author: string;
|
|
86
|
+
publisherId: string;
|
|
87
|
+
tags: string[];
|
|
88
|
+
schema: string;
|
|
89
|
+
uiSchema: string;
|
|
90
|
+
downloads: number;
|
|
91
|
+
rating: number;
|
|
92
|
+
reviewCount: number;
|
|
93
|
+
createdAt: Date;
|
|
94
|
+
updatedAt: Date;
|
|
95
|
+
installed: boolean;
|
|
96
|
+
preview: any;
|
|
97
|
+
publisher: {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
email: string;
|
|
101
|
+
};
|
|
102
|
+
}[]>;
|
|
103
|
+
marketplaceMyFormTemplates: () => Promise<any[]>;
|
|
104
|
+
marketplacePopularFormTemplates: () => Promise<any[]>;
|
|
105
|
+
marketplaceRecentFormTemplates: () => Promise<any[]>;
|
|
106
|
+
};
|
|
107
|
+
Mutation: {
|
|
108
|
+
marketplacePublishFormTemplate: (_: unknown, { input }: {
|
|
109
|
+
input: unknown;
|
|
110
|
+
}, context: Context) => Promise<{
|
|
111
|
+
success: boolean;
|
|
112
|
+
message: string;
|
|
113
|
+
formTemplate: {
|
|
114
|
+
id: string;
|
|
115
|
+
name: string;
|
|
116
|
+
description: string;
|
|
117
|
+
category: string;
|
|
118
|
+
version: string;
|
|
119
|
+
author: string;
|
|
120
|
+
publisherId: string;
|
|
121
|
+
tags: string[];
|
|
122
|
+
schema: string;
|
|
123
|
+
uiSchema: string;
|
|
124
|
+
downloads: number;
|
|
125
|
+
rating: number;
|
|
126
|
+
reviewCount: number;
|
|
127
|
+
createdAt: Date;
|
|
128
|
+
updatedAt: Date;
|
|
129
|
+
installed: boolean;
|
|
130
|
+
preview: any;
|
|
131
|
+
publisher: {
|
|
132
|
+
id: string;
|
|
133
|
+
name: string;
|
|
134
|
+
email: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}>;
|
|
138
|
+
marketplaceInstallFormTemplate: (_: unknown, { input }: {
|
|
139
|
+
input: {
|
|
140
|
+
id: string;
|
|
141
|
+
};
|
|
142
|
+
}, context: Context) => Promise<{
|
|
143
|
+
success: boolean;
|
|
144
|
+
message: string;
|
|
145
|
+
formTemplate: {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
description: string;
|
|
149
|
+
category: string;
|
|
150
|
+
version: string;
|
|
151
|
+
author: string;
|
|
152
|
+
publisherId: string;
|
|
153
|
+
tags: string[];
|
|
154
|
+
schema: string;
|
|
155
|
+
uiSchema: string;
|
|
156
|
+
downloads: number;
|
|
157
|
+
rating: number;
|
|
158
|
+
reviewCount: number;
|
|
159
|
+
createdAt: Date;
|
|
160
|
+
updatedAt: Date;
|
|
161
|
+
installed: boolean;
|
|
162
|
+
preview: any;
|
|
163
|
+
publisher: {
|
|
164
|
+
id: string;
|
|
165
|
+
name: string;
|
|
166
|
+
email: string;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}>;
|
|
170
|
+
marketplaceUninstallFormTemplate: () => Promise<{
|
|
171
|
+
success: boolean;
|
|
172
|
+
message: string;
|
|
173
|
+
}>;
|
|
174
|
+
marketplaceUpdateFormTemplate: () => Promise<{
|
|
175
|
+
success: boolean;
|
|
176
|
+
message: string;
|
|
177
|
+
formTemplate: {
|
|
178
|
+
id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
description: string;
|
|
181
|
+
category: string;
|
|
182
|
+
version: string;
|
|
183
|
+
author: string;
|
|
184
|
+
publisherId: string;
|
|
185
|
+
tags: string[];
|
|
186
|
+
schema: string;
|
|
187
|
+
uiSchema: string;
|
|
188
|
+
downloads: number;
|
|
189
|
+
rating: number;
|
|
190
|
+
reviewCount: number;
|
|
191
|
+
createdAt: Date;
|
|
192
|
+
updatedAt: Date;
|
|
193
|
+
installed: boolean;
|
|
194
|
+
preview: any;
|
|
195
|
+
publisher: {
|
|
196
|
+
id: string;
|
|
197
|
+
name: string;
|
|
198
|
+
email: string;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
}>;
|
|
202
|
+
marketplaceDeleteFormTemplate: () => Promise<{
|
|
203
|
+
success: boolean;
|
|
204
|
+
message: string;
|
|
205
|
+
}>;
|
|
206
|
+
marketplaceRateFormTemplate: () => Promise<{
|
|
207
|
+
success: boolean;
|
|
208
|
+
message: string;
|
|
209
|
+
}>;
|
|
210
|
+
};
|
|
211
|
+
Subscription: {
|
|
212
|
+
marketplaceFormTemplateEvents: {
|
|
213
|
+
subscribe: (_: unknown, { publisherId }: {
|
|
214
|
+
publisherId?: string;
|
|
215
|
+
}) => AsyncIterator<unknown, any, any>;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
export {};
|
|
220
|
+
//# sourceMappingURL=form-templates-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-templates-resolver.d.ts","sourceRoot":"","sources":["../../../src/graphql/resolvers/form-templates-resolver.ts"],"names":[],"mappings":"AAIA,KAAK,OAAO,GAAG;IACX,IAAI,CAAC,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;CACL,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAwCF,eAAO,MAAM,qBAAqB;;sCAInB,OAAO,aACC;YAAE,KAAK,CAAC,EAAE,kBAAkB,CAAA;SAAE,WAEhC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCAsCb,OAAO,UACF;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,WAEb,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4CAeb,OAAO,aAEC;YAAE,KAAK,EAAE,OAAO,CAAA;SAAE,WAEpB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;4CA4Bb,OAAO,aACC;YAAE,KAAK,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,WAE3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAiCT,OAAO,mBACO;gBAAE,WAAW,CAAC,EAAE,MAAM,CAAA;aAAE;;;CASvD,CAAC"}
|