@webiny/mcp 0.0.0-unstable.f6dc066313
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/Extension.d.ts +2 -0
- package/Extension.js +11 -0
- package/Extension.js.map +1 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/agents/claude.d.ts +15 -0
- package/agents/claude.js +33 -0
- package/agents/claude.js.map +1 -0
- package/agents/cline.d.ts +17 -0
- package/agents/cline.js +29 -0
- package/agents/cline.js.map +1 -0
- package/agents/copilot.d.ts +17 -0
- package/agents/copilot.js +64 -0
- package/agents/copilot.js.map +1 -0
- package/agents/cursor.d.ts +15 -0
- package/agents/cursor.js +33 -0
- package/agents/cursor.js.map +1 -0
- package/agents/instructions.d.ts +7 -0
- package/agents/instructions.js +13 -0
- package/agents/instructions.js.map +1 -0
- package/agents/shared.d.ts +41 -0
- package/agents/shared.js +124 -0
- package/agents/shared.js.map +1 -0
- package/agents/windsurf.d.ts +15 -0
- package/agents/windsurf.js +33 -0
- package/agents/windsurf.js.map +1 -0
- package/cli/ConfigureMcp.d.ts +15 -0
- package/cli/ConfigureMcp.js +57 -0
- package/cli/ConfigureMcp.js.map +1 -0
- package/cli/McpServer.d.ts +12 -0
- package/cli/McpServer.js +239 -0
- package/cli/McpServer.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +3 -0
- package/index.js.map +1 -0
- package/package.json +49 -0
- package/skills/admin-ui-extensions/SKILL.md +267 -0
- package/skills/cli-extensions/SKILL.md +133 -0
- package/skills/content-models/SKILL.md +306 -0
- package/skills/custom-graphql-api/SKILL.md +199 -0
- package/skills/dependency-injection/SKILL.md +252 -0
- package/skills/infrastructure-extensions/SKILL.md +192 -0
- package/skills/lifecycle-events/SKILL.md +203 -0
- package/skills/local-development/SKILL.md +245 -0
- package/skills/project-structure/SKILL.md +156 -0
- package/skills/webiny-sdk/SKILL.md +271 -0
- package/skills/website-builder/SKILL.md +384 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webiny-dependency-injection
|
|
3
|
+
context: webiny-extensions
|
|
4
|
+
description: >
|
|
5
|
+
The universal createImplementation DI pattern and all injectable services.
|
|
6
|
+
Use this skill when the developer is writing any Webiny extension and needs to understand
|
|
7
|
+
dependency injection, constructor injection, how to access Logger/BuildParams/IdentityContext,
|
|
8
|
+
how to inject CMS use-cases (list/get/create/update/delete entries), or how the dependencies
|
|
9
|
+
array works. This is the connective tissue across all extension types -- API, Admin, CLI,
|
|
10
|
+
and Infrastructure.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Dependency Injection Patterns
|
|
14
|
+
|
|
15
|
+
## TL;DR
|
|
16
|
+
|
|
17
|
+
Every Webiny extension type uses the same DI pattern: define a class implementing `*.Interface`, declare dependencies in the constructor, and export via `*.createImplementation({ implementation, dependencies })`. The DI container automatically provides the required services, ensures type safety, and validates at compile time. This pattern is the connective tissue across all extension types -- API, Admin, CLI, and Infrastructure.
|
|
18
|
+
|
|
19
|
+
## The Universal Pattern
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { SomeFactory } from "webiny/some/path";
|
|
23
|
+
import { Logger } from "webiny/api/logger";
|
|
24
|
+
import { BuildParams } from "webiny/api/build-params";
|
|
25
|
+
|
|
26
|
+
class MyImplementation implements SomeFactory.Interface {
|
|
27
|
+
constructor(
|
|
28
|
+
private logger: Logger.Interface,
|
|
29
|
+
private buildParams: BuildParams.Interface
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
execute(/* factory-specific params */) {
|
|
33
|
+
this.logger.info("Doing something...");
|
|
34
|
+
const value = this.buildParams.get<string>("MY_PARAM");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default SomeFactory.createImplementation({
|
|
39
|
+
implementation: MyImplementation,
|
|
40
|
+
dependencies: [Logger, BuildParams]
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Key rules:
|
|
45
|
+
1. **One class per file** -- each extension file exports a single implementation.
|
|
46
|
+
2. **Constructor injection** -- dependencies are received as constructor parameters, in the same order as the `dependencies` array.
|
|
47
|
+
3. **Dependencies array** -- must exactly match the constructor parameter order and types.
|
|
48
|
+
4. **Interface types** -- always type constructor params as `Feature.Interface`.
|
|
49
|
+
|
|
50
|
+
## Where This Pattern Appears
|
|
51
|
+
|
|
52
|
+
| Extension Type | Factory | Import Path |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Content Models | `ModelFactory` | `"webiny/api/cms/model"` |
|
|
55
|
+
| GraphQL Schemas | `GraphQLSchemaFactory` | `"webiny/api/graphql"` |
|
|
56
|
+
| CMS Entry Hooks | `EntryBeforeCreateEventHandler`, etc. | `"webiny/api/cms/entry"` |
|
|
57
|
+
| API Key Hooks | `ApiKeyAfterUpdateHandler` | `"webiny/api/security/apiKey"` |
|
|
58
|
+
| API Keys | `ApiKeyFactory` | `"webiny/api/security"` |
|
|
59
|
+
| CLI Commands | `CliCommandFactory` | `"webiny/cli/command"` |
|
|
60
|
+
| Pulumi Handlers | `CorePulumi` | `"webiny/infra/core"` |
|
|
61
|
+
|
|
62
|
+
## Injectable Services
|
|
63
|
+
|
|
64
|
+
### Utility Services
|
|
65
|
+
|
|
66
|
+
| Service | Import | Interface | Available In | Purpose |
|
|
67
|
+
|---|-----------------------------|---|---|---|
|
|
68
|
+
| `Logger` | `"webiny/api/logger"` | `Logger.Interface` | API | Logging (persists to CloudWatch) |
|
|
69
|
+
| `BuildParams` | `"webiny/api/build-params"` | `BuildParams.Interface` | API | Access build-time parameters |
|
|
70
|
+
| `Ui` (CLI) | `"webiny/cli"` | `Ui.Interface` | CLI | Terminal output formatting |
|
|
71
|
+
| `Ui` (Infra) | `"webiny/infra"` | `Ui.Interface` | Infra | Terminal output during deploy |
|
|
72
|
+
|
|
73
|
+
### Logger Methods
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
this.logger.info("Informational message");
|
|
77
|
+
this.logger.warn("Warning message");
|
|
78
|
+
this.logger.error("Error message");
|
|
79
|
+
this.logger.debug("Debug message");
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### BuildParams Methods
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Get a string parameter
|
|
86
|
+
const value = this.buildParams.get<string>("MY_PARAM");
|
|
87
|
+
|
|
88
|
+
// Get a complex parameter
|
|
89
|
+
const config = this.buildParams.get<{ myKey: number; nested: { foo: string } }>("MY_CONFIG");
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Parameters are set in `webiny.config.tsx`:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Api.BuildParam paramName="MY_PARAM" value="customValue" />
|
|
96
|
+
<Api.BuildParam paramName="MY_CONFIG" value={{ myKey: 2, nested: { foo: "bar" } }} />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Core Context Features
|
|
100
|
+
|
|
101
|
+
| Feature | Import Path | Purpose |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `IdentityContext` | `"webiny/api/security"` or `"@webiny/api-core/features/IdentityContext"` | Current user identity and permissions |
|
|
104
|
+
| `TenantContext` | `"@webiny/api-core/features/TenantContext"` | Current tenant information |
|
|
105
|
+
| `EventPublisher` | `"@webiny/api-core/features/EventPublisher"` | Publish domain events |
|
|
106
|
+
| `WcpContext` | `"@webiny/api-core/features/WcpContext"` | Webiny Control Panel integration |
|
|
107
|
+
| `GetSettings` | `"@webiny/api-core/features/settings/GetSettings"` | Retrieve settings records |
|
|
108
|
+
| `UpdateSettings` | `"@webiny/api-core/features/settings/UpdateSettings"` | Create/update settings records |
|
|
109
|
+
|
|
110
|
+
### Headless CMS Use-Cases
|
|
111
|
+
|
|
112
|
+
| Feature | Import Path | Purpose |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `GetEntryByIdUseCase` | `"@webiny/api-headless-cms/features/contentEntry/GetEntryById"` | Fetch entry by revision ID |
|
|
115
|
+
| `GetEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/GetEntry"` | Get entry by query |
|
|
116
|
+
| `ListLatestEntriesUseCase` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | List latest entries |
|
|
117
|
+
| `ListPublishedEntriesUseCase` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | List published entries |
|
|
118
|
+
| `ListDeletedEntriesUseCase` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | List deleted entries |
|
|
119
|
+
| `CreateEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/CreateEntry"` | Create entry |
|
|
120
|
+
| `UpdateEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/UpdateEntry"` | Update entry |
|
|
121
|
+
| `DeleteEntryUseCase` | `"@webiny/api-headless-cms/features/contentEntry/DeleteEntry"` | Delete entry |
|
|
122
|
+
| `GetModelUseCase` | `"@webiny/api-headless-cms/features/contentModel/GetModel"` | Get model by ID |
|
|
123
|
+
| `ListModelsUseCase` | `"@webiny/api-headless-cms/features/contentModel/ListModels"` | List all models |
|
|
124
|
+
| `GetModelRepository` | `"@webiny/api-headless-cms/features/contentModel/GetModel"` | Fetch model from cache |
|
|
125
|
+
| `ListModelsRepository` | `"@webiny/api-headless-cms/features/contentModel/ListModels"` | Fetch all models from cache |
|
|
126
|
+
| `ModelsFetcher` | `"@webiny/api-headless-cms/features/contentModel/shared"` | Centralized model fetching |
|
|
127
|
+
| `ListEntriesRepository` | `"@webiny/api-headless-cms/features/contentEntry/ListEntries"` | Storage-level entry fetching |
|
|
128
|
+
|
|
129
|
+
### Tenancy Use-Cases
|
|
130
|
+
|
|
131
|
+
| Feature | Import Path | Purpose |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `GetTenantByIdUseCase` | `"@webiny/api-core/features/tenancy/GetTenantById"` | Fetch tenant by ID |
|
|
134
|
+
| `CreateTenantUseCase` | `"@webiny/api-core/features/tenancy/CreateTenant"` | Create a tenant |
|
|
135
|
+
| `UpdateTenantUseCase` | `"@webiny/api-core/features/tenancy/UpdateTenant"` | Update a tenant |
|
|
136
|
+
| `DeleteTenantUseCase` | `"@webiny/api-core/features/tenancy/DeleteTenant"` | Delete a tenant |
|
|
137
|
+
| `InstallTenantUseCase` | `"@webiny/api-core/features/tenancy/InstallTenant"` | Install a tenant |
|
|
138
|
+
|
|
139
|
+
## Examples Across Extension Types
|
|
140
|
+
|
|
141
|
+
### API Extension (GraphQL Schema with DI)
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { GraphQLSchemaFactory } from "webiny/api/graphql";
|
|
145
|
+
import { IdentityContext } from "webiny/api/security";
|
|
146
|
+
|
|
147
|
+
class SchemaImpl implements GraphQLSchemaFactory.Interface {
|
|
148
|
+
constructor(private identityContext: IdentityContext.Interface) {}
|
|
149
|
+
|
|
150
|
+
execute(): GraphQLSchemaFactory.Return {
|
|
151
|
+
return [{
|
|
152
|
+
typeDefs: /* GraphQL */ `
|
|
153
|
+
type Query { whoAmI: String }
|
|
154
|
+
`,
|
|
155
|
+
resolvers: {
|
|
156
|
+
Query: {
|
|
157
|
+
whoAmI: () => {
|
|
158
|
+
const identity = this.identityContext.getIdentity();
|
|
159
|
+
return `Hello, ${identity.displayName}!`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default GraphQLSchemaFactory.createImplementation({
|
|
168
|
+
implementation: SchemaImpl,
|
|
169
|
+
dependencies: [IdentityContext]
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### CMS Lifecycle Hook with DI
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { EntryBeforeCreateEventHandler as Handler } from "webiny/api/cms/entry";
|
|
177
|
+
import { Logger } from "webiny/api/logger";
|
|
178
|
+
|
|
179
|
+
class MyHookImpl implements Handler.Interface {
|
|
180
|
+
constructor(private logger: Logger.Interface) {}
|
|
181
|
+
|
|
182
|
+
async handle(event: Handler.Event): Promise<void> {
|
|
183
|
+
this.logger.info(`Entry created for model: ${event.modelId}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default Handler.createImplementation({
|
|
188
|
+
implementation: MyHookImpl,
|
|
189
|
+
dependencies: [Logger]
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### CLI Command with DI
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { Ui } from "webiny/cli";
|
|
197
|
+
import { CliCommandFactory } from "webiny/cli/command";
|
|
198
|
+
|
|
199
|
+
class MyCommandImpl implements CliCommandFactory.Interface<{ name: string }> {
|
|
200
|
+
constructor(private ui: Ui.Interface) {}
|
|
201
|
+
|
|
202
|
+
execute(): CliCommandFactory.CommandDefinition<{ name: string }> {
|
|
203
|
+
return {
|
|
204
|
+
name: "greet",
|
|
205
|
+
description: "Greet someone",
|
|
206
|
+
params: [{ name: "name", description: "Name", type: "string" }],
|
|
207
|
+
handler: async params => {
|
|
208
|
+
this.ui.success(`Hello, ${params.name}!`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default CliCommandFactory.createImplementation({
|
|
215
|
+
implementation: MyCommandImpl,
|
|
216
|
+
dependencies: [Ui]
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Pulumi Handler with DI
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { Ui } from "webiny/infra";
|
|
224
|
+
import { CorePulumi } from "webiny/infra/core";
|
|
225
|
+
|
|
226
|
+
class MyPulumiImpl implements CorePulumi.Interface {
|
|
227
|
+
constructor(private ui: Ui.Interface) {}
|
|
228
|
+
|
|
229
|
+
execute(app: any) {
|
|
230
|
+
this.ui.info("Deploying with environment:", app.env);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default CorePulumi.createImplementation({
|
|
235
|
+
implementation: MyPulumiImpl,
|
|
236
|
+
dependencies: [Ui]
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Key Rules
|
|
241
|
+
|
|
242
|
+
1. Always import from the **feature path**, not the package root.
|
|
243
|
+
2. Use `Feature.Interface` for constructor parameter types.
|
|
244
|
+
3. The `dependencies` array order must match the constructor parameter order.
|
|
245
|
+
4. Read the `abstractions.ts` file in the feature folder to see available methods.
|
|
246
|
+
5. Extensions with no dependencies use `dependencies: []`.
|
|
247
|
+
|
|
248
|
+
## Related Skills
|
|
249
|
+
|
|
250
|
+
- `custom-graphql-api` -- DI in GraphQL schema extensions
|
|
251
|
+
- `lifecycle-events` -- DI in lifecycle event handlers
|
|
252
|
+
- `cli-extensions` -- DI in CLI command extensions
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webiny-infrastructure-extensions
|
|
3
|
+
context: webiny-extensions
|
|
4
|
+
description: >
|
|
5
|
+
Modifying AWS infrastructure using Pulumi handlers and declarative Infra components.
|
|
6
|
+
Use this skill when the developer wants to customize AWS infrastructure, add Pulumi handlers,
|
|
7
|
+
configure OpenSearch, VPC, resource tags, regions, custom domains, blue-green deployments,
|
|
8
|
+
environment-conditional config, or manage production vs development infrastructure modes.
|
|
9
|
+
Covers CorePulumi.Interface, all <Infra.*> declarative components, and <Infra.Env.Is>.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Infrastructure Extensions
|
|
13
|
+
|
|
14
|
+
## TL;DR
|
|
15
|
+
|
|
16
|
+
Infrastructure extensions modify your AWS infrastructure using Pulumi handlers and declarative `<Infra.*>` components in `webiny.config.tsx`. Pulumi handlers implement `CorePulumi.Interface` and are registered via `<Infra.Core.Pulumi>`. Declarative components configure OpenSearch, VPC, tags, regions, custom domains, blue-green deployments, and environment-specific settings.
|
|
17
|
+
|
|
18
|
+
## Pulumi Handler Pattern
|
|
19
|
+
|
|
20
|
+
Write custom Pulumi code that runs during infrastructure deployment:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// extensions/MyCorePulumiHandler.ts
|
|
24
|
+
import { Ui } from "webiny/infra";
|
|
25
|
+
import { CorePulumi } from "webiny/infra/core";
|
|
26
|
+
|
|
27
|
+
class MyCorePulumiHandlerImpl implements CorePulumi.Interface {
|
|
28
|
+
constructor(private ui: Ui.Interface) {}
|
|
29
|
+
|
|
30
|
+
execute(app: any) {
|
|
31
|
+
this.ui.info("Executing MyCorePulumiHandler with environment:", app.env);
|
|
32
|
+
|
|
33
|
+
// Access and modify Pulumi resources here
|
|
34
|
+
// app.resources gives you access to all provisioned resources
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default CorePulumi.createImplementation({
|
|
39
|
+
implementation: MyCorePulumiHandlerImpl,
|
|
40
|
+
dependencies: [Ui]
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Register:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Infra.Core.Pulumi src={"/extensions/MyCorePulumiHandler.ts"} />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Use Cases for Pulumi Handlers
|
|
51
|
+
|
|
52
|
+
- Add custom AWS resources (CloudWatch alarms, extra S3 buckets, Lambda functions)
|
|
53
|
+
- Modify existing resource properties (Lambda memory, timeouts, environment variables)
|
|
54
|
+
- Add conditional infrastructure based on environment
|
|
55
|
+
- Integrate with third-party infrastructure providers
|
|
56
|
+
|
|
57
|
+
## Declarative Infrastructure Components
|
|
58
|
+
|
|
59
|
+
These components go directly in `webiny.config.tsx` -- no separate extension file needed:
|
|
60
|
+
|
|
61
|
+
### AWS Configuration
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
{/* Set the AWS region */}
|
|
65
|
+
<Infra.Aws.DefaultRegion name={"us-east-1"} />
|
|
66
|
+
|
|
67
|
+
{/* Apply tags to all AWS resources -- multiple calls are merged */}
|
|
68
|
+
<Infra.Aws.Tags tags={{ OWNER: "me", PROJECT: "my-project" }} />
|
|
69
|
+
<Infra.Aws.Tags tags={{ COST_CENTER: "engineering" }} />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Search & Networking
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
{/* Enable/disable OpenSearch */}
|
|
76
|
+
<Infra.OpenSearch enabled={true} />
|
|
77
|
+
|
|
78
|
+
{/* Enable/disable VPC deployment */}
|
|
79
|
+
<Infra.Vpc enabled={false} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Resource Naming
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
{/* Prefix all Pulumi resource names */}
|
|
86
|
+
<Infra.PulumiResourceNamePrefix prefix={"myproj-"} />
|
|
87
|
+
|
|
88
|
+
{/* Define which environments use production-grade infrastructure */}
|
|
89
|
+
<Infra.ProductionEnvironments environments={["prod", "staging"]} />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Custom Domains
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Infra.Admin.CustomDomains
|
|
96
|
+
domains={["admin.example.com"]}
|
|
97
|
+
sslMethod="sni-only"
|
|
98
|
+
certificateArn="arn:aws:acm:us-east-1:123456789:certificate/abc-123"
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Blue-Green Deployments
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<Infra.BlueGreenDeployments
|
|
106
|
+
enabled={true}
|
|
107
|
+
domains={{
|
|
108
|
+
acmCertificateArn: "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
|
|
109
|
+
sslSupportMethod: "sni-only",
|
|
110
|
+
domains: {
|
|
111
|
+
api: ["api.example.com"],
|
|
112
|
+
admin: ["admin.example.com"],
|
|
113
|
+
website: ["website.example.com"],
|
|
114
|
+
preview: ["preview.example.com"]
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
deployments={[
|
|
118
|
+
{ name: "green", env: "dev", variant: "green" },
|
|
119
|
+
{ name: "blue", env: "dev", variant: "blue" }
|
|
120
|
+
]}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Environment-Conditional Configuration
|
|
125
|
+
|
|
126
|
+
Use `<Infra.Env.Is>` to apply settings only in specific environments:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
{/* Production only */}
|
|
130
|
+
<Infra.Env.Is env="prod">
|
|
131
|
+
<Infra.Aws.Tags tags={{ ENV: "production" }} />
|
|
132
|
+
<Infra.OpenSearch enabled={true} />
|
|
133
|
+
</Infra.Env.Is>
|
|
134
|
+
|
|
135
|
+
{/* Non-production (accepts array) */}
|
|
136
|
+
<Infra.Env.Is env={["dev", "staging"]}>
|
|
137
|
+
<Infra.Aws.Tags tags={{ ENV: "non-production" }} />
|
|
138
|
+
<Infra.OpenSearch enabled={false} />
|
|
139
|
+
</Infra.Env.Is>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Project-Level Settings
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
{/* Disable telemetry */}
|
|
146
|
+
<Project.Telemetry enabled={false} />
|
|
147
|
+
|
|
148
|
+
{/* Auto-install for CI/CD (skip the installation wizard) */}
|
|
149
|
+
{process.env.WEBINY_CLI_AUTO_INSTALL && (
|
|
150
|
+
<Project.AutoInstall
|
|
151
|
+
adminUser={{
|
|
152
|
+
firstName: "Ad",
|
|
153
|
+
lastName: "Min",
|
|
154
|
+
email: "admin@webiny.com",
|
|
155
|
+
password: "12345678"
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## All Infrastructure Components Reference
|
|
162
|
+
|
|
163
|
+
| Component | Purpose |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `<Infra.Aws.DefaultRegion name="..." />` | Set the AWS region |
|
|
166
|
+
| `<Infra.Aws.Tags tags={{ ... }} />` | Tag all AWS resources |
|
|
167
|
+
| `<Infra.OpenSearch enabled={bool} />` | Enable/disable OpenSearch cluster |
|
|
168
|
+
| `<Infra.Vpc enabled={bool} />` | Enable/disable VPC deployment |
|
|
169
|
+
| `<Infra.PulumiResourceNamePrefix prefix="..." />` | Prefix Pulumi resource names |
|
|
170
|
+
| `<Infra.ProductionEnvironments environments={[...]} />` | Define production-grade environments |
|
|
171
|
+
| `<Infra.Admin.CustomDomains ... />` | Custom domains for Admin app |
|
|
172
|
+
| `<Infra.BlueGreenDeployments ... />` | Blue-green deployment configuration |
|
|
173
|
+
| `<Infra.Env.Is env="..." />` | Conditional config per environment |
|
|
174
|
+
| `<Infra.Core.Pulumi src="..." />` | Register a custom Pulumi handler |
|
|
175
|
+
| `<Project.Telemetry enabled={bool} />` | Enable/disable telemetry |
|
|
176
|
+
| `<Project.AutoInstall adminUser={{ ... }} />` | Auto-install for CI/CD |
|
|
177
|
+
|
|
178
|
+
## Quick Reference
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Pulumi import: import { CorePulumi } from "webiny/infra/core";
|
|
182
|
+
Ui import: import { Ui } from "webiny/infra";
|
|
183
|
+
Interface: CorePulumi.Interface
|
|
184
|
+
Export: CorePulumi.createImplementation({ implementation, dependencies })
|
|
185
|
+
Register: <Infra.Core.Pulumi src={"/extensions/MyHandler.ts"} />
|
|
186
|
+
Deploy: yarn webiny deploy core (infrastructure changes)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Related Skills
|
|
190
|
+
|
|
191
|
+
- `project-structure` -- Full `webiny.config.tsx` anatomy
|
|
192
|
+
- `local-development` -- Deployment commands and environment management
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webiny-lifecycle-events
|
|
3
|
+
context: webiny-extensions
|
|
4
|
+
description: >
|
|
5
|
+
CMS entry lifecycle hooks and security event handlers.
|
|
6
|
+
Use this skill when the developer wants to intercept content entry operations
|
|
7
|
+
(create, update, delete, publish, unpublish), validate data before save, auto-calculate
|
|
8
|
+
fields, send notifications after operations, enforce security policies, sync with external
|
|
9
|
+
systems, or hook into API key updates. Covers before/after hooks, event filtering by modelId,
|
|
10
|
+
payload mutation, and the Logger/BuildParams DI services.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Lifecycle Events
|
|
14
|
+
|
|
15
|
+
## TL;DR
|
|
16
|
+
|
|
17
|
+
Webiny fires lifecycle events before and after CMS entry operations (create, update, delete, publish, unpublish) and security operations (API key updates). You hook into these by implementing the corresponding `EventHandler.Interface`, filtering by `modelId`, and optionally mutating `payload.values` before data is saved. Register as `<Api.Extension>`.
|
|
18
|
+
|
|
19
|
+
## CMS Entry Lifecycle Events
|
|
20
|
+
|
|
21
|
+
### Available Hooks
|
|
22
|
+
|
|
23
|
+
| Hook | Import From | Fires When |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `EntryBeforeCreateEventHandler` | `"webiny/api/cms/entry"` | Before a new entry is saved |
|
|
26
|
+
| `EntryAfterCreateEventHandler` | `"webiny/api/cms/entry"` | After a new entry is saved |
|
|
27
|
+
| `EntryBeforeUpdateEventHandler` | `"webiny/api/cms/entry"` | Before an existing entry is updated |
|
|
28
|
+
| `EntryAfterUpdateEventHandler` | `"webiny/api/cms/entry"` | After an existing entry is updated |
|
|
29
|
+
| `EntryBeforeDeleteEventHandler` | `"webiny/api/cms/entry"` | Before an entry is deleted |
|
|
30
|
+
| `EntryAfterDeleteEventHandler` | `"webiny/api/cms/entry"` | After an entry is deleted |
|
|
31
|
+
| `EntryBeforePublishEventHandler` | `"webiny/api/cms/entry"` | Before an entry is published |
|
|
32
|
+
| `EntryAfterPublishEventHandler` | `"webiny/api/cms/entry"` | After an entry is published |
|
|
33
|
+
|
|
34
|
+
### The Event Object
|
|
35
|
+
|
|
36
|
+
Every handler receives an `event` with:
|
|
37
|
+
|
|
38
|
+
- `event.modelId` -- The model ID string (e.g., `"contactSubmission"`)
|
|
39
|
+
- `event.payload` -- The entry data object
|
|
40
|
+
- `event.payload.values` -- The field values (can be mutated in `before` hooks)
|
|
41
|
+
|
|
42
|
+
### Pattern
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { EntryBeforeCreateEventHandler as Handler } from "webiny/api/cms/entry";
|
|
46
|
+
|
|
47
|
+
class MyHookImpl implements Handler.Interface {
|
|
48
|
+
async handle(event: Handler.Event): Promise<void> {
|
|
49
|
+
const { payload, modelId } = event;
|
|
50
|
+
|
|
51
|
+
// 1. Filter by model -- handlers fire for ALL models
|
|
52
|
+
if (modelId !== "myTargetModel") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Read values
|
|
57
|
+
const someField = payload.values?.someField as string;
|
|
58
|
+
|
|
59
|
+
// 3. Mutate values (before hooks only)
|
|
60
|
+
if (!payload.values) {
|
|
61
|
+
payload.values = {};
|
|
62
|
+
}
|
|
63
|
+
payload.values.computedField = "computed value";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default Handler.createImplementation({
|
|
68
|
+
implementation: MyHookImpl,
|
|
69
|
+
dependencies: []
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Register in `webiny.config.tsx`:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<Api.Extension src={"/extensions/MyHook.ts"} />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### When to Use Before vs After
|
|
80
|
+
|
|
81
|
+
| Use Case | Hook Type | Why |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Validate data | `Before` | Reject or modify before persistence |
|
|
84
|
+
| Auto-calculate fields | `Before` | Set computed values before save |
|
|
85
|
+
| Send email notification | `After` | Ensure data is persisted first |
|
|
86
|
+
| Sync with external CRM | `After` | Side effect after successful save |
|
|
87
|
+
| Enforce security policies | `Before` | Block operations before they happen |
|
|
88
|
+
|
|
89
|
+
## Full Example: Email Classification Hook
|
|
90
|
+
|
|
91
|
+
This hook intercepts contact form submissions, checks the email domain, and automatically classifies it as "work" or "personal":
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// extensions/contactSubmission/ContactSubmissionHook.ts
|
|
95
|
+
import { EntryBeforeCreateEventHandler as Handler } from "webiny/api/cms/entry";
|
|
96
|
+
import { Logger } from "webiny/api/logger";
|
|
97
|
+
|
|
98
|
+
const PERSONAL_EMAIL_DOMAINS = [
|
|
99
|
+
"gmail.com",
|
|
100
|
+
"yahoo.com",
|
|
101
|
+
"hotmail.com",
|
|
102
|
+
"outlook.com",
|
|
103
|
+
"aol.com",
|
|
104
|
+
"icloud.com",
|
|
105
|
+
"protonmail.com"
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
class ContactSubmissionHookImpl implements Handler.Interface {
|
|
109
|
+
public constructor(private logger: Logger.Interface) {}
|
|
110
|
+
|
|
111
|
+
public async handle(event: Handler.Event): Promise<void> {
|
|
112
|
+
const { payload, modelId } = event;
|
|
113
|
+
|
|
114
|
+
// Only run for Contact Submission entries
|
|
115
|
+
if (modelId !== "contactSubmission") {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.logger.info(`Processing contact submission for model: ${modelId}`);
|
|
120
|
+
|
|
121
|
+
const email = payload.values?.email as string;
|
|
122
|
+
if (!email) {
|
|
123
|
+
this.logger.warn("No email found in contact submission");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Classify the email
|
|
128
|
+
const domain = email.split("@")[1]?.toLowerCase();
|
|
129
|
+
let type = "work";
|
|
130
|
+
if (domain && PERSONAL_EMAIL_DOMAINS.includes(domain)) {
|
|
131
|
+
type = "personal";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.logger.info(`Classified email ${email} as ${type}`);
|
|
135
|
+
|
|
136
|
+
// Set the emailType field before the entry is saved
|
|
137
|
+
if (!payload.values) {
|
|
138
|
+
payload.values = {};
|
|
139
|
+
}
|
|
140
|
+
payload.values.emailType = type;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default Handler.createImplementation({
|
|
145
|
+
implementation: ContactSubmissionHookImpl,
|
|
146
|
+
dependencies: [Logger]
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Security Lifecycle Events
|
|
151
|
+
|
|
152
|
+
### API Key After Update
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// extensions/MyApiKeyAfterUpdate.ts
|
|
156
|
+
import { ApiKeyAfterUpdateHandler } from "webiny/api/security/api-key";
|
|
157
|
+
import { Logger } from "webiny/api/logger";
|
|
158
|
+
import { BuildParams } from "webiny/api/build-params";
|
|
159
|
+
|
|
160
|
+
class MyApiKeyAfterUpdateImpl implements ApiKeyAfterUpdateHandler.Interface {
|
|
161
|
+
constructor(
|
|
162
|
+
private logger: Logger.Interface,
|
|
163
|
+
private buildParams: BuildParams.Interface
|
|
164
|
+
) {}
|
|
165
|
+
|
|
166
|
+
async handle() {
|
|
167
|
+
this.logger.warn("An API key was updated!");
|
|
168
|
+
|
|
169
|
+
const param1 = this.buildParams.get<string>("MY_CUSTOM_BUILD_PARAM");
|
|
170
|
+
console.log(`Build param 1: ${param1}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const MyApiKeyAfterUpdate = ApiKeyAfterUpdateHandler.createImplementation({
|
|
175
|
+
implementation: MyApiKeyAfterUpdateImpl,
|
|
176
|
+
dependencies: [Logger, BuildParams]
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
export default MyApiKeyAfterUpdate;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Register with a dedicated JSX element:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
<Security.ApiKey.AfterUpdate src={"/extensions/MyApiKeyAfterUpdate.ts"} />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Quick Reference
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
CMS hooks import: import { EntryBeforeCreateEventHandler } from "webiny/api/cms/entry";
|
|
192
|
+
Security import: import { ApiKeyAfterUpdateHandler } from "webiny/api/security/apiKey";
|
|
193
|
+
Event shape: event.modelId (string), event.payload (object), event.payload.values (object)
|
|
194
|
+
Export: Handler.createImplementation({ implementation, dependencies })
|
|
195
|
+
Register CMS: <Api.Extension src={"/extensions/MyHook.ts"} />
|
|
196
|
+
Register Security: <Security.ApiKey.AfterUpdate src={"/extensions/MyHook.ts"} />
|
|
197
|
+
Deploy: yarn webiny deploy api
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Related Skills
|
|
201
|
+
|
|
202
|
+
- `content-models` -- Define the models your hooks target
|
|
203
|
+
- `dependency-injection` -- Inject Logger, BuildParams, and other services
|