@jaypie/mcp 0.7.15 → 0.7.17
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/dist/suites/docs/index.js +1 -1
- package/package.json +1 -1
- package/release-notes/constructs/1.2.30.md +15 -0
- package/release-notes/mcp/0.7.16.md +9 -0
- package/skills/cdk.md +36 -2
- package/skills/cicd-actions.md +4 -4
- package/skills/cicd-deploy.md +1 -1
- package/skills/fabricator.md +200 -0
- package/skills/jaypie.md +1 -1
- package/skills/monorepo.md +1 -1
- package/skills/secrets.md +88 -1
|
@@ -9,7 +9,7 @@ import { gt } from 'semver';
|
|
|
9
9
|
/**
|
|
10
10
|
* Docs Suite - Documentation services (skill, version, release_notes)
|
|
11
11
|
*/
|
|
12
|
-
const BUILD_VERSION_STRING = "@jaypie/mcp@0.7.
|
|
12
|
+
const BUILD_VERSION_STRING = "@jaypie/mcp@0.7.17#0cfeac13"
|
|
13
13
|
;
|
|
14
14
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname$1 = path.dirname(__filename$1);
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: 1.2.30
|
|
3
|
+
date: 2026-02-13
|
|
4
|
+
summary: Default security headers for JaypieDistribution
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# @jaypie/constructs 1.2.30
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **JaypieDistribution**: Ships with default security response headers via a `ResponseHeadersPolicy`, analogous to `helmet` for Express. Headers include HSTS, X-Content-Type-Options, X-Frame-Options (DENY), Referrer-Policy, Content-Security-Policy, Permissions-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, and Server removal.
|
|
12
|
+
- **JaypieDistribution**: New `securityHeaders` prop — `true` (default) applies sensible defaults, `false` disables, or pass a `SecurityHeadersOverrides` object to selectively override individual headers.
|
|
13
|
+
- **JaypieDistribution**: New `responseHeadersPolicy` prop for full override with a custom `IResponseHeadersPolicy`.
|
|
14
|
+
- **SecurityHeadersOverrides**: New exported interface for selective security header configuration.
|
|
15
|
+
- **CDK.SECURITY_HEADERS**: New constants for default CSP, HSTS max-age, and Permissions-Policy values.
|
package/skills/cdk.md
CHANGED
|
@@ -80,10 +80,10 @@ const bucket = new JaypieBucket(this, "AssetsBucket", {
|
|
|
80
80
|
|
|
81
81
|
## Stack Structure
|
|
82
82
|
|
|
83
|
-
Organize stacks in the `
|
|
83
|
+
Organize stacks in the `workspaces/` directory:
|
|
84
84
|
|
|
85
85
|
```
|
|
86
|
-
|
|
86
|
+
workspaces/
|
|
87
87
|
├── cdk/
|
|
88
88
|
│ ├── src/
|
|
89
89
|
│ │ ├── app.ts # CDK app entry
|
|
@@ -169,6 +169,40 @@ new JaypieNextJs(this, "App", {
|
|
|
169
169
|
|
|
170
170
|
**Streaming Note:** When `streaming: true`, also create `open-next.config.ts` in your Next.js app with `wrapper: "aws-lambda-streaming"`. See `skill("streaming")` for details.
|
|
171
171
|
|
|
172
|
+
## Security Headers
|
|
173
|
+
|
|
174
|
+
`JaypieDistribution` ships with default security response headers via a `ResponseHeadersPolicy` (analogous to `helmet` for Express):
|
|
175
|
+
|
|
176
|
+
- HSTS (2-year max-age, includeSubDomains, preload)
|
|
177
|
+
- X-Content-Type-Options (nosniff)
|
|
178
|
+
- X-Frame-Options (DENY)
|
|
179
|
+
- Referrer-Policy (strict-origin-when-cross-origin)
|
|
180
|
+
- Content-Security-Policy (conservative defaults)
|
|
181
|
+
- Permissions-Policy (camera, microphone, geolocation, payment disabled)
|
|
182
|
+
- Cross-Origin-Opener-Policy (same-origin)
|
|
183
|
+
- Cross-Origin-Resource-Policy (same-origin)
|
|
184
|
+
- Server header removed
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Disable security headers
|
|
188
|
+
new JaypieDistribution(this, "Dist", { handler, securityHeaders: false });
|
|
189
|
+
|
|
190
|
+
// Override specific headers
|
|
191
|
+
new JaypieDistribution(this, "Dist", {
|
|
192
|
+
handler,
|
|
193
|
+
securityHeaders: {
|
|
194
|
+
contentSecurityPolicy: "default-src 'self';",
|
|
195
|
+
frameOption: HeadersFrameOption.SAMEORIGIN,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Full custom policy override
|
|
200
|
+
new JaypieDistribution(this, "Dist", {
|
|
201
|
+
handler,
|
|
202
|
+
responseHeadersPolicy: myCustomPolicy,
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
172
206
|
## See Also
|
|
173
207
|
|
|
174
208
|
- **`skill("streaming")`** - JaypieDistribution and JaypieNextJs streaming configuration
|
package/skills/cicd-actions.md
CHANGED
|
@@ -187,7 +187,7 @@ runs:
|
|
|
187
187
|
path: |
|
|
188
188
|
node_modules
|
|
189
189
|
packages/*/node_modules
|
|
190
|
-
|
|
190
|
+
workspaces/*/node_modules
|
|
191
191
|
key: ${{ runner.os }}-node-${{ inputs.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
|
192
192
|
restore-keys: |
|
|
193
193
|
${{ runner.os }}-node-${{ inputs.node-version }}-
|
|
@@ -198,8 +198,8 @@ runs:
|
|
|
198
198
|
with:
|
|
199
199
|
path: |
|
|
200
200
|
packages/*/dist
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
workspaces/*/dist
|
|
202
|
+
workspaces/*/.open-next
|
|
203
203
|
key: ${{ runner.os }}-build-${{ github.sha }}
|
|
204
204
|
restore-keys: |
|
|
205
205
|
${{ runner.os }}-build-
|
|
@@ -255,7 +255,7 @@ inputs:
|
|
|
255
255
|
working-directory:
|
|
256
256
|
description: 'Working directory for CDK commands'
|
|
257
257
|
required: false
|
|
258
|
-
default: '
|
|
258
|
+
default: 'workspaces/cdk'
|
|
259
259
|
require-approval:
|
|
260
260
|
description: 'CDK approval mode (never, any-change, broadening)'
|
|
261
261
|
required: false
|
package/skills/cicd-deploy.md
CHANGED
|
@@ -248,7 +248,7 @@ jobs:
|
|
|
248
248
|
Use consistent stack naming with environment and nonce:
|
|
249
249
|
|
|
250
250
|
```typescript
|
|
251
|
-
//
|
|
251
|
+
// workspaces/cdk/src/app.ts
|
|
252
252
|
const env = process.env.PROJECT_ENV || "sandbox";
|
|
253
253
|
const nonce = process.env.PROJECT_NONCE || "dev";
|
|
254
254
|
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Seeded deterministic test data generation with @jaypie/fabricator
|
|
3
|
+
related: tests, mocks, models
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fabricator
|
|
7
|
+
|
|
8
|
+
`@jaypie/fabricator` provides seeded, deterministic data generation built on `@faker-js/faker`.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @jaypie/fabricator
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Core: Fabricator Class
|
|
17
|
+
|
|
18
|
+
Wraps faker.js with deterministic seeding. Same seed = same data.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Fabricator } from "@jaypie/fabricator";
|
|
22
|
+
|
|
23
|
+
const fab = new Fabricator("my-seed");
|
|
24
|
+
fab.internet.email(); // Always the same for "my-seed"
|
|
25
|
+
fab.person.firstName(); // Proxies to faker.js
|
|
26
|
+
fab.id; // UUID derived from seed
|
|
27
|
+
fab.name; // Auto-generated word pair
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Factory Function
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { fabricator } from "@jaypie/fabricator";
|
|
34
|
+
|
|
35
|
+
const fab = fabricator("my-seed");
|
|
36
|
+
const fab = fabricator({ seed: "my-seed", name: "Custom Name" });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Environment Seed
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
process.env.PROJECT_SEED = "test-seed";
|
|
43
|
+
const fab = new Fabricator(); // Falls back to PROJECT_SEED
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Random Number Generation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const fab = new Fabricator("seed");
|
|
50
|
+
|
|
51
|
+
fab.random(); // 0-1 float
|
|
52
|
+
fab.random({ min: 1, max: 10, integer: true }); // 1-10 integer
|
|
53
|
+
fab.random({ mean: 50, stddev: 10 }); // Normal distribution
|
|
54
|
+
fab.random({ min: 10, max: 100, currency: true }); // 2 decimal places
|
|
55
|
+
fab.random({ min: 0, max: 1, precision: 4 }); // 4 decimal places
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Standalone:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { random } from "@jaypie/fabricator";
|
|
62
|
+
|
|
63
|
+
const rng = random("my-seed");
|
|
64
|
+
rng({ min: 1, max: 100, integer: true });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Built-in Generators
|
|
68
|
+
|
|
69
|
+
### Words
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
fab.generate.words(); // "adjective noun", "adjective verb", or "noun verb"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Person
|
|
76
|
+
|
|
77
|
+
Generates realistic person objects with probabilistic variations using Jaypie golden numbers:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
fab.generate.person();
|
|
81
|
+
// { id, firstName, middleName, lastName, fullName }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- **UNCOMMON (14.6%)**: middleName missing, fullName includes middle
|
|
85
|
+
- **RARE (2.1%)**: firstName is a surname, lastName is hyphenated
|
|
86
|
+
- **EPIC (0.307%)**: double middle names
|
|
87
|
+
|
|
88
|
+
## CHANCE Constants
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { CHANCE } from "@jaypie/fabricator/constants";
|
|
92
|
+
|
|
93
|
+
CHANCE.UNCOMMON; // 0.146 (14.6%)
|
|
94
|
+
CHANCE.RARE; // 0.021 (2.1%)
|
|
95
|
+
CHANCE.EPIC; // 0.00307 (0.307%)
|
|
96
|
+
CHANCE.LEGENDARY; // 0.000441 (0.0441%)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Nested Fabricators
|
|
100
|
+
|
|
101
|
+
Create hierarchical fabricators with `Fabricator.new`:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const world = Fabricator.new({
|
|
105
|
+
seed: "earth",
|
|
106
|
+
name: ({ fabricator }) => fabricator.generate.words(),
|
|
107
|
+
fabricators: {
|
|
108
|
+
cities: { name: ({ fabricator }) => fabricator.location.city() },
|
|
109
|
+
exports: { name: ({ fabricator }) => fabricator.commerce.product() },
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
world.cities(5); // Array of 5 city fabricators
|
|
114
|
+
for (const c of world.cities()) break; // Infinite generator
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## EventFabricator
|
|
118
|
+
|
|
119
|
+
Abstract base for generating temporally-distributed events across a year.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { EventFabricator } from "@jaypie/fabricator";
|
|
123
|
+
|
|
124
|
+
class OrderFabricator extends EventFabricator<Order> {
|
|
125
|
+
protected createEvent({ timestamp, seed, index }) {
|
|
126
|
+
return { id: seed, timestamp, amount: 100 };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const fab = new OrderFabricator({
|
|
131
|
+
seed: "orders-2025",
|
|
132
|
+
annualCount: 10000,
|
|
133
|
+
template: ["HOURS_BUSINESS", "DAYS_WEEKDAYS_ONLY", "BOOST_HOLIDAY_SEASON"],
|
|
134
|
+
timezone: "America/New_York",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const orders = fab.events({ year: 2025 });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Temporal Templates
|
|
141
|
+
|
|
142
|
+
Combine templates for realistic distribution patterns:
|
|
143
|
+
|
|
144
|
+
| Category | Templates |
|
|
145
|
+
|----------|-----------|
|
|
146
|
+
| Hours | `HOURS_BUSINESS`, `HOURS_RETAIL`, `HOURS_EVENING`, `HOURS_OVERNIGHT`, `HOURS_24_7`, etc. |
|
|
147
|
+
| Days | `DAYS_WEEKDAYS_ONLY`, `DAYS_NO_SUNDAY`, `DAYS_NO_MONDAY`, `DAYS_NO_SUNDAY_MONDAY` |
|
|
148
|
+
| Dates | `DATES_15_AND_25` (billing cycles) |
|
|
149
|
+
| Seasonal | `SEASON_SUMMER_ONLY`, `SEASON_WINTER_ONLY`, `SEASON_NO_SUMMER`, `SEASON_NO_WINTER` |
|
|
150
|
+
| Curves | `CURVE_EVENING_PEAK`, `CURVE_ECOMMERCE`, `CURVE_MIDDAY_PEAK` |
|
|
151
|
+
| Spikes | `SPIKE_MORNING`, `SPIKE_LUNCH`, `SPIKE_EVENING` |
|
|
152
|
+
| Boosts | `BOOST_SUMMER`, `BOOST_WINTER`, `BOOST_WEEKENDS`, `BOOST_HOLIDAY_SEASON` |
|
|
153
|
+
| Lulls | `LULL_SUMMER`, `LULL_WINTER`, `LULL_WEEKENDS`, `LULL_WEEKDAYS` |
|
|
154
|
+
|
|
155
|
+
### Derived Events
|
|
156
|
+
|
|
157
|
+
Generate cascading follow-up events from parent events:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const fab = new TransactionFabricator({
|
|
161
|
+
seed: "txn",
|
|
162
|
+
annualCount: 500,
|
|
163
|
+
derived: {
|
|
164
|
+
maxDepth: 3,
|
|
165
|
+
rules: [
|
|
166
|
+
{
|
|
167
|
+
name: "refund",
|
|
168
|
+
probability: CHANCE.UNCOMMON,
|
|
169
|
+
timing: { mode: "range", delayMin: 1, delayMax: 30, unit: "days" },
|
|
170
|
+
createDerived: ({ parent, seed, timestamp }) => ({
|
|
171
|
+
id: seed,
|
|
172
|
+
type: "refund",
|
|
173
|
+
amount: -parent.amount,
|
|
174
|
+
timestamp,
|
|
175
|
+
parentId: parent.id,
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Timing Modes
|
|
184
|
+
|
|
185
|
+
| Mode | Description |
|
|
186
|
+
|------|-------------|
|
|
187
|
+
| `fixed` | Exact delay from parent |
|
|
188
|
+
| `range` | Random delay within min/max |
|
|
189
|
+
| `recurring` | Repeated at interval with `maxRecurrences` or `until` |
|
|
190
|
+
| `same-day` | Same day as parent event |
|
|
191
|
+
|
|
192
|
+
## Utilities
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { isUuid, numericSeed, uuidFrom } from "@jaypie/fabricator";
|
|
196
|
+
|
|
197
|
+
isUuid("550e8400-e29b-41d4-a716-446655440000"); // true
|
|
198
|
+
numericSeed("my-string"); // Deterministic number
|
|
199
|
+
uuidFrom("any-value"); // UUID v5 from string
|
|
200
|
+
```
|
package/skills/jaypie.md
CHANGED
package/skills/monorepo.md
CHANGED
|
@@ -138,7 +138,7 @@ npm install --save-dev @jaypie/eslint @jaypie/testkit eslint rimraf sort-package
|
|
|
138
138
|
| Directory | Purpose |
|
|
139
139
|
|-----------|---------|
|
|
140
140
|
| `packages/` | npm packages (default workspace) |
|
|
141
|
-
| `
|
|
141
|
+
| `workspaces/` | CDK-deployed infrastructure and sites |
|
|
142
142
|
|
|
143
143
|
## Scripts Reference
|
|
144
144
|
|
package/skills/secrets.md
CHANGED
|
@@ -92,7 +92,7 @@ The construct auto-detects consumer mode for personal/ephemeral environments (`P
|
|
|
92
92
|
|
|
93
93
|
## Generated Secrets
|
|
94
94
|
|
|
95
|
-
For secrets
|
|
95
|
+
For secrets that don't come from an external source, use `generateSecretString`. CloudFormation generates the value on the **first deploy only** and preserves it across subsequent deploys. The value is never visible in templates, logs, or CI/CD output — it exists only inside AWS Secrets Manager and is retrievable at runtime.
|
|
96
96
|
|
|
97
97
|
```typescript
|
|
98
98
|
new JaypieEnvSecret(this, "DB_PASSWORD", {
|
|
@@ -103,6 +103,93 @@ new JaypieEnvSecret(this, "DB_PASSWORD", {
|
|
|
103
103
|
});
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
This is the preferred pattern for any secret that can be randomly generated. No GitHub secret or workflow variable is needed — the CDK deploy creates and manages the value automatically.
|
|
107
|
+
|
|
108
|
+
### Base62 Generated Secrets
|
|
109
|
+
|
|
110
|
+
For secrets that must be base62-safe (`0-9A-Za-z` only), such as seeds and signing keys:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
new JaypieEnvSecret(this, "AdminSeed", {
|
|
114
|
+
envKey: "PROJECT_ADMIN_SEED",
|
|
115
|
+
generateSecretString: {
|
|
116
|
+
excludePunctuation: true,
|
|
117
|
+
includeSpace: false,
|
|
118
|
+
passwordLength: 64,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
With `excludePunctuation: true` and `includeSpace: false`, the generated value contains only base62 characters.
|
|
124
|
+
|
|
125
|
+
## Seeds and API Keys in Workflows
|
|
126
|
+
|
|
127
|
+
Seeds are secrets used to derive deterministic values (like API keys) at runtime. The generated-secret pattern is ideal for seeds because:
|
|
128
|
+
|
|
129
|
+
1. **Set once**: CloudFormation generates the value on the first deploy. Subsequent deploys do not regenerate it.
|
|
130
|
+
2. **Never known**: No human ever sees the seed value. It exists only in Secrets Manager.
|
|
131
|
+
3. **Retrievable at runtime**: `loadEnvSecrets("PROJECT_ADMIN_SEED")` populates `process.env` for the handler to derive keys from.
|
|
132
|
+
4. **Environment-isolated**: Each environment (sandbox, development, production) generates its own independent seed.
|
|
133
|
+
|
|
134
|
+
### CDK Pattern
|
|
135
|
+
|
|
136
|
+
Create the secret with `generateSecretString` and pass it to the Lambda:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { JaypieEnvSecret, JaypieExpressLambda } from "@jaypie/constructs";
|
|
140
|
+
|
|
141
|
+
const adminSeed = new JaypieEnvSecret(this, "ProjectAdminSeed", {
|
|
142
|
+
envKey: "PROJECT_ADMIN_SEED",
|
|
143
|
+
generateSecretString: {
|
|
144
|
+
excludePunctuation: true,
|
|
145
|
+
includeSpace: false,
|
|
146
|
+
passwordLength: 64,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
new JaypieExpressLambda(this, "ApiLambda", {
|
|
151
|
+
code: "dist",
|
|
152
|
+
handler: "index.handler",
|
|
153
|
+
secrets: [adminSeed],
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Runtime Usage
|
|
158
|
+
|
|
159
|
+
The handler loads the seed at startup and derives keys:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { expressHandler } from "jaypie";
|
|
163
|
+
|
|
164
|
+
export default expressHandler(handler, {
|
|
165
|
+
secrets: ["PROJECT_ADMIN_SEED"],
|
|
166
|
+
setup: () => initClient(),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
At runtime, `process.env.PROJECT_ADMIN_SEED` contains the generated seed value. Application code uses it to derive API keys via HMAC, validate presented keys, and auto-provision on first use.
|
|
171
|
+
|
|
172
|
+
### Workflow Integration
|
|
173
|
+
|
|
174
|
+
No special workflow steps are needed for generated secrets. The CDK deploy step handles everything:
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
- name: Deploy CDK Stacks
|
|
178
|
+
uses: ./.github/actions/cdk-deploy
|
|
179
|
+
with:
|
|
180
|
+
stack-name: JaypieGardenApi
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
On the first deploy, CloudFormation generates the seed. On subsequent deploys, the seed is preserved. The workflow never needs to generate, store, or pass the seed value.
|
|
184
|
+
|
|
185
|
+
### Why Not GitHub Secrets?
|
|
186
|
+
|
|
187
|
+
For externally-provided credentials (API keys from vendors, database URIs), GitHub environment secrets and env vars are the right approach. But for **internally-generated** secrets like seeds:
|
|
188
|
+
|
|
189
|
+
- `generateSecretString` is simpler — no manual setup per environment
|
|
190
|
+
- The value is never exposed to CI/CD logs or GitHub settings
|
|
191
|
+
- CloudFormation guarantees idempotent creation — first deploy sets, updates preserve
|
|
192
|
+
|
|
106
193
|
## Tagging
|
|
107
194
|
|
|
108
195
|
Apply standard tags for organization:
|