@percepta/create 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/README.md +93 -0
- package/dist/chunk-GEVZERMP.js +108 -0
- package/dist/chunk-R4FWPE4A.js +49 -0
- package/dist/chunk-WMJT7CB5.js +57 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +974 -0
- package/dist/init-Z4VGBHAK.js +96 -0
- package/dist/status-MITGDLTT.js +76 -0
- package/dist/sync-J4SFZHDX.js +136 -0
- package/dist/upstream-AQI7P4EU.js +144 -0
- package/package.json +58 -0
- package/template-versions.json +4 -0
- package/templates/library/README.md +30 -0
- package/templates/library/eslint.config.js +10 -0
- package/templates/library/gitignore.template +18 -0
- package/templates/library/package.json.template +29 -0
- package/templates/library/src/index.ts +9 -0
- package/templates/library/tsconfig.json +19 -0
- package/templates/monorepo/README.md +41 -0
- package/templates/monorepo/eslint.config.js +10 -0
- package/templates/monorepo/gitignore.template +31 -0
- package/templates/monorepo/npmrc.template +4 -0
- package/templates/monorepo/package.json.template +25 -0
- package/templates/monorepo/packages/.gitkeep +0 -0
- package/templates/monorepo/pnpm-workspace.yaml +2 -0
- package/templates/monorepo/tsconfig.json +16 -0
- package/templates/webapp/.claude/commands/sync.md +19 -0
- package/templates/webapp/.claude/commands/upstream.md +17 -0
- package/templates/webapp/.dockerignore +59 -0
- package/templates/webapp/.gitattributes +1 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +114 -0
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +28 -0
- package/templates/webapp/.github/workflows/ci.yml +149 -0
- package/templates/webapp/.node-version +2 -0
- package/templates/webapp/.prettierrc.mjs +5 -0
- package/templates/webapp/AGENTS.md +240 -0
- package/templates/webapp/Dockerfile +64 -0
- package/templates/webapp/README.md +200 -0
- package/templates/webapp/agent-skills/database.md +140 -0
- package/templates/webapp/agent-skills/deploy.md +94 -0
- package/templates/webapp/agent-skills/inngest.md +147 -0
- package/templates/webapp/agent-skills/langfuse.md +117 -0
- package/templates/webapp/agent-skills/oneshot.md +216 -0
- package/templates/webapp/agent-skills/ryvn.md +25 -0
- package/templates/webapp/deploy/README.md +39 -0
- package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +11 -0
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +121 -0
- package/templates/webapp/docker-compose.yml +19 -0
- package/templates/webapp/drizzle.config.ts +30 -0
- package/templates/webapp/env.example.template +44 -0
- package/templates/webapp/eslint.config.mjs +52 -0
- package/templates/webapp/gitignore.template +53 -0
- package/templates/webapp/next.config.ts +8 -0
- package/templates/webapp/npmrc.template +4 -0
- package/templates/webapp/package.json.template +122 -0
- package/templates/webapp/postcss.config.mjs +5 -0
- package/templates/webapp/scripts/create-user.ts +47 -0
- package/templates/webapp/scripts/migrate.ts +18 -0
- package/templates/webapp/scripts/seed.ts +62 -0
- package/templates/webapp/scripts/setup-database.ts +57 -0
- package/templates/webapp/scripts/setup-readonly-user.ts +193 -0
- package/templates/webapp/scripts/start.sh +52 -0
- package/templates/webapp/src/app/(app)/layout.tsx +21 -0
- package/templates/webapp/src/app/(app)/page.tsx +30 -0
- package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +103 -0
- package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +30 -0
- package/templates/webapp/src/app/(auth)/layout.tsx +15 -0
- package/templates/webapp/src/app/api/auth/[...all]/route.ts +4 -0
- package/templates/webapp/src/app/api/healthz/route.ts +10 -0
- package/templates/webapp/src/app/api/inngest/route.ts +31 -0
- package/templates/webapp/src/app/api/readyz/route.ts +31 -0
- package/templates/webapp/src/app/api/trpc/[trpc]/route.ts +21 -0
- package/templates/webapp/src/app/favicon.ico +0 -0
- package/templates/webapp/src/app/global-error.tsx +27 -0
- package/templates/webapp/src/app/layout.tsx +18 -0
- package/templates/webapp/src/components/FaroProvider.tsx +37 -0
- package/templates/webapp/src/components/Header.tsx +70 -0
- package/templates/webapp/src/components/Providers.tsx +45 -0
- package/templates/webapp/src/components/form/FormItem.tsx +82 -0
- package/templates/webapp/src/config/clientEnvConfig.ts +11 -0
- package/templates/webapp/src/config/getEnvConfig.ts +62 -0
- package/templates/webapp/src/config/isDev.ts +7 -0
- package/templates/webapp/src/drizzle/db.ts +28 -0
- package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +57 -0
- package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +376 -0
- package/templates/webapp/src/drizzle/migrations/meta/_journal.json +13 -0
- package/templates/webapp/src/drizzle/schema/auth/accounts.ts +33 -0
- package/templates/webapp/src/drizzle/schema/auth/sessions.ts +25 -0
- package/templates/webapp/src/drizzle/schema/auth/users.ts +38 -0
- package/templates/webapp/src/drizzle/schema/auth/verifications.ts +19 -0
- package/templates/webapp/src/drizzle/schema/index.ts +4 -0
- package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +25 -0
- package/templates/webapp/src/instrumentation.ts +35 -0
- package/templates/webapp/src/lib/auth/index.ts +85 -0
- package/templates/webapp/src/lib/auth-client.ts +6 -0
- package/templates/webapp/src/lib/trpc.ts +15 -0
- package/templates/webapp/src/server/api/root.ts +5 -0
- package/templates/webapp/src/server/trpc.ts +61 -0
- package/templates/webapp/src/services/AuthContextService.ts +63 -0
- package/templates/webapp/src/services/DatabaseService.ts +54 -0
- package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +5 -0
- package/templates/webapp/src/services/inngest/InngestService.ts +71 -0
- package/templates/webapp/src/services/inngest/events/AppEvents.ts +34 -0
- package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +14 -0
- package/templates/webapp/src/services/langfuse/LangfuseService.ts +80 -0
- package/templates/webapp/src/services/logger/AppLogger.ts +61 -0
- package/templates/webapp/src/services/logger/withRequestContext.ts +27 -0
- package/templates/webapp/src/services/observability/initFaro.ts +22 -0
- package/templates/webapp/src/startup-checks.ts +32 -0
- package/templates/webapp/src/styles/globals.css +27 -0
- package/templates/webapp/src/utils/__tests__/cn.test.ts +20 -0
- package/templates/webapp/src/utils/cn.ts +6 -0
- package/templates/webapp/src/utils/syncInngestApp.ts +62 -0
- package/templates/webapp/terraform/README.md +147 -0
- package/templates/webapp/terraform/deploy.sh +97 -0
- package/templates/webapp/terraform/main.tf +101 -0
- package/templates/webapp/terraform/modules/cloudtrail/main.tf +27 -0
- package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +10 -0
- package/templates/webapp/terraform/modules/cloudtrail/variables.tf +15 -0
- package/templates/webapp/terraform/modules/networking/main.tf +118 -0
- package/templates/webapp/terraform/modules/networking/outputs.tf +38 -0
- package/templates/webapp/terraform/modules/networking/variables.tf +24 -0
- package/templates/webapp/terraform/modules/rds/main.tf +227 -0
- package/templates/webapp/terraform/modules/rds/outputs.tf +73 -0
- package/templates/webapp/terraform/modules/rds/variables.tf +61 -0
- package/templates/webapp/terraform/modules/s3-logging/main.tf +148 -0
- package/templates/webapp/terraform/modules/s3-logging/outputs.tf +10 -0
- package/templates/webapp/terraform/modules/s3-logging/variables.tf +16 -0
- package/templates/webapp/terraform/modules/secrets/main.tf +39 -0
- package/templates/webapp/terraform/modules/secrets/outputs.tf +9 -0
- package/templates/webapp/terraform/modules/secrets/variables.tf +51 -0
- package/templates/webapp/terraform/outputs.tf +102 -0
- package/templates/webapp/terraform/providers.tf +32 -0
- package/templates/webapp/terraform/terraform.tfvars.example +65 -0
- package/templates/webapp/terraform/variables.tf +129 -0
- package/templates/webapp/tsconfig.json +14 -0
- package/templates/webapp/vitest.config.ts +9 -0
- package/templates/webapp/vitest.setup.ts +5 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Deploying to Percepta Test
|
|
2
|
+
|
|
3
|
+
This guide deploys __APP_TITLE__ to `https://__APP_NAME__.percepta-test.aitco.dev` using Ryvn. Tell Claude "deploy this app to percepta-test" and it will follow the steps below.
|
|
4
|
+
|
|
5
|
+
## What's already scaffolded
|
|
6
|
+
|
|
7
|
+
When this app was created with `@percepta/create`, the IaC files were generated at `deploy/ryvn/` with all values pre-filled:
|
|
8
|
+
|
|
9
|
+
- `deploy/ryvn/__APP_NAME__.service.yaml` — Ryvn Service definition
|
|
10
|
+
- `deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml` — percepta-test installation
|
|
11
|
+
- `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root) — release workflow that builds the Docker image and creates a Ryvn release on push to `main`. Path filter scoped to `packages/__APP_NAME__/` so unrelated changes don't trigger builds.
|
|
12
|
+
|
|
13
|
+
Per-app values (URLs, k8s service names, database name) are substituted at create-time. Shared platform values (Inngest, Langfuse, OTel endpoints) are baked in as literals — they're stable across percepta-test apps. Three secrets stay in the Ryvn UI.
|
|
14
|
+
|
|
15
|
+
See [`deploy/README.md`](../deploy/README.md) for the file-by-file breakdown.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- The app repo is under the `Percepta-Core` GitHub org. If not yet, run `gh repo create Percepta-Core/__APP_NAME__ --private --source=. --push` from the monorepo root.
|
|
20
|
+
- The Percepta-Core org has `RYVN_CLIENT_ID`, `RYVN_CLIENT_SECRET`, and `NPM_TOKEN` as org-level GitHub secrets (already in place).
|
|
21
|
+
- The `percepta-internal-terraform` installation provides the shared PostgreSQL — already deployed on percepta-test.
|
|
22
|
+
- The infra repo (`Percepta-Core/infra`) is checked out locally, typically at `../../infra` relative to this package.
|
|
23
|
+
|
|
24
|
+
## Deploy
|
|
25
|
+
|
|
26
|
+
### Step 1: Open the infra PR
|
|
27
|
+
|
|
28
|
+
Copy the two scaffolded files into the infra repo and open a PR:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
INFRA=../../infra # adjust to your local path
|
|
32
|
+
NAME=__APP_NAME__
|
|
33
|
+
|
|
34
|
+
cp deploy/ryvn/$NAME.service.yaml \
|
|
35
|
+
$INFRA/ryvn/$NAME.service.yaml
|
|
36
|
+
|
|
37
|
+
cp deploy/ryvn/environments/percepta-test/installations/$NAME.env.percepta-test.serviceinstallation.yaml \
|
|
38
|
+
$INFRA/ryvn/environments/percepta-test/installations/$NAME.env.percepta-test.serviceinstallation.yaml
|
|
39
|
+
|
|
40
|
+
cd $INFRA
|
|
41
|
+
git checkout -b deploy/$NAME-percepta-test
|
|
42
|
+
git add ryvn/$NAME.service.yaml ryvn/environments/percepta-test/installations/$NAME.env.percepta-test.serviceinstallation.yaml
|
|
43
|
+
git commit -m "Add $NAME service + percepta-test installation"
|
|
44
|
+
git push -u origin deploy/$NAME-percepta-test
|
|
45
|
+
gh pr create --fill
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Get the PR reviewed and merged.
|
|
49
|
+
|
|
50
|
+
### Step 2: Set the three secrets in the Ryvn UI
|
|
51
|
+
|
|
52
|
+
After the infra PR merges, Ryvn creates the installation. Open the Ryvn UI for the `__APP_NAME__` installation in `percepta-test` and set:
|
|
53
|
+
|
|
54
|
+
| Secret | Value |
|
|
55
|
+
|--------|-------|
|
|
56
|
+
| `BETTER_AUTH_SECRET` | `openssl rand -base64 32` |
|
|
57
|
+
| `ENCRYPTION_SECRET_KEY` | `openssl rand -hex 16` |
|
|
58
|
+
| `LANGFUSE_SECRET_KEY` | from 1Password: "Percepta Test Secrets" |
|
|
59
|
+
|
|
60
|
+
### Step 3: Trigger the first deploy
|
|
61
|
+
|
|
62
|
+
Push to `main` in the app repo (or `gh workflow run "Build & Release __APP_NAME__"`). The release workflow builds the Docker image and creates a Ryvn release; Ryvn deploys it to percepta-test.
|
|
63
|
+
|
|
64
|
+
The workflow lives at `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root, where GitHub Actions picks it up). It only fires on changes under `packages/__APP_NAME__/`, so unrelated edits to other packages in the monorepo won't trigger it.
|
|
65
|
+
|
|
66
|
+
### Step 4: Verify
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
ryvn get installation __APP_NAME__ -e percepta-test
|
|
70
|
+
ryvn logs __APP_NAME__ -e percepta-test
|
|
71
|
+
curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/healthz
|
|
72
|
+
curl -s https://__APP_NAME__.percepta-test.aitco.dev/api/readyz
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The app will be available at **https://__APP_NAME__.percepta-test.aitco.dev**.
|
|
76
|
+
|
|
77
|
+
## Troubleshooting
|
|
78
|
+
|
|
79
|
+
- **Pod crash-looping** → secrets probably aren't set yet (Step 2). Check `ryvn logs`.
|
|
80
|
+
- **Database connection refused** → verify `DATABASE_USE_SSL=true` and that `percepta-internal-terraform` is deployed.
|
|
81
|
+
- **Inngest can't reach the app** → `INNGEST_APP_URL` must use the k8s service name `__APP_NAME__-web-server`. The scaffolded YAML gets this right; if you renamed anything, double-check.
|
|
82
|
+
- **Image build fails** → check `NPM_TOKEN` in the service definition is current. Build context is `packages/__APP_NAME__`.
|
|
83
|
+
|
|
84
|
+
For Ryvn CLI operations, use the `/use-ryvn` skill.
|
|
85
|
+
|
|
86
|
+
## Updating shared platform values
|
|
87
|
+
|
|
88
|
+
The Inngest/Langfuse/OTel URLs are baked as literals in every webapp's installation YAML. If percepta-test infra ever changes those endpoints, update them with a single grep across the infra repo:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
grep -rl "inngest.percepta-test.svc.cluster.local:8288" ryvn/environments/percepta-test/installations/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A future improvement would be a shared ConfigMap or Ryvn output reference; for now the scaffolded literals are the convention.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Inngest — Background Jobs & Agent Workflows
|
|
2
|
+
|
|
3
|
+
Inngest is a durable execution engine for background tasks. Instead of managing queues, workers, and retries yourself, you define functions that respond to events — Inngest handles scheduling, retries, concurrency, and observability. Think of it as "cron jobs + event-driven workflows + built-in retry logic" in one tool.
|
|
4
|
+
|
|
5
|
+
In this template, Inngest powers all async work: background processing, scheduled jobs, multi-step agent pipelines, and anything that shouldn't block the HTTP request/response cycle.
|
|
6
|
+
|
|
7
|
+
## Adding a New Event
|
|
8
|
+
|
|
9
|
+
### 1. Define the payload schema
|
|
10
|
+
|
|
11
|
+
Create a Zod schema for the event payload:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import z from "zod";
|
|
15
|
+
|
|
16
|
+
export type DocumentProcessedPayload = z.infer<typeof DocumentProcessedPayload.SCHEMA>;
|
|
17
|
+
export namespace DocumentProcessedPayload {
|
|
18
|
+
export const SCHEMA = z.object({
|
|
19
|
+
documentId: z.string(),
|
|
20
|
+
userId: z.string(),
|
|
21
|
+
pageCount: z.number(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Register in AppEvents
|
|
27
|
+
|
|
28
|
+
Add the event to the central `AppEvents` registry:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { DocumentProcessedPayload } from "./payloads/DocumentProcessedPayload";
|
|
32
|
+
|
|
33
|
+
export const AppEvents = {
|
|
34
|
+
"app/document.processed": z.object({
|
|
35
|
+
data: DocumentProcessedPayload.SCHEMA,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Event names follow the convention `"app/<entity>.<action>"`.
|
|
41
|
+
|
|
42
|
+
## Adding a New Function
|
|
43
|
+
|
|
44
|
+
### 1. Create a function collection
|
|
45
|
+
|
|
46
|
+
Group related functions into a collection class that implements `InngestFunctionCollection`:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { type InngestFunction } from "inngest";
|
|
50
|
+
import { type InngestFunctionCollection } from "../InngestFunctionCollection";
|
|
51
|
+
import { InngestService } from "../InngestService";
|
|
52
|
+
|
|
53
|
+
export class DocumentFunctions implements InngestFunctionCollection {
|
|
54
|
+
private inngestService = InngestService.create();
|
|
55
|
+
|
|
56
|
+
public get functions(): InngestFunction.Like[] {
|
|
57
|
+
return [this.processDocument()];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private processDocument() {
|
|
61
|
+
return this.inngestService.createFunction(
|
|
62
|
+
{ id: "process-document", retries: 3 },
|
|
63
|
+
{ event: "app/document.processed" },
|
|
64
|
+
async ({ event, step }) => {
|
|
65
|
+
const { documentId, userId } = event.data;
|
|
66
|
+
|
|
67
|
+
// step.run wraps each unit of work for durability — if a step fails,
|
|
68
|
+
// Inngest retries from that step, not from the beginning
|
|
69
|
+
const extracted = await step.run("extract-text", async () => {
|
|
70
|
+
// ... extract text from document
|
|
71
|
+
return { text: "..." };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await step.run("store-results", async () => {
|
|
75
|
+
// ... save to database
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 2. Register with the serve endpoint
|
|
84
|
+
|
|
85
|
+
Add your collection to the `functionCollections` array in the Inngest serve endpoint:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const functionCollections: InngestFunctionCollection[] = compact([
|
|
89
|
+
new DocumentFunctions(),
|
|
90
|
+
]);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Sending Events
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const inngest = InngestService.create();
|
|
97
|
+
await inngest.client.send({
|
|
98
|
+
name: "app/document.processed",
|
|
99
|
+
data: { documentId: "abc", userId: "user-1", pageCount: 5 },
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Running Inngest Locally
|
|
104
|
+
|
|
105
|
+
### 1. Install the Inngest Dev Server
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm dlx inngest-cli@latest dev
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This starts the Inngest Dev Server at `http://localhost:8288` with a dashboard UI.
|
|
112
|
+
|
|
113
|
+
### 2. Set environment variables
|
|
114
|
+
|
|
115
|
+
In `.env.local`:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
INNGEST_BASE_URL=http://localhost:8288
|
|
119
|
+
INNGEST_EVENT_KEY=local # any value works locally
|
|
120
|
+
# INNGEST_SIGNING_KEY is not needed for local dev
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Start the app
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pnpm dev
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The Inngest Dev Server auto-discovers functions by calling the serve endpoint at `/api/inngest`. Open `http://localhost:8288` to see registered functions, trigger events manually, and inspect runs.
|
|
130
|
+
|
|
131
|
+
## Environment Variables
|
|
132
|
+
|
|
133
|
+
| Variable | Required | Description |
|
|
134
|
+
|----------|----------|-------------|
|
|
135
|
+
| `INNGEST_BASE_URL` | Yes | Inngest server URL (`http://localhost:8288` locally) |
|
|
136
|
+
| `INNGEST_EVENT_KEY` | Yes (prod) | Event authentication key |
|
|
137
|
+
| `INNGEST_SIGNING_KEY` | Yes (prod) | Function registration signing key |
|
|
138
|
+
| `INNGEST_APP_URL` | No | Override app URL for Inngest to call back |
|
|
139
|
+
| `SKIP_INNGEST_SYNC` | No | Set `true` to skip Inngest app sync on startup |
|
|
140
|
+
|
|
141
|
+
## Key Concepts
|
|
142
|
+
|
|
143
|
+
- **Events** are the trigger. Define them with Zod schemas for runtime validation.
|
|
144
|
+
- **Functions** respond to events. They contain one or more **steps**.
|
|
145
|
+
- **Steps** (`step.run`, `step.sleep`, `step.waitForEvent`) are durable — if the function crashes mid-execution, Inngest resumes from the last completed step.
|
|
146
|
+
- **Retries** are automatic. Default is 3 retries with exponential backoff.
|
|
147
|
+
- The `InngestService` singleton wraps the Inngest client and enforces typed events via `AppEvents`.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Langfuse — LLM Observability & Tracing
|
|
2
|
+
|
|
3
|
+
Langfuse is an open-source LLM observability platform. It captures traces, spans, and generations from LLM calls so you can debug prompt behavior, monitor latency/cost, and run evaluations. Think of it as "Datadog for LLM apps."
|
|
4
|
+
|
|
5
|
+
**Our opinionated setup:** We integrate Langfuse via OpenTelemetry (OTEL), not the Langfuse SDK directly. OTEL auto-instruments the app (HTTP, DB, etc.), and Langfuse acts as a span processor that receives LLM-specific traces. This gives us unified tracing — both traditional backend spans and LLM generations in the same trace.
|
|
6
|
+
|
|
7
|
+
## When to Use Langfuse
|
|
8
|
+
|
|
9
|
+
**Use Langfuse when the app calls LLMs** (OpenAI, Bedrock/Claude, etc.). It gives you:
|
|
10
|
+
- Trace visualization of multi-step LLM chains
|
|
11
|
+
- Token usage and cost tracking per request
|
|
12
|
+
- Prompt versioning and A/B testing
|
|
13
|
+
- Evaluation scores attached to traces
|
|
14
|
+
|
|
15
|
+
**Skip Langfuse when the app has no LLM calls.** The OTEL instrumentation still works without Langfuse — you just won't have the LLM-specific dashboard. If Langfuse env vars are not set, the integration gracefully no-ops.
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
### OpenTelemetry + Langfuse Span Processor
|
|
20
|
+
|
|
21
|
+
The template uses Next.js's instrumentation hook (called on server startup) to bootstrap OTEL with Langfuse:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { LangfuseSpanProcessor } from "@langfuse/otel";
|
|
25
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
26
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
27
|
+
|
|
28
|
+
const sdk = new NodeSDK({
|
|
29
|
+
spanProcessors: [new LangfuseSpanProcessor({ baseUrl, publicKey, secretKey })],
|
|
30
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
31
|
+
});
|
|
32
|
+
sdk.start();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `getNodeAutoInstrumentations()` automatically instruments HTTP calls, database queries, and other standard Node.js operations.
|
|
36
|
+
- `LangfuseSpanProcessor` forwards spans to Langfuse. LLM spans (from the Vercel AI SDK or OpenAI SDK) are recognized and displayed as generations in the Langfuse UI.
|
|
37
|
+
|
|
38
|
+
**You do not need to manually instrument LLM calls.** If you use the Vercel AI SDK (`ai` package) or the OpenAI SDK, OTEL picks them up automatically.
|
|
39
|
+
|
|
40
|
+
### LangfuseService (Direct API)
|
|
41
|
+
|
|
42
|
+
For operations beyond automatic tracing — like attaching metadata or evaluation scores to a trace — the template includes a `LangfuseService` singleton:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const langfuse = LangfuseService.create();
|
|
46
|
+
|
|
47
|
+
if (langfuse.isConfigured()) {
|
|
48
|
+
await langfuse.updateTraceMetadata(traceId, {
|
|
49
|
+
userId: "user-123",
|
|
50
|
+
feedbackScore: 0.95,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This is a singleton — `create()` returns the same instance every time. It gracefully no-ops when Langfuse is not configured.
|
|
56
|
+
|
|
57
|
+
## Getting Langfuse Keys
|
|
58
|
+
|
|
59
|
+
### Percepta's Internal Instance
|
|
60
|
+
|
|
61
|
+
Percepta runs a shared Langfuse instance. To get keys:
|
|
62
|
+
|
|
63
|
+
1. Ask your team lead for access to the Percepta Langfuse instance
|
|
64
|
+
2. Log into the Langfuse dashboard
|
|
65
|
+
3. Go to **Settings → API Keys** and create a new key pair
|
|
66
|
+
4. You'll get a **public key** and **secret key**
|
|
67
|
+
|
|
68
|
+
### Self-Hosted / Langfuse Cloud
|
|
69
|
+
|
|
70
|
+
For external projects, you can:
|
|
71
|
+
- Use [Langfuse Cloud](https://cloud.langfuse.com) (free tier available)
|
|
72
|
+
- Self-host with `docker run langfuse/langfuse`
|
|
73
|
+
|
|
74
|
+
## Running Locally
|
|
75
|
+
|
|
76
|
+
### Option 1: Use Percepta's Langfuse (recommended)
|
|
77
|
+
|
|
78
|
+
Set the keys in `.env.local` pointing to the shared instance:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
LANGFUSE_BASE_URL=https://langfuse.internal.percepta.ai # ask team for actual URL
|
|
82
|
+
LANGFUSE_PUBLIC_KEY=pk-lf-...
|
|
83
|
+
LANGFUSE_SECRET_KEY=sk-lf-...
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Traces from local dev appear in the shared dashboard under your project.
|
|
87
|
+
|
|
88
|
+
### Option 2: Run Langfuse locally
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
docker run -d --name langfuse \
|
|
92
|
+
-p 3001:3000 \
|
|
93
|
+
-e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5434/langfuse \
|
|
94
|
+
langfuse/langfuse
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then set:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
LANGFUSE_BASE_URL=http://localhost:3001
|
|
101
|
+
LANGFUSE_PUBLIC_KEY=pk-lf-... # create in local UI after first login
|
|
102
|
+
LANGFUSE_SECRET_KEY=sk-lf-...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Option 3: Skip Langfuse entirely
|
|
106
|
+
|
|
107
|
+
Simply don't set the `LANGFUSE_*` env vars. The instrumentation gracefully skips the Langfuse span processor, and `LangfuseService.isConfigured()` returns `false`. OTEL still instruments the app for general tracing.
|
|
108
|
+
|
|
109
|
+
## Environment Variables
|
|
110
|
+
|
|
111
|
+
| Variable | Required | Description |
|
|
112
|
+
|----------|----------|-------------|
|
|
113
|
+
| `LANGFUSE_BASE_URL` | No | Langfuse server URL. If unset, Langfuse integration is disabled. |
|
|
114
|
+
| `LANGFUSE_PUBLIC_KEY` | No | Public key from Langfuse dashboard |
|
|
115
|
+
| `LANGFUSE_SECRET_KEY` | No | Secret key from Langfuse dashboard |
|
|
116
|
+
|
|
117
|
+
All three must be set for Langfuse to activate. If any is missing, the integration silently no-ops.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Oneshot — Build a Complete App from Requirements
|
|
2
|
+
|
|
3
|
+
Build a full application end-to-end in one shot: gather requirements, design, implement, verify locally, create the GitHub repo, and optionally deploy — fully autonomous after requirements are locked.
|
|
4
|
+
|
|
5
|
+
**When to use this:** The user has scaffolded a new project with `@percepta/create` and wants to build an app on top of it. They'll describe what they want at a high level. Your job is to turn that into a running application.
|
|
6
|
+
|
|
7
|
+
<HARD-GATE>
|
|
8
|
+
After requirements are confirmed, do NOT stop and give the turn back to the user until the app is fully built and running locally. Do NOT ask for approval between phases. Make reasonable assumptions and keep moving. The user can interrupt with course corrections at any time — treat those as input, not as a reason to pause.
|
|
9
|
+
</HARD-GATE>
|
|
10
|
+
|
|
11
|
+
## Process Flow
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Gather requirements
|
|
15
|
+
→ Confirm with user (ONLY pause point)
|
|
16
|
+
→ Design architecture
|
|
17
|
+
→ Implement in a loop (build → verify → fix → repeat)
|
|
18
|
+
→ Get local app running
|
|
19
|
+
→ Create GitHub repo
|
|
20
|
+
→ [If requested] Deploy to Ryvn
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Phase 1: Gather Requirements
|
|
26
|
+
|
|
27
|
+
Ask the user structured questions AND open-ended questions. Do this in ONE message — do not drip-feed questions across multiple turns.
|
|
28
|
+
|
|
29
|
+
### Structured Questions
|
|
30
|
+
|
|
31
|
+
Ask all that are relevant:
|
|
32
|
+
|
|
33
|
+
1. **What does this app do?** One-sentence description of the core purpose.
|
|
34
|
+
2. **Who are the users?** Internal team, external customers, both?
|
|
35
|
+
3. **What are the core entities?** (e.g., "Documents, Users, Teams" — the nouns in the data model)
|
|
36
|
+
4. **What are the key workflows?** (e.g., "User uploads a document → system extracts text → user reviews results")
|
|
37
|
+
5. **Does this app call LLMs?** If yes, which provider (OpenAI, Bedrock/Claude, both)? What for?
|
|
38
|
+
6. **Does this app need background jobs?** Long-running tasks, scheduled jobs, multi-step pipelines?
|
|
39
|
+
7. **Auth requirements?** Which provider — Google, Okta, Credentials, or just use the template default?
|
|
40
|
+
8. **Any external integrations?** APIs, webhooks, third-party services?
|
|
41
|
+
|
|
42
|
+
### Open-Ended
|
|
43
|
+
|
|
44
|
+
After the structured questions, ask:
|
|
45
|
+
|
|
46
|
+
> "Is there anything else I should know about this app — constraints, design preferences, prior art, or things you definitely don't want?"
|
|
47
|
+
|
|
48
|
+
### Confirm Requirements
|
|
49
|
+
|
|
50
|
+
Summarize what you understood back to the user in a structured format:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
## Requirements Summary
|
|
54
|
+
|
|
55
|
+
**App:** [one sentence]
|
|
56
|
+
**Users:** [who]
|
|
57
|
+
**Entities:** [list]
|
|
58
|
+
**Workflows:** [numbered list]
|
|
59
|
+
**LLM usage:** [yes/no, details]
|
|
60
|
+
**Background jobs:** [yes/no, details]
|
|
61
|
+
**Auth:** [provider]
|
|
62
|
+
**Integrations:** [list or none]
|
|
63
|
+
**Notes:** [anything else]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Ask: **"Does this look right? Any corrections before I start building?"**
|
|
67
|
+
|
|
68
|
+
This is the ONLY point where you pause for user confirmation. After they confirm, go fully autonomous.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Phase 2: Design Architecture
|
|
73
|
+
|
|
74
|
+
Based on the confirmed requirements, design the architecture. Do NOT write a plan document — think through it and write it as a brief message to the user (so they can see your thinking), then start building.
|
|
75
|
+
|
|
76
|
+
Cover:
|
|
77
|
+
1. **Database schema** — which tables, columns, relationships
|
|
78
|
+
2. **API routes** — which tRPC routers and procedures
|
|
79
|
+
3. **Pages** — which routes/pages in the app
|
|
80
|
+
4. **Background jobs** — which Inngest events and functions (if any)
|
|
81
|
+
5. **LLM integration** — which service to use, how to wire it (if any)
|
|
82
|
+
|
|
83
|
+
### Decision: Langfuse
|
|
84
|
+
|
|
85
|
+
Read [agent-skills/langfuse.md](langfuse.md) to understand Langfuse. **Use Langfuse if the app calls LLMs.** Skip it otherwise. The template already has the integration wired — you just need to ensure env vars are set.
|
|
86
|
+
|
|
87
|
+
### Decision: Inngest
|
|
88
|
+
|
|
89
|
+
Read [agent-skills/inngest.md](inngest.md) to understand Inngest. **Use Inngest if the app has background jobs, scheduled tasks, or multi-step agent pipelines.** The template has the scaffold — you add events and functions.
|
|
90
|
+
|
|
91
|
+
### Decision: Database
|
|
92
|
+
|
|
93
|
+
Read [agent-skills/database.md](database.md) for the Drizzle patterns. You will almost always need new tables.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Phase 3: Implement
|
|
98
|
+
|
|
99
|
+
Build the app in logical chunks. For each chunk: implement, then verify it compiles and runs. Fix issues before moving on.
|
|
100
|
+
|
|
101
|
+
### Implementation Order
|
|
102
|
+
|
|
103
|
+
Follow this order — each layer builds on the previous:
|
|
104
|
+
|
|
105
|
+
1. **Database schema** — Define tables, export from schema index, generate migration, apply it
|
|
106
|
+
2. **Services** — Business logic services (follow the existing singleton + `AsyncLocalStorage` pattern)
|
|
107
|
+
3. **tRPC routers** — API endpoints, compose in the root appRouter
|
|
108
|
+
4. **Inngest functions** — Events and function collections (if needed)
|
|
109
|
+
5. **Pages and components** — React pages and UI components
|
|
110
|
+
6. **Polish** — Error states, loading states, edge cases
|
|
111
|
+
|
|
112
|
+
### After Each Chunk
|
|
113
|
+
|
|
114
|
+
Run:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pnpm build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If it fails, fix the errors before moving to the next chunk. Do not accumulate broken code.
|
|
121
|
+
|
|
122
|
+
### Implementation Rules
|
|
123
|
+
|
|
124
|
+
- **Use `@percepta/design` components.** Do not write custom UI for things that exist in the design system (Button, Dialog, Table, Card, Input, etc.). Read `node_modules/@percepta/design/dist/src/index.d.ts` to see what's available.
|
|
125
|
+
- **Use `AsyncContent` for loading states.** Every data-fetching UI should use `AsyncContent` from `@percepta/components`.
|
|
126
|
+
- **Use `getEnvConfig()` for env vars.** Never use `process.env` directly.
|
|
127
|
+
- **Use `getLogger()` for logging.** Never use `console.log`. Use safe/unsafe field separation.
|
|
128
|
+
- **Follow the singleton pattern** for new services. Follow the existing `DatabaseService` singleton pattern in the codebase.
|
|
129
|
+
- **Use `protectedProcedure`** for authenticated tRPC routes.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Phase 4: Local Verification
|
|
134
|
+
|
|
135
|
+
Once all chunks are implemented, verify the app runs end-to-end locally.
|
|
136
|
+
|
|
137
|
+
### Step 1: Start dependencies
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm docker:up
|
|
141
|
+
pnpm db:setup-and-migrate
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Step 2: Start the dev server
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm dev
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Step 3: Verify the app works
|
|
151
|
+
|
|
152
|
+
Open the app in a browser and walk through the core workflows from the requirements. Check:
|
|
153
|
+
- Pages load without errors
|
|
154
|
+
- Data can be created, read, updated, deleted
|
|
155
|
+
- Auth works (if configured)
|
|
156
|
+
- Background jobs trigger and complete (if applicable)
|
|
157
|
+
- No console errors in the browser
|
|
158
|
+
|
|
159
|
+
### Step 4: Build check
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pnpm build
|
|
163
|
+
pnpm lint
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Both must pass clean.
|
|
167
|
+
|
|
168
|
+
**Do not report the app as complete until it builds, lints, and the core workflows work in the browser.**
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Phase 5: Create GitHub Repository
|
|
173
|
+
|
|
174
|
+
After the app is verified locally, create the GitHub repo:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Initialize git if not already done
|
|
178
|
+
git init
|
|
179
|
+
git add -A
|
|
180
|
+
git commit -m "Initial implementation of <app-name>"
|
|
181
|
+
|
|
182
|
+
# Create the repo under percepta-ai org
|
|
183
|
+
gh repo create percepta-ai/<app-name> --private --source=. --push
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
If `gh` is not authenticated, tell the user to run `gh auth login` and then continue.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Phase 6: Deploy to Ryvn (When Requested)
|
|
191
|
+
|
|
192
|
+
Only do this when the user explicitly asks to deploy.
|
|
193
|
+
|
|
194
|
+
Follow the step-by-step guide in [agent-skills/deploy.md](deploy.md). It contains the service definition, installation YAML, environment variables, and secrets needed for percepta-test.
|
|
195
|
+
|
|
196
|
+
For Ryvn CLI operations (checking status, logs, troubleshooting), use the `/use-ryvn` skill.
|
|
197
|
+
|
|
198
|
+
Report the deployment URL to the user when done.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Common Mistakes
|
|
203
|
+
|
|
204
|
+
| Mistake | Fix |
|
|
205
|
+
|---------|-----|
|
|
206
|
+
| Stopping after design to ask permission | After requirements are confirmed, build autonomously. |
|
|
207
|
+
| Not running `pnpm build` between chunks | Build after every chunk. Fix errors immediately. |
|
|
208
|
+
| Writing custom UI instead of using `@percepta/design` | Check the design system first. |
|
|
209
|
+
| Using `console.log` | Use `getLogger()` with safe/unsafe fields. |
|
|
210
|
+
| Using `process.env` | Use `getEnvConfig()`. |
|
|
211
|
+
| Forgetting to export new schema from the schema index | Every new table must be exported from the index. |
|
|
212
|
+
| Forgetting to register Inngest functions in the serve endpoint | Function collections must be added to the serve endpoint. |
|
|
213
|
+
| Forgetting to add new router to the root appRouter | Every new tRPC router must be composed in the root. |
|
|
214
|
+
| Not verifying the app in the browser | Build + lint passing is necessary but not sufficient. Test the UI. |
|
|
215
|
+
| Creating the GitHub repo before the app works locally | Verify locally first, then create the repo. |
|
|
216
|
+
| Deploying without the user asking | Only deploy when explicitly requested. |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Ryvn — Deployment Platform
|
|
2
|
+
|
|
3
|
+
Ryvn is the deployment platform used by Percepta to manage cloud infrastructure. It provisions environments, deploys services, and handles CI/CD — abstracting away Kubernetes, Terraform, and cloud provider details.
|
|
4
|
+
|
|
5
|
+
## Percepta Environments
|
|
6
|
+
|
|
7
|
+
| Environment | Purpose | Domain pattern |
|
|
8
|
+
|-------------|---------|---------------|
|
|
9
|
+
| `percepta-test` | Internal dev/test | `<app>.percepta-test.aitco.dev` |
|
|
10
|
+
|
|
11
|
+
New apps are deployed to **percepta-test**. This environment has a shared PostgreSQL instance, Inngest server, Langfuse, and OTEL collector.
|
|
12
|
+
|
|
13
|
+
## Deploying This App
|
|
14
|
+
|
|
15
|
+
For the full step-by-step deployment guide to percepta-test, see [deploy.md](deploy.md).
|
|
16
|
+
|
|
17
|
+
## Ryvn CLI
|
|
18
|
+
|
|
19
|
+
For all Ryvn CLI operations (checking status, viewing logs, troubleshooting, managing installations), use the **`/use-ryvn`** skill. It has comprehensive CLI reference docs and handles authentication, deployment, configuration, and operations.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Quick status check
|
|
23
|
+
ryvn get installation __APP_NAME__ -e percepta-test
|
|
24
|
+
ryvn logs __APP_NAME__ -e percepta-test
|
|
25
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Deploy
|
|
2
|
+
|
|
3
|
+
This directory holds infrastructure-as-code for deploying __APP_TITLE__ to Percepta environments.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
deploy/
|
|
7
|
+
└── ryvn/
|
|
8
|
+
├── __APP_NAME__.service.yaml # Ryvn Service definition
|
|
9
|
+
└── environments/
|
|
10
|
+
└── percepta-test/
|
|
11
|
+
└── installations/
|
|
12
|
+
└── __APP_NAME__.env.percepta-test.serviceinstallation.yaml # percepta-test installation
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
These files are pre-filled with all values needed to deploy to `https://__APP_NAME__.percepta-test.aitco.dev`. The only manual steps are: open a PR in `Percepta-Core/infra` to copy them in, and set three secrets in the Ryvn UI.
|
|
16
|
+
|
|
17
|
+
## Deploying
|
|
18
|
+
|
|
19
|
+
Tell Claude "deploy this app to percepta-test" — it'll follow [`agent-skills/deploy.md`](../agent-skills/deploy.md), which walks through copying these files into the infra repo, opening the PR, and the manual Ryvn UI step.
|
|
20
|
+
|
|
21
|
+
## What's in these files
|
|
22
|
+
|
|
23
|
+
**`__APP_NAME__.service.yaml`** — tells Ryvn how to build the Docker image. Points at `Percepta-Core/__APP_NAME__` (change the repo if you've named the GitHub repo differently). The `NPM_TOKEN` build arg is a read-only npm token shared across all Percepta apps for installing `@percepta/*` packages — it's not a secret.
|
|
24
|
+
|
|
25
|
+
**`__APP_NAME__.env.percepta-test.serviceinstallation.yaml`** — configures the deployment on `percepta-test`. Three categories of values are baked in:
|
|
26
|
+
|
|
27
|
+
1. **Per-app values** that vary by app name (URLs, k8s service names) — substituted at create-time.
|
|
28
|
+
2. **Shared platform values** (Inngest, Langfuse, OTel endpoints) — copied as literals from the `tc-emo` reference installation. These are stable across percepta-test apps.
|
|
29
|
+
3. **Secrets** (`BETTER_AUTH_SECRET`, `ENCRYPTION_SECRET_KEY`, `LANGFUSE_SECRET_KEY`) — declared in the YAML, value entered once in the Ryvn UI.
|
|
30
|
+
|
|
31
|
+
## Repo layout note
|
|
32
|
+
|
|
33
|
+
These files assume this repo is a **monorepo** with the app at `packages/__APP_NAME__/` (the layout produced by `@percepta/create`). The `service.yaml` sets `context: packages/__APP_NAME__` accordingly. If you've flattened this into a single-package repo, change `context:` and `dockerfile:` to `.` and `Dockerfile`.
|
|
34
|
+
|
|
35
|
+
The release workflow lives at `.github/workflows/__APP_NAME__-ryvn-release.yaml` (at the repo root, where GitHub Actions picks it up). It's path-filtered to `packages/__APP_NAME__/`, so changes to other packages won't trigger this app's deploy. Multiple webapps in the same monorepo each get their own `<name>-ryvn-release.yaml` and don't collide.
|
|
36
|
+
|
|
37
|
+
## Adding more environments
|
|
38
|
+
|
|
39
|
+
Copy `environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml` to a new `environments/<env>/installations/__APP_NAME__.env.<env>.serviceinstallation.yaml` and change the `environment:`, host, and any environment-specific values.
|