@withmata/blueprints 0.5.0 → 3.0.0
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/.claude/skills/audit/SKILL.md +6 -6
- package/.claude/skills/blueprint-catalog/SKILL.md +4 -0
- package/.claude/skills/design-system/SKILL.md +11 -5
- package/.claude/skills/reflect/SKILL.md +119 -0
- package/.claude/skills/scaffold-env/SKILL.md +10 -13
- package/.claude/skills/scaffold-foundation/SKILL.md +5 -6
- package/.claude/skills/scaffold-infisical/SKILL.md +173 -0
- package/ENGINEERING.md +1 -1
- package/blueprints/features/env-t3/BLUEPRINT.md +26 -33
- package/blueprints/features/secrets-infisical/BLUEPRINT.md +295 -0
- package/blueprints/features/secrets-infisical/files/docs/INFISICAL.md +119 -0
- package/blueprints/features/secrets-infisical/files/scripts/infisical-map.sh +115 -0
- package/blueprints/features/secrets-infisical/files/scripts/infisical-sync.sh +89 -0
- package/blueprints/thinking/thinking-partner/BLUEPRINT.md +333 -0
- package/blueprints/thinking/thinking-partner/templates/_patterns.md +61 -0
- package/blueprints/thinking/thinking-partner/templates/reflection-template.md +131 -0
- package/dist/index.js +11 -2
- package/package.json +1 -1
- package/blueprints/features/env-t3/files/nextjs/.env.example +0 -17
- package/blueprints/features/env-t3/files/server/.env.example +0 -14
|
@@ -10,7 +10,9 @@ Complete.
|
|
|
10
10
|
|
|
11
11
|
## Problem
|
|
12
12
|
|
|
13
|
-
Environment variable management is tedious and error-prone across projects. Developers
|
|
13
|
+
Environment variable management is tedious and error-prone across projects. Developers miss the split between client and server vars in Next.js, deploy with missing variables that only crash at runtime, and have no type safety when accessing `process.env`. This blueprint creates a validated, type-safe environment variable system using `@t3-oss/env` + Zod that catches missing or malformed variables at build time.
|
|
14
|
+
|
|
15
|
+
The Zod schema files are the **registry** of what environment variables an app needs — there is no separate `.env.example`. Values are delivered by Infisical (see the `secrets-infisical` blueprint) or the deployment platform; for local work, a developer uses `.env.local` or `infisical run`.
|
|
14
16
|
|
|
15
17
|
This blueprint covers both Next.js apps (client/server split with `@t3-oss/env-nextjs`) and backend apps (server-only with `@t3-oss/env-core`).
|
|
16
18
|
|
|
@@ -56,9 +58,9 @@ Both files are imported in `next.config.ts` to trigger build-time validation.
|
|
|
56
58
|
|
|
57
59
|
- **`z.preprocess` for PORT** (recommended) — Environment variables are always strings. PORT needs to be a number. `z.preprocess` handles the string-to-number conversion cleanly.
|
|
58
60
|
|
|
59
|
-
-
|
|
61
|
+
- **Schema files are the registry** (required) — The Zod schema files (`client.ts`, `server.ts`, `env.ts`) are the single source of truth for what environment variables an app needs. Keep them well-commented (purpose, local/production hints) — they replace the old `.env.example`. Values come from Infisical or the platform.
|
|
60
62
|
|
|
61
|
-
- **Empty `.env.local` created** (required) — An empty `.env.local` file is created during scaffolding so developers have the file ready to fill in. This file is gitignored.
|
|
63
|
+
- **Empty `.env.local` created** (required) — An empty `.env.local` file is created during scaffolding so developers have the file ready to fill in for local work. This file is gitignored.
|
|
62
64
|
|
|
63
65
|
---
|
|
64
66
|
|
|
@@ -70,7 +72,7 @@ Both files are imported in `next.config.ts` to trigger build-time validation.
|
|
|
70
72
|
- Zod for schema validation
|
|
71
73
|
- Client/server split in Next.js (two files)
|
|
72
74
|
- Build-time validation via `next.config.ts` import
|
|
73
|
-
- `.env.example`
|
|
75
|
+
- Well-commented schema files as the variable registry (no `.env.example`)
|
|
74
76
|
|
|
75
77
|
**Recommended defaults — ask during scaffolding:**
|
|
76
78
|
|
|
@@ -123,7 +125,6 @@ apps/web/
|
|
|
123
125
|
│ ├── client.ts # Client env schema + validation
|
|
124
126
|
│ └── server.ts # Server env schema + validation
|
|
125
127
|
├── next.config.ts # Imports both env files for build-time validation
|
|
126
|
-
├── .env.example # Documented env template
|
|
127
128
|
└── .env.local # Local values (gitignored)
|
|
128
129
|
```
|
|
129
130
|
|
|
@@ -152,7 +153,6 @@ apis/server/
|
|
|
152
153
|
├── src/
|
|
153
154
|
│ ├── env.ts # Server env schema + validation
|
|
154
155
|
│ └── index.ts # Imports env.ts at top
|
|
155
|
-
├── .env.example # Documented env template
|
|
156
156
|
└── .env.local # Local values (gitignored)
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -175,7 +175,6 @@ For standalone applications (Next.js or backend) without a monorepo, the pattern
|
|
|
175
175
|
|--------|----------|-------------|
|
|
176
176
|
| Next.js env location | `apps/web/src/env/` | `src/env/` |
|
|
177
177
|
| Backend env location | `apis/server/src/env.ts` | `src/env.ts` |
|
|
178
|
-
| `.env.example` location | Per-app (e.g., `apps/web/.env.example`) | Project root `.env.example` |
|
|
179
178
|
| `.env.local` location | Per-app (e.g., `apps/web/.env.local`) | Project root `.env.local` |
|
|
180
179
|
| Import path | `~/env/server` or `~/env/client` | `~/env/server` or `@/env/server` |
|
|
181
180
|
|
|
@@ -186,7 +185,7 @@ All core patterns are project-type-agnostic:
|
|
|
186
185
|
- Zod schemas for validation
|
|
187
186
|
- Client/server split (two files) for Next.js
|
|
188
187
|
- Build-time validation via `next.config.ts` import
|
|
189
|
-
-
|
|
188
|
+
- Well-commented schema files as the variable registry
|
|
190
189
|
- `emptyStringAsUndefined: true`
|
|
191
190
|
|
|
192
191
|
---
|
|
@@ -197,10 +196,8 @@ All core patterns are project-type-agnostic:
|
|
|
197
196
|
|---|---|---|---|
|
|
198
197
|
| `files/nextjs/env/client.ts` | Next.js client env schema | `apps/web/src/env/client.ts` | `src/env/client.ts` |
|
|
199
198
|
| `files/nextjs/env/server.ts` | Next.js server env schema | `apps/web/src/env/server.ts` | `src/env/server.ts` |
|
|
200
|
-
| `files/nextjs/.env.example` | Documented env template (Next.js) | `apps/web/.env.example` | `.env.example` |
|
|
201
199
|
| `files/nextjs/.env.local` | Empty local env file (Next.js) | `apps/web/.env.local` | `.env.local` |
|
|
202
200
|
| `files/server/env.ts` | Backend env schema (`@t3-oss/env-core`) | `apis/server/src/env.ts` | `src/env.ts` |
|
|
203
|
-
| `files/server/.env.example` | Documented env template (backend) | `apis/server/.env.example` | `.env.example` |
|
|
204
201
|
| `files/server/.env.local` | Empty local env file (backend) | `apis/server/.env.local` | `.env.local` |
|
|
205
202
|
|
|
206
203
|
---
|
|
@@ -222,29 +219,27 @@ All core patterns are project-type-agnostic:
|
|
|
222
219
|
import "./src/env/client";
|
|
223
220
|
```
|
|
224
221
|
If existing `import "./env"` is present, replace it with the two new imports.
|
|
225
|
-
6.
|
|
226
|
-
7.
|
|
227
|
-
8. If other blueprints are already installed, pre-populate their env vars into the appropriate schema files
|
|
222
|
+
6. Create `.env.local` (empty)
|
|
223
|
+
7. If other blueprints are already installed, pre-populate their env vars into the appropriate schema files
|
|
228
224
|
|
|
229
225
|
### Phase 2: Backend App Setup (if present)
|
|
230
226
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
12. Create `.env.local` (empty)
|
|
227
|
+
8. Copy `env.ts` to `src/env.ts` in the backend app — replace `{{APP_NAME}}`
|
|
228
|
+
9. Update the app's entry point (e.g., `src/index.ts`) to import `env.ts`:
|
|
229
|
+
```typescript
|
|
230
|
+
import { env } from "./env.js";
|
|
231
|
+
```
|
|
232
|
+
10. Create `.env.local` (empty)
|
|
238
233
|
|
|
239
234
|
### Phase 3: Dependency Installation
|
|
240
235
|
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
11. For each Next.js app: ensure `@t3-oss/env-nextjs` and `zod` are in `dependencies`
|
|
237
|
+
12. For each backend app: ensure `@t3-oss/env-core`, `zod`, and `dotenv` are in `dependencies`
|
|
243
238
|
|
|
244
239
|
### Phase 4: Verification
|
|
245
240
|
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
13. Run `bun dev` (or equivalent) — env validation should run without errors
|
|
242
|
+
14. Try removing a required variable from `.env.local` — build should fail with a clear error message
|
|
248
243
|
|
|
249
244
|
---
|
|
250
245
|
|
|
@@ -254,22 +249,22 @@ All core patterns are project-type-agnostic:
|
|
|
254
249
|
|
|
255
250
|
| When you... | Then do... | Why |
|
|
256
251
|
|---|---|---|
|
|
257
|
-
| Add a new env var in code | Add to `env/client.ts` or `env/server.ts` schema
|
|
252
|
+
| Add a new env var in code | Add to the `env/client.ts` or `env/server.ts` schema (the registry) and, if used locally, `.env.local` | Missing from schema = build-time crash; the schema documents what's needed |
|
|
258
253
|
| Add a `NEXT_PUBLIC_*` var | Put it in `env/client.ts` with both `client` schema and `runtimeEnv` entries | Client vars must be explicitly mapped in Next.js |
|
|
259
254
|
| Add a server-only var to Next.js | Put it in `env/server.ts` (uses `experimental__runtimeEnv: process.env`) | Avoids listing every server var twice |
|
|
260
|
-
| Remove an env var | Remove from schema file
|
|
255
|
+
| Remove an env var | Remove from the schema file and `.env.local` | Stale vars cause confusion |
|
|
261
256
|
| Add a new app to the monorepo | Create its own `env/` files — don't share env schemas between apps | Each app has different variable requirements |
|
|
262
|
-
| Change a var from optional to required | Update the Zod schema (remove `.optional()`) and ensure
|
|
257
|
+
| Change a var from optional to required | Update the Zod schema (remove `.optional()`) and ensure the value is provided (Infisical/platform/`.env.local`) | Will break builds where the value is empty |
|
|
263
258
|
|
|
264
259
|
### Condensed Rules for project rules file
|
|
265
260
|
|
|
266
261
|
```markdown
|
|
267
262
|
### env-t3 maintenance
|
|
268
|
-
-
|
|
263
|
+
- The Zod schema files (client.ts/server.ts/env.ts) are the registry of env vars — keep them well-commented. There is no .env.example; values come from Infisical or the platform.
|
|
264
|
+
- After adding a new env var: add it to the env schema file (client.ts or server.ts) and, if used locally, .env.local
|
|
269
265
|
- NEXT_PUBLIC_* vars go in env/client.ts with explicit runtimeEnv mapping; server vars go in env/server.ts
|
|
270
|
-
- After removing an env var: remove from schema
|
|
266
|
+
- After removing an env var: remove from the schema and .env.local
|
|
271
267
|
- Never access process.env directly — always use the typed env objects (clientEnv, serverEnv, or env)
|
|
272
|
-
- Keep .env.example documented with section headers and local/production hints
|
|
273
268
|
```
|
|
274
269
|
|
|
275
270
|
---
|
|
@@ -288,7 +283,7 @@ All core patterns are project-type-agnostic:
|
|
|
288
283
|
- **Client/server split** — Next.js apps always get two files, not one combined file
|
|
289
284
|
- **Build-time validation** — env files are always imported in `next.config.ts`
|
|
290
285
|
- **`emptyStringAsUndefined`** — always enabled (prevents empty string foot-guns)
|
|
291
|
-
-
|
|
286
|
+
- **Schema as registry** — the Zod schema files are the documented source of truth for env vars; no `.env.example` (values come from Infisical or the platform)
|
|
292
287
|
|
|
293
288
|
## Project Context Output
|
|
294
289
|
|
|
@@ -307,13 +302,11 @@ apps_configured:
|
|
|
307
302
|
files:
|
|
308
303
|
- src/env/client.ts
|
|
309
304
|
- src/env/server.ts
|
|
310
|
-
- .env.example
|
|
311
305
|
- .env.local
|
|
312
306
|
- name: server
|
|
313
307
|
type: backend
|
|
314
308
|
files:
|
|
315
309
|
- src/env.ts
|
|
316
|
-
- .env.example
|
|
317
310
|
- .env.local
|
|
318
311
|
packages_added:
|
|
319
312
|
- "@t3-oss/env-nextjs"
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Secrets Management Blueprint — Infisical /shared + Env-Aware References
|
|
2
|
+
|
|
3
|
+
## Tier
|
|
4
|
+
|
|
5
|
+
Feature
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
Complete.
|
|
10
|
+
|
|
11
|
+
## Problem
|
|
12
|
+
|
|
13
|
+
Multi-app monorepos duplicate the same secret (DB URLs, API keys) across every app's env, so a rotation means editing N places and inevitably drifting. Local `.env` files also have to be copied into every fresh clone / workspace, and there's no single place that says "this is the canonical value." This blueprint centralizes every shared secret **once** in Infisical under a domain-grouped `/shared` folder, and has each app reference those values, so a single change propagates everywhere and a fresh checkout needs only `infisical login` + the committed `.infisical.json`.
|
|
14
|
+
|
|
15
|
+
The agent does all the plumbing (folders, placeholders, references, `infisical run` wiring); the human's only remaining job is to paste real secret values over the placeholders in the Infisical dashboard. The agent never invents or guesses secret values.
|
|
16
|
+
|
|
17
|
+
## Blueprint Dependencies
|
|
18
|
+
|
|
19
|
+
- **Required:** `env-t3` — the T3 Zod schemas (`env/client.ts`, `env/server.ts`, backend `env.ts`) are the source of truth for which variables exist; this blueprint reads them to build the Infisical structure. If `env-t3` is not installed, run `/scaffold-env` first.
|
|
20
|
+
- **Recommended:** `db-drizzle-postgres`, `auth-better-auth` — they define the natural domains (databases, auth) and their variables.
|
|
21
|
+
|
|
22
|
+
### Interaction with Other Blueprints
|
|
23
|
+
|
|
24
|
+
- `env-t3` defines *what* vars exist + validation. This blueprint defines *where the values live and how they're delivered*. They are complementary layers — `env-t3` stays usable without Infisical (some projects deliver secrets via Vercel/Railway directly), and Infisical cleanly **consumes** the T3 schemas instead of bloating them.
|
|
25
|
+
- When a feature blueprint adds an env var to a T3 schema, the maintenance rules here sync it into Infisical as a placeholder. The T3 schema change and the Infisical change are two halves of the same edit.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
### Core Pattern
|
|
32
|
+
|
|
33
|
+
- Canonical **values** live exactly once in `/shared/<domain>/<KEY>` (placeholders until a human fills them).
|
|
34
|
+
- Apps/packages hold **environment-aware references**: `${<ENV_SLUG>.shared.<domain>.<KEY>}` — never raw values for shared vars.
|
|
35
|
+
- **App-native** vars (`PORT`, `NEXT_PUBLIC_*`, langfuse keys, etc.) stay as real values in the app folder, never referenced.
|
|
36
|
+
- The CLI delivers secrets at launch via `infisical run --path=<app> --env=<env>`.
|
|
37
|
+
|
|
38
|
+
Change a shared value once → every app that references it sees the new value. No copy-paste drift across `apps/server`, `apps/web`, `packages/db`.
|
|
39
|
+
|
|
40
|
+
### Reference Syntax (CRITICAL)
|
|
41
|
+
|
|
42
|
+
References MUST be environment-prefixed, and the slug MUST match the environment the reference lives in:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
${dev.shared.pipeline.PIPELINE_VERSION} # in the dev environment
|
|
46
|
+
${staging.shared.pipeline.PIPELINE_VERSION} # in the staging environment
|
|
47
|
+
${prod.shared.pipeline.PIPELINE_VERSION} # in the prod environment
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The **same key** therefore has a **different literal value in each environment** — each points at its own environment's shared subtree.
|
|
51
|
+
|
|
52
|
+
The bare form `${shared.<domain>.<KEY>}` **DOES NOT RESOLVE** (this is the #1 past failure). Other wrong forms:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
${shared.pipeline.PIPELINE_VERSION} ← MISSING env slug — does not resolve
|
|
56
|
+
${env.shared.pipeline.PIPELINE_VERSION} ← literal "env" — wrong
|
|
57
|
+
${/shared/pipeline/PIPELINE_VERSION} ← slash path form — wrong for this case
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Verify with the §Verification check below — the reference must print its value, not the literal `${...}`.
|
|
61
|
+
|
|
62
|
+
### Domains
|
|
63
|
+
|
|
64
|
+
`/shared` is subdivided by **concern** (not by app), derived from installed blueprints + the T3 schemas: e.g. `core`, `auth`, `pipeline`, `knowledge-base`, `image-storage`, `ai`, `search`, `infra`. Domains are a soft taxonomy; the user confirms the map before anything is applied.
|
|
65
|
+
|
|
66
|
+
| Source signal | Domain(s) created |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `db-drizzle-postgres` installed, schema groups | one domain per DB group (`core`, `auth`, `pipeline`, …), each with a `*_DATABASE_URL` |
|
|
69
|
+
| `auth-better-auth` installed | `auth` (`BETTER_AUTH_*`, `GOOGLE_*`, `RESEND_EMAIL_API_KEY`) |
|
|
70
|
+
| Object storage / S3-style vars present | `image-storage`, `knowledge-base` (bucket keys) |
|
|
71
|
+
| LLM / model API keys present | `ai` |
|
|
72
|
+
| Web search / scraping / media API keys present | `search` |
|
|
73
|
+
| Redis / queue / cache | `infra` |
|
|
74
|
+
| Anything not classifiable | `core` (catch-all) or ask |
|
|
75
|
+
|
|
76
|
+
### Placeholders
|
|
77
|
+
|
|
78
|
+
Every shared secret is created with the `REPLACE_ME` sentinel (caps-with-underscore, trivially greppable in the UI). Sensible **non-secret** defaults are allowed for clearly non-sensitive config (`KB_QUEUE_NAMESPACE=pgboss_<env>`, `KB_ENABLE_RERANK=false`, `*_PUBLIC_BASE_URL=https://replace-me.example.com`). Anything that is an actual secret (keys, passwords, connection strings) → `REPLACE_ME`. The sync script only stubs a value if it is currently empty — it never overwrites a filled value.
|
|
79
|
+
|
|
80
|
+
### Verbose, Meaningful Names
|
|
81
|
+
|
|
82
|
+
The blueprint encourages self-describing variable names with the domain/purpose embedded: `STORAGE_ENDPOINT` → `IMAGE_BUCKET_ENDPOINT`, `RESEND_API_KEY` → `RESEND_EMAIL_API_KEY`, `SYNTHETIC_API_KEY` → `SYNTHETIC_AI_API_KEY`, `PARALLEL_API_KEY` → `PARALLEL_RESEARCH_API_KEY`. The skill offers an optional rename/cleanup step.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Hard Requirements vs Recommended Defaults
|
|
87
|
+
|
|
88
|
+
**Hard requirements:**
|
|
89
|
+
- Infisical CLI + a linked project (`.infisical.json` committed per app/package — holds no secrets).
|
|
90
|
+
- `/shared/<domain>` canonical values; app folders hold env-aware references.
|
|
91
|
+
- Environment-prefixed reference syntax (the slug matches the environment the reference lives in).
|
|
92
|
+
- An idempotent, dry-run-by-default sync script committed to the repo.
|
|
93
|
+
- `infisical run --path --env` wrappers in dev/run scripts.
|
|
94
|
+
|
|
95
|
+
**Recommended defaults — ask during scaffolding:**
|
|
96
|
+
|
|
97
|
+
| Choice | Recommended | Alternatives |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Environments | `dev`, `staging`, `prod` | project-specific set (use the real Infisical slugs) |
|
|
100
|
+
| Placeholder sentinel | `REPLACE_ME` | per-key sentinel `REPLACE_ME_<KEY>`; key-name-as-value |
|
|
101
|
+
| Sync script language | bash (uses Infisical CLI, zero deps) | TypeScript |
|
|
102
|
+
| Domain map | derived from blueprints/schemas, user confirms | fully manual |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Dependencies
|
|
107
|
+
|
|
108
|
+
### Tooling
|
|
109
|
+
|
|
110
|
+
| Tool | Install | Purpose |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| Infisical CLI | `brew install infisical/get-cli/infisical` | Create folders/secrets, deliver them via `infisical run` |
|
|
113
|
+
|
|
114
|
+
### npm packages
|
|
115
|
+
|
|
116
|
+
None required at runtime — delivery is via the CLI wrapper. `env-t3` already provides the validation layer.
|
|
117
|
+
|
|
118
|
+
### Environment Variables
|
|
119
|
+
|
|
120
|
+
This blueprint creates no new variables of its own. It mirrors the variables already declared in the project's T3 env schemas into Infisical.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Monorepo Wiring
|
|
125
|
+
|
|
126
|
+
- `.infisical.json` committed in each of `apps/*`, `packages/*` that need secrets (workspaceId only — no secrets).
|
|
127
|
+
- `package.json` dev scripts wrapped in `infisical run --path=/apps/<app> --env=dev`:
|
|
128
|
+
```jsonc
|
|
129
|
+
// apps/server
|
|
130
|
+
"dev": "infisical run --path=/apps/server --env=dev -- bun --watch src/index.ts"
|
|
131
|
+
// apps/web
|
|
132
|
+
"dev": "infisical run --path=/apps/web --env=dev -- bun run --bun next dev"
|
|
133
|
+
// packages/db — a :dev variant per script that needs DB envs
|
|
134
|
+
"core:migrate:dev": "infisical run --env=dev --path=/packages/db -- bun run core:migrate"
|
|
135
|
+
```
|
|
136
|
+
- Production scripts stay clean — Railway/the host injects Infisical at the platform level.
|
|
137
|
+
|
|
138
|
+
## Single-Repo Adaptation
|
|
139
|
+
|
|
140
|
+
One app folder (e.g. `/app`) plus `/shared`. Same env-aware references; same placeholder + sync-script approach. `infisical run --path=/app --env=dev`. The `APP_PATHS` and `REF_MAP` in the data map collapse to the single app.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## File Manifest
|
|
145
|
+
|
|
146
|
+
| File | Purpose | Target |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `files/scripts/infisical-sync.sh` | Idempotent, dry-run-by-default structure sync; loops all environments | `scripts/infisical-sync.sh` |
|
|
149
|
+
| `files/scripts/infisical-map.sh` | Data map: `ENVIRONMENTS`, `DOMAINS`, `APP_PATHS`, `SHARED_MAP`, `REF_MAP` (generated per project) | `scripts/infisical-map.sh` |
|
|
150
|
+
| `files/docs/INFISICAL.md` | Runbook: setup, reference syntax, verification, maintenance | `docs/INFISICAL.md` |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Integration Steps
|
|
155
|
+
|
|
156
|
+
### Phase 0: Preconditions
|
|
157
|
+
|
|
158
|
+
1. Confirm Infisical CLI installed (`infisical --version`); else `brew install infisical/get-cli/infisical`.
|
|
159
|
+
2. Confirm `infisical login` done.
|
|
160
|
+
3. Confirm an Infisical project exists and `infisical init` produced a committed `.infisical.json` in each app/package dir.
|
|
161
|
+
4. Confirm `env-t3` is installed (read `.project-context.md`). If not, run `/scaffold-env` first.
|
|
162
|
+
5. Capture the real environment **slugs** (default `dev`, `staging`, `prod`) — these go into the reference prefix and MUST match Infisical exactly.
|
|
163
|
+
|
|
164
|
+
### Phase 1: Read Sources of Truth
|
|
165
|
+
|
|
166
|
+
6. Read the T3 env schemas in every app/package → full variable list per app.
|
|
167
|
+
7. Read `.project-context.md` → installed blueprints, app layout, npm scope.
|
|
168
|
+
|
|
169
|
+
### Phase 2: Derive and Confirm the Plan (do not apply yet)
|
|
170
|
+
|
|
171
|
+
8. Classify each var: shared-reference vs app-native.
|
|
172
|
+
9. Group shared vars into domains (table above).
|
|
173
|
+
10. Produce a dry-run plan: folders, shared placeholders, app references, app-native vars left in place.
|
|
174
|
+
11. **Present the domain map + plan and ask the user to confirm/edit.**
|
|
175
|
+
|
|
176
|
+
### Phase 3: Generate the Sync Script + Data Map
|
|
177
|
+
|
|
178
|
+
12. Write `scripts/infisical-sync.sh` and `scripts/infisical-map.sh` from the templates, filling the data map from the confirmed plan.
|
|
179
|
+
|
|
180
|
+
### Phase 4: Apply
|
|
181
|
+
|
|
182
|
+
13. Run dry-run, show output, then (on user OK) re-run with `APPLY=1`. The script loops every environment.
|
|
183
|
+
|
|
184
|
+
### Phase 5: Wire package.json
|
|
185
|
+
|
|
186
|
+
14. Add/adjust `infisical run --path=... --env=...` wrappers in dev/run scripts; add `:dev` variants for `packages/db` scripts. Leave production scripts clean.
|
|
187
|
+
|
|
188
|
+
### Phase 6: Verify (do not skip)
|
|
189
|
+
|
|
190
|
+
15. Resolve a reference end to end for at least `dev`:
|
|
191
|
+
```bash
|
|
192
|
+
infisical run --path=/apps/server --env=dev -- printenv CORE_DATABASE_URL
|
|
193
|
+
```
|
|
194
|
+
Prints a value → good. Prints the literal `${dev.shared...}` → the reference is malformed or the shared key/folder is missing. Fix before continuing.
|
|
195
|
+
|
|
196
|
+
### Phase 7: Record + Summarize
|
|
197
|
+
|
|
198
|
+
16. Update `.project-context.md` and the project-maintenance skill.
|
|
199
|
+
17. Print files created, the full domain map, and the **`REPLACE_ME` checklist** the user must fill in Infisical.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Maintenance Hooks
|
|
204
|
+
|
|
205
|
+
### Secrets Hooks
|
|
206
|
+
|
|
207
|
+
| When you... | Then do... | Why |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| Add a **shared** env var to a T3 schema | Add the key to `/shared/<domain>` (placeholder) in every env, add an env-aware reference in each consuming app folder, update `infisical-map.sh`, re-run the sync script | Keeps the single source of truth and every consumer in sync |
|
|
210
|
+
| Add an **app-native** var | Set it directly in that app's Infisical folder as a real value — no shared key, no reference | App-native vars don't belong in `/shared` |
|
|
211
|
+
| Add a new environment | Add its slug to `ENVIRONMENTS` and re-run the sync script | Folders + references are per-environment |
|
|
212
|
+
| Rotate a secret | Edit the single `/shared/<domain>/<KEY>` value; all apps inherit it | Never edit a value in an app folder — it should be a reference |
|
|
213
|
+
| Remove a var | Delete the shared key AND every app reference; remove from `infisical-map.sh` | Stale references print the literal `${...}` |
|
|
214
|
+
|
|
215
|
+
### Condensed Rules for project rules file
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
### secrets-infisical maintenance
|
|
219
|
+
- Adding a SHARED env var (used by >1 app, or a domain's canonical value):
|
|
220
|
+
1. Add the key to its `/shared/<domain>` folder in EVERY environment, set to
|
|
221
|
+
`REPLACE_ME` (or a non-secret default).
|
|
222
|
+
2. Add an environment-aware reference `${<env>.shared.<domain>.<KEY>}` in each
|
|
223
|
+
consuming app/package folder, for every environment.
|
|
224
|
+
3. Add the key to scripts/infisical-map.sh (SHARED_MAP + REF_MAP) and re-run
|
|
225
|
+
`APPLY=1 ./scripts/infisical-sync.sh`.
|
|
226
|
+
4. Tell the human which `/shared/<domain>/<KEY>` placeholders to fill.
|
|
227
|
+
- Adding an APP-NATIVE var (PORT, NEXT_PUBLIC_*, single-app config): set it
|
|
228
|
+
directly in that app's Infisical folder as a real value — do NOT create a
|
|
229
|
+
shared key or reference.
|
|
230
|
+
- Reference syntax is ALWAYS `${<env-slug>.shared.<domain>.<KEY>}` where the slug
|
|
231
|
+
matches the environment the reference lives in. The bare `${shared...}` form
|
|
232
|
+
DOES NOT resolve. Verify with:
|
|
233
|
+
`infisical run --path=/apps/<app> --env=dev -- printenv <KEY>`.
|
|
234
|
+
- Rotating a secret: edit the single `/shared/<domain>/<KEY>` value; all
|
|
235
|
+
referencing apps inherit it. Never edit the value in an app folder.
|
|
236
|
+
- Removing a var: delete the shared key AND every app reference to it; remove from
|
|
237
|
+
infisical-map.sh.
|
|
238
|
+
- Never commit real secret values. `.infisical.json` is safe to commit; `.env*`
|
|
239
|
+
stays gitignored.
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## What's Configurable
|
|
245
|
+
|
|
246
|
+
- Environments list (slugs)
|
|
247
|
+
- Domain map (the soft taxonomy)
|
|
248
|
+
- Placeholder sentinel
|
|
249
|
+
- Which apps/packages are wired
|
|
250
|
+
- Sync script language (bash vs TS)
|
|
251
|
+
|
|
252
|
+
## What's Opinionated
|
|
253
|
+
|
|
254
|
+
- **Infisical** as the secret store.
|
|
255
|
+
- **`/shared` + env-aware references** as the single-source-of-truth mechanism.
|
|
256
|
+
- **Environment-prefixed reference syntax** — non-negotiable; it's the only form that resolves.
|
|
257
|
+
- **Placeholder-first, human-fills-values** workflow — the agent never writes real secret values.
|
|
258
|
+
|
|
259
|
+
## Project Context Output
|
|
260
|
+
|
|
261
|
+
Appends to `.project-context.md` under `## Installed Blueprints`:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
### secrets-infisical
|
|
265
|
+
blueprint: secrets-infisical
|
|
266
|
+
installed_at: <date>
|
|
267
|
+
choices:
|
|
268
|
+
placeholder_sentinel: REPLACE_ME
|
|
269
|
+
script_language: bash
|
|
270
|
+
environments:
|
|
271
|
+
- dev
|
|
272
|
+
- staging
|
|
273
|
+
- prod
|
|
274
|
+
domain_map:
|
|
275
|
+
core: [CORE_DATABASE_URL]
|
|
276
|
+
auth: [AUTH_DATABASE_URL, BETTER_AUTH_URL, BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, RESEND_EMAIL_API_KEY]
|
|
277
|
+
# ... one entry per domain
|
|
278
|
+
apps_wired:
|
|
279
|
+
- path: /apps/server
|
|
280
|
+
references: [core, auth, ...]
|
|
281
|
+
- path: /apps/web
|
|
282
|
+
references: [ai]
|
|
283
|
+
- path: /packages/db
|
|
284
|
+
references: [core, auth, pipeline, knowledge-base]
|
|
285
|
+
script_path: scripts/infisical-sync.sh
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## References
|
|
291
|
+
|
|
292
|
+
- **Infisical secret references:** https://infisical.com/docs/documentation/platform/secret-reference
|
|
293
|
+
- **Infisical CLI:** https://infisical.com/docs/cli/overview
|
|
294
|
+
- **`infisical run`:** https://infisical.com/docs/cli/commands/run
|
|
295
|
+
- **`infisical secrets`:** https://infisical.com/docs/cli/commands/secrets
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Infisical Secrets Runbook
|
|
2
|
+
|
|
3
|
+
How secrets work in this project, and how to keep them in sync.
|
|
4
|
+
|
|
5
|
+
## The model
|
|
6
|
+
|
|
7
|
+
- **Canonical values live once** in Infisical under `/shared/<domain>/<KEY>`.
|
|
8
|
+
- **Apps and packages hold references**, never raw values, for anything shared.
|
|
9
|
+
- **App-native values** (`PORT`, `NEXT_PUBLIC_*`, etc.) live directly in the app's
|
|
10
|
+
Infisical folder.
|
|
11
|
+
- Secrets are delivered at launch by the Infisical CLI:
|
|
12
|
+
`infisical run --path=<app> --env=<env> -- <command>`.
|
|
13
|
+
|
|
14
|
+
Change a shared value once and every referencing app inherits it — no drift.
|
|
15
|
+
|
|
16
|
+
## Reference syntax (read this before editing anything)
|
|
17
|
+
|
|
18
|
+
References are **environment-prefixed**. The slug MUST match the environment the
|
|
19
|
+
reference lives in:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
${dev.shared.pipeline.PIPELINE_VERSION} # stored in the dev environment
|
|
23
|
+
${staging.shared.pipeline.PIPELINE_VERSION} # stored in the staging environment
|
|
24
|
+
${prod.shared.pipeline.PIPELINE_VERSION} # stored in the prod environment
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The same key has a different literal value per environment — each points at its
|
|
28
|
+
own environment's `/shared` subtree.
|
|
29
|
+
|
|
30
|
+
**Wrong forms (do not use — they silently fail to resolve):**
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
${shared.pipeline.PIPELINE_VERSION} ← missing env slug (the classic bug)
|
|
34
|
+
${env.shared.pipeline.PIPELINE_VERSION} ← literal "env"
|
|
35
|
+
${/shared/pipeline/PIPELINE_VERSION} ← slash path form
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## First-time setup
|
|
39
|
+
|
|
40
|
+
1. Install the CLI: `brew install infisical/get-cli/infisical`
|
|
41
|
+
2. `infisical login`
|
|
42
|
+
3. Ensure each app/package has a committed `.infisical.json` (from `infisical init`).
|
|
43
|
+
It holds the workspace id only — no secrets — and is safe to commit.
|
|
44
|
+
4. Run the sync script to create the structure (see below).
|
|
45
|
+
5. Open the Infisical dashboard and paste real values over every `REPLACE_ME`
|
|
46
|
+
placeholder in `/shared/<domain>`.
|
|
47
|
+
|
|
48
|
+
## The sync script
|
|
49
|
+
|
|
50
|
+
`scripts/infisical-sync.sh` creates and maintains the `/shared` structure and all
|
|
51
|
+
app references. It is **dry-run by default** and **idempotent** (never overwrites a
|
|
52
|
+
filled value):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# from a dir with .infisical.json (e.g. apps/server)
|
|
56
|
+
APPLY=0 ./scripts/infisical-sync.sh # dry run — prints the plan, changes nothing
|
|
57
|
+
APPLY=1 ./scripts/infisical-sync.sh # apply to every environment in ENVIRONMENTS
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The plan lives in `scripts/infisical-map.sh` (`ENVIRONMENTS`, `DOMAINS`,
|
|
61
|
+
`APP_PATHS`, `SHARED_MAP`, `REF_MAP`). Edit that file to add/remove vars.
|
|
62
|
+
|
|
63
|
+
## Verification
|
|
64
|
+
|
|
65
|
+
After applying, resolve a reference end to end:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
infisical run --path=/apps/server --env=dev -- printenv CORE_DATABASE_URL
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- ✅ prints the value (or `REPLACE_ME`) → the reference resolved correctly.
|
|
72
|
+
- ❌ prints the literal `${dev.shared.core.CORE_DATABASE_URL}` → the reference is
|
|
73
|
+
malformed or the shared key/folder is missing. Fix before proceeding (almost
|
|
74
|
+
always a missing shared key or a bare `${shared...}` reference).
|
|
75
|
+
|
|
76
|
+
## Running apps with secrets
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
// apps/server
|
|
80
|
+
"dev": "infisical run --path=/apps/server --env=dev -- bun --watch src/index.ts"
|
|
81
|
+
// apps/web
|
|
82
|
+
"dev": "infisical run --path=/apps/web --env=dev -- bun run --bun next dev"
|
|
83
|
+
// packages/db — a :dev variant per script that needs DB envs
|
|
84
|
+
"core:migrate:dev": "infisical run --env=dev --path=/packages/db -- bun run core:migrate"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Production scripts stay clean — the host (e.g. Railway) injects Infisical at the
|
|
88
|
+
platform level.
|
|
89
|
+
|
|
90
|
+
## Maintenance
|
|
91
|
+
|
|
92
|
+
**Add a shared var** (used by >1 app, or a domain's canonical value):
|
|
93
|
+
1. Add `"domain KEY"` to `SHARED_MAP` and `"appPath domain KEY"` to `REF_MAP` in
|
|
94
|
+
`scripts/infisical-map.sh`.
|
|
95
|
+
2. Run `APPLY=1 ./scripts/infisical-sync.sh`.
|
|
96
|
+
3. Paste the real value over the new `/shared/<domain>/<KEY>` placeholder.
|
|
97
|
+
|
|
98
|
+
**Add an app-native var:** set it directly in that app's Infisical folder as a
|
|
99
|
+
real value. Do not add it to the map.
|
|
100
|
+
|
|
101
|
+
**Add an environment:** add its slug to `ENVIRONMENTS` and re-run the sync.
|
|
102
|
+
|
|
103
|
+
**Rotate a secret:** edit the single `/shared/<domain>/<KEY>` value. Every
|
|
104
|
+
referencing app inherits it. Never edit the value in an app folder.
|
|
105
|
+
|
|
106
|
+
**Remove a var:** delete the shared key and every app reference, and remove it
|
|
107
|
+
from `infisical-map.sh`.
|
|
108
|
+
|
|
109
|
+
## Gotchas
|
|
110
|
+
|
|
111
|
+
- **Env slugs must match Infisical exactly.** If your envs are
|
|
112
|
+
Development/Staging/Production with slugs `dev`/`staging`/`prod`, the references
|
|
113
|
+
use `dev`/`staging`/`prod`.
|
|
114
|
+
- **Folders are per-environment.** The tree exists in every environment; the sync
|
|
115
|
+
loops them all.
|
|
116
|
+
- **Local override foot-gun:** a local `.env.local` applied with `override: true`
|
|
117
|
+
beats Infisical. Watch for stale local values masking the real ones.
|
|
118
|
+
- **Never commit real secret values.** `.infisical.json` is safe to commit;
|
|
119
|
+
`.env*` stays gitignored.
|