@percepta/create 3.6.2 → 4.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.
Files changed (117) hide show
  1. package/README.md +37 -6
  2. package/dist/{git-ops-C2CIjuce.js → git-ops-BD7JNnal.js} +1 -1
  3. package/dist/{git-ops-C2CIjuce.js.map → git-ops-BD7JNnal.js.map} +1 -1
  4. package/dist/github-RCIMUq70.js +131 -0
  5. package/dist/github-RCIMUq70.js.map +1 -0
  6. package/dist/index.js +68 -125
  7. package/dist/index.js.map +1 -1
  8. package/dist/{init-sI9aIrkU.js → init-COp0nGdk.js} +4 -2
  9. package/dist/{init-sI9aIrkU.js.map → init-COp0nGdk.js.map} +1 -1
  10. package/dist/manifest-CqIDnbgs.js +58 -0
  11. package/dist/manifest-CqIDnbgs.js.map +1 -0
  12. package/dist/register-app-C7ZBpAaZ.js +103 -0
  13. package/dist/register-app-C7ZBpAaZ.js.map +1 -0
  14. package/dist/register-os-blueprint-DGjBUZYa.js +90 -0
  15. package/dist/register-os-blueprint-DGjBUZYa.js.map +1 -0
  16. package/dist/{status-CKe4aKso.js → status-BXYaQ4a2.js} +3 -3
  17. package/dist/{status-CKe4aKso.js.map → status-BXYaQ4a2.js.map} +1 -1
  18. package/dist/{sync-D1vkoofl.js → sync-BayU4w1j.js} +3 -3
  19. package/dist/{sync-D1vkoofl.js.map → sync-BayU4w1j.js.map} +1 -1
  20. package/dist/template-versions-CEIP9vhl.js +35 -0
  21. package/dist/template-versions-CEIP9vhl.js.map +1 -0
  22. package/dist/{upstream-gUHLWSR1.js → upstream-CZEzLrS4.js} +3 -3
  23. package/dist/{upstream-gUHLWSR1.js.map → upstream-CZEzLrS4.js.map} +1 -1
  24. package/dist/validate-dssldJAj.js +14 -0
  25. package/dist/validate-dssldJAj.js.map +1 -0
  26. package/package.json +1 -1
  27. package/template-versions.json +2 -2
  28. package/templates/infra/os.blueprint.yaml.template +138 -0
  29. package/templates/library/README.md +5 -2
  30. package/templates/library/gitignore.template +1 -0
  31. package/templates/library/package.json.template +17 -13
  32. package/templates/library/src/index.test.ts +8 -0
  33. package/templates/library/tsconfig.json +1 -17
  34. package/templates/library/tsdown.config.ts +3 -0
  35. package/templates/library/vitest.config.ts +3 -0
  36. package/templates/monorepo/.dockerignore +1 -0
  37. package/templates/monorepo/.github/CODEOWNERS +67 -0
  38. package/templates/monorepo/.github/actions/ci/action.yml +56 -0
  39. package/templates/monorepo/.github/workflows/build-and-publish.yml +22 -0
  40. package/templates/monorepo/.github/workflows/pr-build.yml +21 -0
  41. package/templates/monorepo/.node-version +1 -0
  42. package/templates/monorepo/README.md +41 -3
  43. package/templates/monorepo/auth/README.md +6 -3
  44. package/templates/monorepo/auth/package.json +5 -7
  45. package/templates/monorepo/auth/src/auth.ts +0 -1
  46. package/templates/monorepo/auth/src/config/database.ts +1 -1
  47. package/templates/monorepo/auth/tsconfig.json +1 -10
  48. package/templates/{webapp → monorepo}/docker-compose.yml +2 -2
  49. package/templates/monorepo/gitignore.template +1 -0
  50. package/templates/monorepo/oxfmt.config.ts.template +3 -0
  51. package/templates/monorepo/oxlint.config.ts.template +3 -0
  52. package/templates/monorepo/package.json.template +22 -11
  53. package/templates/monorepo/scripts/setup-local-databases.mjs +183 -0
  54. package/templates/monorepo/turbo.json +20 -0
  55. package/templates/webapp/.node-version +0 -1
  56. package/templates/webapp/AGENTS.md +33 -35
  57. package/templates/webapp/README.md +34 -38
  58. package/templates/webapp/agent-skills/database.md +21 -21
  59. package/templates/webapp/agent-skills/langfuse.md +7 -7
  60. package/templates/webapp/agent-skills/llm.md +4 -2
  61. package/templates/webapp/agent-skills/oneshot.md +7 -6
  62. package/templates/webapp/agent-skills/ryvn.md +12 -16
  63. package/templates/webapp/deploy/README.md +10 -51
  64. package/templates/webapp/drizzle.config.ts +2 -23
  65. package/templates/webapp/env.example.template +8 -14
  66. package/templates/webapp/globals.d.ts +1 -0
  67. package/templates/webapp/oxfmt.config.ts.template +5 -0
  68. package/templates/webapp/package.json.template +18 -33
  69. package/templates/webapp/scripts/seed.ts +1 -1
  70. package/templates/webapp/scripts/start.sh +12 -16
  71. package/templates/webapp/src/app/global-error.tsx +1 -1
  72. package/templates/webapp/src/config/getEnvConfig.ts +4 -10
  73. package/templates/webapp/src/config/isDev.ts +0 -2
  74. package/templates/webapp/src/drizzle/db.ts +6 -21
  75. package/templates/webapp/src/lib/auth-client.ts +6 -3
  76. package/templates/webapp/src/startup-checks.ts +28 -7
  77. package/templates/webapp/tsconfig.json +1 -12
  78. package/templates/webapp/vitest.config.ts +3 -7
  79. package/templates/library/eslint.config.js +0 -10
  80. package/templates/monorepo/auth/scripts/setup-database.ts +0 -11
  81. package/templates/monorepo/eslint.config.js +0 -10
  82. package/templates/monorepo/tsconfig.json +0 -16
  83. package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +0 -92
  84. package/templates/webapp/.github/workflows/ci.yml +0 -149
  85. package/templates/webapp/.prettierrc.mjs +0 -5
  86. package/templates/webapp/agent-skills/deploy.md +0 -92
  87. package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +0 -10
  88. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +0 -11
  89. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +0 -154
  90. package/templates/webapp/eslint.config.mjs +0 -100
  91. package/templates/webapp/npmrc.template +0 -4
  92. package/templates/webapp/terraform/README.md +0 -147
  93. package/templates/webapp/terraform/deploy.sh +0 -97
  94. package/templates/webapp/terraform/main.tf +0 -101
  95. package/templates/webapp/terraform/modules/cloudtrail/main.tf +0 -27
  96. package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +0 -10
  97. package/templates/webapp/terraform/modules/cloudtrail/variables.tf +0 -15
  98. package/templates/webapp/terraform/modules/networking/main.tf +0 -118
  99. package/templates/webapp/terraform/modules/networking/outputs.tf +0 -38
  100. package/templates/webapp/terraform/modules/networking/variables.tf +0 -24
  101. package/templates/webapp/terraform/modules/rds/main.tf +0 -227
  102. package/templates/webapp/terraform/modules/rds/outputs.tf +0 -73
  103. package/templates/webapp/terraform/modules/rds/variables.tf +0 -61
  104. package/templates/webapp/terraform/modules/s3-logging/main.tf +0 -148
  105. package/templates/webapp/terraform/modules/s3-logging/outputs.tf +0 -10
  106. package/templates/webapp/terraform/modules/s3-logging/variables.tf +0 -16
  107. package/templates/webapp/terraform/modules/secrets/main.tf +0 -39
  108. package/templates/webapp/terraform/modules/secrets/outputs.tf +0 -9
  109. package/templates/webapp/terraform/modules/secrets/variables.tf +0 -51
  110. package/templates/webapp/terraform/outputs.tf +0 -102
  111. package/templates/webapp/terraform/providers.tf +0 -32
  112. package/templates/webapp/terraform/schema/main.tf +0 -4
  113. package/templates/webapp/terraform/schema/outputs.tf +0 -9
  114. package/templates/webapp/terraform/schema/variables.tf +0 -19
  115. package/templates/webapp/terraform/schema/versions.tf +0 -38
  116. package/templates/webapp/terraform/terraform.tfvars.example +0 -65
  117. package/templates/webapp/terraform/variables.tf +0 -129
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from "node:child_process";
4
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const LOCAL_POSTGRES_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
9
+ const LOCAL_POSTGRES_PORT = "5434";
10
+ const POSTGRES_SERVICE = "postgres";
11
+ const POSTGRES_USER = "postgres";
12
+ const ROOT_DIR = path.resolve(
13
+ path.dirname(fileURLToPath(import.meta.url)),
14
+ "..",
15
+ );
16
+ const PACKAGES_DIR = path.join(ROOT_DIR, "packages");
17
+
18
+ const databases = new Set(["auth"]);
19
+
20
+ for (const packageDir of listPackageDirs()) {
21
+ const database = readPackageDatabaseName(packageDir);
22
+ if (database != null) {
23
+ databases.add(database);
24
+ }
25
+ }
26
+
27
+ for (const database of [...databases].sort()) {
28
+ ensureDatabase(database);
29
+ }
30
+
31
+ function listPackageDirs() {
32
+ if (!existsSync(PACKAGES_DIR)) return [];
33
+
34
+ return readdirSync(PACKAGES_DIR, { withFileTypes: true })
35
+ .filter((entry) => entry.isDirectory())
36
+ .map((entry) => packageDirFor(entry.name));
37
+ }
38
+
39
+ function packageDirFor(packageName) {
40
+ if (
41
+ packageName === "." ||
42
+ packageName === ".." ||
43
+ packageName.includes("/") ||
44
+ packageName.includes("\\")
45
+ ) {
46
+ throw new Error(`Unexpected package directory name: ${packageName}`);
47
+ }
48
+
49
+ return path.join(PACKAGES_DIR, packageName);
50
+ }
51
+
52
+ function readPackageDatabaseName(packageDir) {
53
+ const env = readPackageEnvFile(packageDir);
54
+ const databaseUrl = env.DATABASE_URL;
55
+ if (databaseUrl == null || databaseUrl.length === 0) return null;
56
+
57
+ let url;
58
+ try {
59
+ url = new URL(databaseUrl);
60
+ } catch {
61
+ throw new Error(
62
+ `Invalid DATABASE_URL in ${path.relative(ROOT_DIR, packageDir)}/.env.local`,
63
+ );
64
+ }
65
+
66
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") {
67
+ throw new Error(
68
+ `DATABASE_URL in ${path.relative(ROOT_DIR, packageDir)}/.env.local must use postgres or postgresql.`,
69
+ );
70
+ }
71
+
72
+ const port = url.port || "5432";
73
+ if (!LOCAL_POSTGRES_HOSTS.has(url.hostname) || port !== LOCAL_POSTGRES_PORT) {
74
+ console.log(
75
+ `Skipping non-local app database for ${path.relative(ROOT_DIR, packageDir)}.`,
76
+ );
77
+ return null;
78
+ }
79
+
80
+ const database = decodeURIComponent(url.pathname.replace(/^\/+/, ""));
81
+ if (database.length === 0) {
82
+ throw new Error(
83
+ `DATABASE_URL in ${path.relative(ROOT_DIR, packageDir)}/.env.local must include a database name.`,
84
+ );
85
+ }
86
+
87
+ return database;
88
+ }
89
+
90
+ function readPackageEnvFile(packageDir) {
91
+ const safePackageDir = assertPackageDir(packageDir);
92
+ const envPath = path.join(safePackageDir, ".env.local");
93
+ if (!existsSync(envPath)) return {};
94
+
95
+ const env = {};
96
+ const content = readFileSync(envPath, "utf8");
97
+ for (const rawLine of content.split(/\r?\n/)) {
98
+ const line = rawLine.trim();
99
+ if (!line || line.startsWith("#")) continue;
100
+
101
+ const normalized = line.startsWith("export ") ? line.slice(7).trim() : line;
102
+ const separatorIndex = normalized.indexOf("=");
103
+ if (separatorIndex === -1) continue;
104
+
105
+ const key = normalized.slice(0, separatorIndex).trim();
106
+ const rawValue = normalized.slice(separatorIndex + 1).trim();
107
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
108
+
109
+ env[key] = unquote(rawValue);
110
+ }
111
+
112
+ return env;
113
+ }
114
+
115
+ function assertPackageDir(packageDir) {
116
+ const resolvedPackageDir = path.resolve(packageDir);
117
+ const relative = path.relative(PACKAGES_DIR, resolvedPackageDir);
118
+
119
+ if (
120
+ relative.length === 0 ||
121
+ relative.startsWith("..") ||
122
+ path.isAbsolute(relative) ||
123
+ relative.includes(path.sep)
124
+ ) {
125
+ throw new Error(`Unexpected package directory: ${packageDir}`);
126
+ }
127
+
128
+ return resolvedPackageDir;
129
+ }
130
+
131
+ function unquote(value) {
132
+ if (
133
+ (value.startsWith('"') && value.endsWith('"')) ||
134
+ (value.startsWith("'") && value.endsWith("'"))
135
+ ) {
136
+ return value.slice(1, -1);
137
+ }
138
+
139
+ return value;
140
+ }
141
+
142
+ function ensureDatabase(database) {
143
+ const exists = execDockerCompose([
144
+ "exec",
145
+ "-T",
146
+ POSTGRES_SERVICE,
147
+ "psql",
148
+ "-U",
149
+ POSTGRES_USER,
150
+ "-d",
151
+ "postgres",
152
+ "-tAc",
153
+ `SELECT 1 FROM pg_database WHERE datname = ${quotePgLiteral(database)}`,
154
+ ]).trim();
155
+
156
+ if (exists === "1") {
157
+ console.log(`Database ${database} already exists.`);
158
+ return;
159
+ }
160
+
161
+ execDockerCompose([
162
+ "exec",
163
+ "-T",
164
+ POSTGRES_SERVICE,
165
+ "createdb",
166
+ "-U",
167
+ POSTGRES_USER,
168
+ database,
169
+ ]);
170
+ console.log(`Database ${database} created.`);
171
+ }
172
+
173
+ function execDockerCompose(args) {
174
+ return execFileSync("docker", ["compose", ...args], {
175
+ cwd: ROOT_DIR,
176
+ encoding: "utf8",
177
+ stdio: ["ignore", "pipe", "inherit"],
178
+ });
179
+ }
180
+
181
+ function quotePgLiteral(value) {
182
+ return `'${value.replaceAll("'", "''")}'`;
183
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://turborepo.com/schema.json",
3
+ "ui": "tui",
4
+ "globalDependencies": ["pnpm-workspace.yaml"],
5
+ "tasks": {
6
+ "build": {
7
+ "dependsOn": ["^build"],
8
+ "outputs": ["dist/**", "**/.next/**", "!**/.next/cache/**"]
9
+ },
10
+ "typecheck": {
11
+ "dependsOn": ["^build"]
12
+ },
13
+ "test": {
14
+ "dependsOn": ["^build"]
15
+ },
16
+ "clean": {
17
+ "cache": false
18
+ }
19
+ }
20
+ }
@@ -1,2 +1 @@
1
1
  24.4.1
2
-
@@ -1,35 +1,34 @@
1
1
  # Webapp Template
2
2
 
3
- Next.js 15 full-stack application scaffolded from the Mosaic webapp template via `@percepta/create`. Uses React 19, TypeScript, Tailwind CSS v4, tRPC, Drizzle ORM, Better Auth, SpiceDB access control, and Inngest.
3
+ Next.js 16 full-stack application scaffolded from the Mosaic webapp template via `@percepta/create`. Uses React 19, TypeScript, Tailwind CSS v4, tRPC, Drizzle ORM, Better Auth, SpiceDB access control, and Inngest.
4
4
 
5
5
  ## Build & Dev Commands
6
6
 
7
7
  - `pnpm dev` — start dev server with Turbopack
8
8
  - `pnpm build` — production build
9
- - `pnpm lint` — run ESLint
9
+ - `pnpm typecheck` — type-check with `tsc`
10
10
  - `pnpm test` — run Vitest tests
11
- - `pnpm docker:up` / `pnpm docker:down` start PostgreSQL and SpiceDB, waiting for health, or stop them
11
+ - Linting and formatting run from the monorepo root: `pnpm lint` (oxlint) and `pnpm format` (oxfmt)
12
12
  - `pnpm access:validate` — validate access schema and manifest
13
13
  - `pnpm access:apply-local` — apply merged customer access schema to local SpiceDB
14
- - `pnpm auth:db:setup-and-migrate` — setup and migrate the shared customer auth database
14
+ - `pnpm auth:db:migrate` — run shared customer auth database migrations
15
15
  - `pnpm inngest:dev` — start local Inngest dev server when working on background jobs
16
16
  - `pnpm db:generate` — generate Drizzle migrations
17
17
  - `pnpm db:migrate` — apply migrations
18
- - `pnpm db:setup-and-migrate` — create DB + migrate
19
- - `pnpm db:studio` — setup/migrate the database, then open Drizzle Studio
18
+ - `pnpm db:studio` — run migrations, then open Drizzle Studio
20
19
  - `pnpm db:seed` — seed shared-auth dev users and local grants for customer admin, app admin, app user, and app non-user personas (all use password)
21
20
 
22
21
  **Package manager**: Always use `pnpm`, never `npm` or `yarn`.
23
22
 
24
- Local development requires PostgreSQL and SpiceDB. Run Inngest locally when a workflow needs it. Do not run a local LGTM/Langfuse stack unless you are specifically debugging telemetry; those are wired by the Ryvn environment for deploys.
23
+ Local PostgreSQL and SpiceDB are owned by the monorepo root. Use `pnpm run setup` from this package or the root to start services, create local databases, run migrations, and seed users/grants. Run Inngest locally when a workflow needs it. Do not run a local LGTM/Langfuse stack unless you are specifically debugging telemetry; those are wired by the Ryvn environment for deploys.
25
24
  If local LLM calls are needed, `pnpm dev` loads shared provider keys from `~/.config/percepta/create.env` when it exists.
26
25
 
27
26
  ## Code Style
28
27
 
29
28
  - Double quotes for strings
30
- - `no-console` is enforced use `@percepta/logger` instead (see below)
31
- - Logger messages must be plain string literals, not variables or templates
32
- - `no-process-env` is enforced — use `getEnvConfig()` from `src/config/`
29
+ - Prefer `@percepta/logger` over `console` (oxlint flags `console` as a warning, see below)
30
+ - Logger messages should be plain string literals, not variables or templates
31
+ - Read env via `getEnvConfig()` from `src/config/` rather than `process.env` directly (a project convention)
33
32
  - Use `@percepta/design` components before writing custom UI
34
33
  - For loading/error states, use local UI or add `@percepta/components` if you want `AsyncContent`
35
34
  - Tailwind CSS for all styling; icons from `lucide-react`
@@ -58,13 +57,9 @@ src/ # Application source
58
57
  │ └── observability/ # OpenTelemetry setup
59
58
  └── utils/ # Helpers (cn, pathEncryption, etc.)
60
59
 
61
- deploy/ # Infrastructure-as-code for Ryvn deployments
60
+ deploy/ # Optional release metadata
62
61
  └── ryvn/
63
- ├── __APP_NAME__.service.yaml # Ryvn web service
64
- ├── __APP_NAME__-terraform.service.yaml # Ryvn schema service
65
- └── environments/<env>/installations/
66
- ├── __APP_NAME__.env.<env>.serviceinstallation.yaml
67
- └── __APP_NAME__-terraform.env.<env>.serviceinstallation.yaml
62
+ └── __APP_NAME__.service.yaml # Ryvn web service
68
63
  ```
69
64
 
70
65
  ## @percepta Packages
@@ -109,22 +104,26 @@ Also includes composite components: `ButtonWithDropdown`, `Combobox`, `IconButto
109
104
 
110
105
  ### @percepta/build — Shared Build Configs
111
106
 
112
- Provides centralized formatter, TypeScript, bundler, and Vitest configuration.
113
-
114
- ```js
115
- // eslint.config.mjs
116
- import eslint from "@eslint/js";
117
- import tseslint from "typescript-eslint";
118
-
119
- export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended);
120
- ```
107
+ Provides centralized linter (oxlint), formatter (oxfmt), TypeScript, bundler (tsdown), and Vitest configuration.
121
108
 
122
109
  ```json
123
110
  // tsconfig.json
124
111
  { "extends": "@percepta/build/tsconfig/web" }
125
112
  ```
126
113
 
127
- Vitest config is also available via `@percepta/build/vitest`.
114
+ ```ts
115
+ // oxfmt.config.ts
116
+ import { defineOxfmtReactConfig } from "@percepta/build/oxfmt";
117
+ export default defineOxfmtReactConfig({ stylesheet: "./src/styles/globals.css" });
118
+ ```
119
+
120
+ ```ts
121
+ // vitest.config.ts
122
+ import { createVitestConfig } from "@percepta/build/vitest";
123
+ export default createVitestConfig({ additionalSetupFiles: ["./vitest.setup.ts"] });
124
+ ```
125
+
126
+ Linting is configured once at the monorepo root via `@percepta/build/oxlint`.
128
127
 
129
128
  ### @percepta/components — Optional React Utilities
130
129
 
@@ -200,8 +199,7 @@ Detailed how-to guides for each major stack component. Read the relevant guide w
200
199
  | LLM Observability (Langfuse) | [agent-skills/langfuse.md](agent-skills/langfuse.md) | App uses LLMs and needs trace/eval monitoring |
201
200
  | Database (Drizzle) | [agent-skills/database.md](agent-skills/database.md) | Adding tables, writing migrations, querying data |
202
201
  | Access Control (SpiceDB) | [agent-skills/access-control.md](agent-skills/access-control.md) | Adding Zed policy, app roles, permissioned resources, or group sync |
203
- | Deployment (Ryvn) | [agent-skills/ryvn.md](agent-skills/ryvn.md) | Ryvn overview and Percepta environment context |
204
- | Deploy to percepta-test | [agent-skills/deploy.md](agent-skills/deploy.md) | Step-by-step deploy using the pre-scaffolded `deploy/ryvn/` IaC files |
202
+ | Deployment (Ryvn) | [agent-skills/ryvn.md](agent-skills/ryvn.md) | Ryvn service release notes; environment installs live in infra |
205
203
  | Build App (Oneshot) | [agent-skills/oneshot.md](agent-skills/oneshot.md) | Building a complete app from requirements end-to-end |
206
204
 
207
205
  ## Key Patterns
@@ -221,14 +219,14 @@ Client-side usage via `src/lib/trpc.ts`.
221
219
 
222
220
  ### Authentication
223
221
 
224
- Better Auth is configured in the customer monorepo's shared `@__REPO_NAME__/auth` package. The app imports it through `src/lib/auth/` for local session validation.
222
+ Better Auth is configured in the customer monorepo's shared `@__REPO_NAME__/auth` package. The app imports it through `src/lib/auth/` for local session validation. `DATABASE_URL` is this app's database only; deployed auth should use `AUTH_DATABASE_URL` from the monorepo auth Secret.
225
223
 
226
224
  - **Server-side**: `auth.api.getSession({ headers: await headers() })` — get session in server components or tRPC context
227
225
  - **Client-side**: `authClient.useSession()` — React hook from `src/lib/auth-client.ts`
228
226
  - **Sign in**: `authClient.signIn.email({ email, password })` — client-side
229
227
  - **Sign out**: `authClient.signOut()` — client-side
230
228
  - **API route**: `src/app/api/auth/[...all]/route.ts` — Better Auth handler
231
- - **Env vars**: `BETTER_AUTH_SECRET` (required), `BETTER_AUTH_URL` (defaults to `http://localhost:3000`)
229
+ - **Env vars**: `BETTER_AUTH_SECRET` (required), `BETTER_AUTH_URL` (defaults to `http://localhost:3000`), `AUTH_DATABASE_URL` for deployed shared auth DB wiring
232
230
 
233
231
  ### Background Jobs
234
232
 
@@ -248,11 +246,11 @@ OpenTelemetry initialized in `src/instrumentation.ts`. Server traces and metrics
248
246
 
249
247
  ## Deployment
250
248
 
251
- To deploy this app to percepta-test, follow [agent-skills/deploy.md](agent-skills/deploy.md). The direct deploy helper treats percepta-test as an existing platform environment: it preflights the shared Postgres, Inngest, OTEL collector, and LGTM installations, then creates/updates this app's Ryvn services and installations.
252
-
253
- The release CI/CD workflows are already included under `.github/workflows/`.
254
-
255
- For Ryvn CLI operations, use the `/use-ryvn` skill.
249
+ Blueberry does not generate environment-specific deployments. Customer and
250
+ stack-specific infrastructure, app database registration, runtime services,
251
+ ingress, secrets, and service installations belong in the infra repo. The
252
+ release CI/CD workflow is included under `.github/workflows/` for stacks that
253
+ use Ryvn releases.
256
254
 
257
255
  ## Template Sync
258
256
 
@@ -1,12 +1,12 @@
1
1
  # __APP_TITLE__
2
2
 
3
- A production-ready Next.js application with authentication, database, logging, background jobs, and infrastructure as code.
3
+ A production-ready Next.js application with authentication, database, logging, and background jobs.
4
4
 
5
5
  Design theme: `__MOSAIC_DESIGN_THEME__`
6
6
 
7
7
  ## Features
8
8
 
9
- - **Next.js 15** with App Router
9
+ - **Next.js 16** with App Router
10
10
  - **Authentication** via Better Auth with email/password credentials
11
11
  - **Database** with PostgreSQL, Drizzle ORM, and migrations
12
12
  - **Access Control** with SpiceDB schema authoring and manifest validation
@@ -14,7 +14,7 @@ Design theme: `__MOSAIC_DESIGN_THEME__`
14
14
  - **Background Jobs** with Inngest
15
15
  - **LLM Calls** with a provider-backed `LLMService`
16
16
  - **Observability** with OpenTelemetry, LGTM-compatible traces/metrics/logs, and Langfuse integration
17
- - **Infrastructure** with Terraform modules for AWS (RDS, S3, IAM)
17
+ - **Deployment-ready** Dockerfile and environment-neutral service metadata
18
18
  - **Type Safety** with TypeScript and Zod schemas
19
19
 
20
20
  ## Quick Start
@@ -29,9 +29,9 @@ Copy `.env.example` to `.env.local` and configure any app-specific environment v
29
29
  pnpm run setup
30
30
  ```
31
31
 
32
- This starts local PostgreSQL and SpiceDB, applies the local access schema,
33
- sets up the shared customer auth database, runs app migrations, and seeds dev
34
- users.
32
+ This delegates to the monorepo root. The root setup starts local PostgreSQL and
33
+ SpiceDB, creates the shared auth database and this app's database, applies the
34
+ local access schema, runs migrations, and seeds dev users.
35
35
 
36
36
  ### 3. Start Inngest When Using Background Jobs
37
37
 
@@ -49,7 +49,7 @@ pnpm dev
49
49
 
50
50
  Open [http://localhost:3000](http://localhost:3000) to see your app.
51
51
 
52
- OpenTelemetry, Faro, and Langfuse are optional in local development. Leave their env vars empty unless you are actively debugging telemetry; production deploys wire server traces, metrics, logs, and shared Langfuse demo credentials into the target Ryvn environment. General HTTP and database spans go to the OTEL/LGTM pipeline; Langfuse receives only AI SDK spans by default.
52
+ OpenTelemetry, Faro, and Langfuse are optional in local development. Leave their env vars empty unless you are actively debugging telemetry. Remote deployments should provide telemetry and Langfuse values through the target platform. General HTTP and database spans go to the OTEL/LGTM pipeline; Langfuse receives only AI SDK spans by default.
53
53
 
54
54
  If you need local LLM calls, set provider keys once in your shell profile or in `~/.config/percepta/create.env`:
55
55
 
@@ -87,18 +87,14 @@ src/
87
87
  | `pnpm dev` | Start development server with Turbopack |
88
88
  | `pnpm build` | Build for production |
89
89
  | `pnpm start` | Start production server |
90
- | `pnpm lint` | Run ESLint |
91
- | `pnpm docker:up` | Start PostgreSQL and SpiceDB containers and wait until they are healthy |
92
- | `pnpm docker:down` | Stop PostgreSQL and SpiceDB containers |
90
+ | `pnpm typecheck` | Type-check with `tsc` |
93
91
  | `pnpm access:validate` | Validate the access manifest and schema |
94
92
  | `pnpm access:apply-local` | Apply the merged customer access schema to local SpiceDB |
95
- | `pnpm auth:db:setup-and-migrate` | Setup and migrate the shared customer auth database |
93
+ | `pnpm auth:db:migrate` | Run migrations for the shared customer auth database |
96
94
  | `pnpm inngest:dev` | Start the local Inngest dev server for this app |
97
95
  | `pnpm db:generate` | Generate Drizzle migrations |
98
96
  | `pnpm db:migrate` | Run database migrations |
99
- | `pnpm db:setup` | Create database and user |
100
- | `pnpm db:setup-and-migrate` | Setup and migrate database |
101
- | `pnpm db:studio` | Setup/migrate the database, then open Drizzle Studio |
97
+ | `pnpm db:studio` | Run migrations, then open Drizzle Studio |
102
98
  | `pnpm db:seed` | Seed default shared-auth dev users and local access grants |
103
99
  | `pnpm test:e2e:install` | Install the Chromium browser used by Playwright |
104
100
  | `pnpm test:e2e` | Run Playwright e2e tests after local setup |
@@ -153,15 +149,19 @@ logger.error(
153
149
 
154
150
  ## Authentication
155
151
 
156
- This app consumes the customer monorepo's shared [Better Auth](https://better-auth.com) package, `@__REPO_NAME__/auth`. The app still serves local development auth routes, but the users, sessions, accounts, groups, and group memberships live in the shared customer auth database.
152
+ This app consumes the customer monorepo's shared [Better Auth](https://better-auth.com) package, `@__REPO_NAME__/auth`. The app still serves local development auth routes, but the users, sessions, accounts, groups, and group memberships live in the shared customer auth database. Deployed apps should receive that shared database through `AUTH_DATABASE_URL` from the monorepo auth Secret; `DATABASE_URL` is reserved for this app's own database.
157
153
 
158
- Required environment variables:
154
+ Required auth environment variables:
159
155
 
160
156
  ```bash
161
157
  BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32
162
158
  BETTER_AUTH_URL=http://localhost:3000
163
159
  ```
164
160
 
161
+ Remote deployments should also set `AUTH_DATABASE_URL` from the shared auth
162
+ database Secret. Local development can omit it and use the root-created local
163
+ `auth` database.
164
+
165
165
  To create dev users:
166
166
  ```bash
167
167
  pnpm db:seed
@@ -184,17 +184,17 @@ App permissions are authored in `src/access/schema.zed`; `src/access/access.mani
184
184
  | `NODE_ENV` | Environment mode | `development` |
185
185
  | `APP_BASE_URL` | Base URL for the app | - |
186
186
 
187
- ### Database
187
+ ### App Database
188
188
 
189
189
  | Variable | Description | Default |
190
190
  |----------|-------------|---------|
191
- | `DATABASE_HOST` | PostgreSQL host | `localhost` |
192
- | `DATABASE_PORT` | PostgreSQL port | `5434` |
193
- | `DATABASE_USERNAME` | Database user | `postgres` |
194
- | `DATABASE_PASSWORD` | Database password | `postgres` |
195
- | `DATABASE_NAME` | Database name | `__DB_NAME__` |
196
- | `DATABASE_SCHEMA` | Optional Postgres schema/search path | - |
197
- | `DATABASE_USE_SSL` | Enable SSL | `false` |
191
+ | `DATABASE_URL` | App PostgreSQL connection URL | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` |
192
+
193
+ ### Shared Auth Database
194
+
195
+ | Variable | Description | Default |
196
+ |----------|-------------|---------|
197
+ | `AUTH_DATABASE_URL` | Shared auth database URL from the monorepo auth Secret | - |
198
198
 
199
199
  ### Security
200
200
 
@@ -231,13 +231,13 @@ node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
231
231
  | `LANGFUSE_PUBLIC_KEY` | Langfuse public key |
232
232
  | `LANGFUSE_SECRET_KEY` | Langfuse secret key |
233
233
 
234
- `deploy:percepta-test` sets the shared Langfuse URL and inherits the demo project keys from the `demos-commons` Ryvn variable group.
234
+ Set these values through the target deployment platform when Langfuse is enabled.
235
235
 
236
236
  ### LLM Providers
237
237
 
238
238
  | Variable | Description |
239
239
  |----------|-------------|
240
- | `ANTHROPIC_API_KEY` | Anthropic API key. Inherited from `demos-commons` for `percepta-test` deploys |
240
+ | `ANTHROPIC_API_KEY` | Anthropic API key |
241
241
  | `OPENAI_API_KEY` | OpenAI API key for local or non-demo deployments |
242
242
  | `LLM_PROVIDER` | Optional provider override: `anthropic` or `openai` |
243
243
  | `LLM_MODEL` | Optional model override |
@@ -254,7 +254,7 @@ const result = await LLMService.create().generateText({
254
254
  });
255
255
  ```
256
256
 
257
- For local development, `pnpm dev` also loads `~/.config/percepta/create.env` when that file exists. Production deploys do not use that local file; they inherit provider keys from the target Ryvn environment.
257
+ For local development, `pnpm dev` also loads `~/.config/percepta/create.env` when that file exists. Remote deployments do not use that local file; they should receive provider keys from the target platform.
258
258
 
259
259
  ### OpenTelemetry / LGTM
260
260
 
@@ -270,7 +270,7 @@ For local development, `pnpm dev` also loads `~/.config/percepta/create.env` whe
270
270
  | `OTEL_METRICS_EXPORTER` | Set to `otlp` to export server metrics |
271
271
  | `OTEL_METRIC_EXPORT_INTERVAL` | Metrics export interval in milliseconds |
272
272
 
273
- `deploy:percepta-test` configures these for the existing percepta-test OTEL collector and LGTM stack. Application logs are written to stdout and collected by the platform collector.
273
+ Configure these values through the target deployment platform. Application logs are written to stdout so the platform collector can collect them.
274
274
 
275
275
  ## Local AWS Development
276
276
 
@@ -284,17 +284,13 @@ This application uses the default AWS SDK credential provider chain:
284
284
 
285
285
  2. **AWS Credentials File**: Run `aws configure` or `aws sso login`
286
286
 
287
- ## Infrastructure (Terraform)
288
-
289
- The `terraform/` directory contains modules for AWS infrastructure:
290
-
291
- - **RDS**: PostgreSQL database
292
- - **S3**: File storage with logging
293
- - **Networking**: VPC endpoints
294
- - **Secrets**: AWS Secrets Manager
295
- - **IAM**: Roles and policies
287
+ ## Infrastructure
296
288
 
297
- See `terraform/README.md` for deployment instructions.
289
+ The app template does not own cloud infrastructure. The infra repo owns base
290
+ environment infrastructure, customer monorepo infrastructure, app database
291
+ registration, observability, and shared services. App deployments expect the
292
+ customer monorepo infrastructure blueprint to create an app-scoped Kubernetes
293
+ Secret named `__APP_NAME__-postgresql`.
298
294
 
299
295
  ## Learn More
300
296
 
@@ -38,11 +38,9 @@ export * from "./documents";
38
38
  pnpm db:generate
39
39
  ```
40
40
 
41
- This creates a new SQL migration file and normalizes generated foreign key
42
- references so they stay schema-relative when `DATABASE_SCHEMA` is set in
43
- `percepta-test`. **Review the generated SQL** Drizzle generates it
44
- automatically but you should verify it's correct, especially for new foreign
45
- keys and destructive changes.
41
+ This creates a new SQL migration file and normalizes generated SQL. **Review
42
+ the generated SQL** Drizzle generates it automatically but you should verify
43
+ it's correct, especially for new foreign keys and destructive changes.
46
44
 
47
45
  ### 4. Apply the migration
48
46
 
@@ -95,21 +93,25 @@ If `createTransaction` is called while already inside a transaction, it reuses t
95
93
 
96
94
  ## Running PostgreSQL Locally
97
95
 
98
- ### Start with Docker Compose
96
+ ### Start with root setup
99
97
 
100
98
  ```bash
101
- pnpm docker:up
99
+ pnpm run setup
102
100
  ```
103
101
 
104
- This starts PostgreSQL 16 on port **5434** (not the default 5432, to avoid conflicts).
102
+ This delegates to the monorepo root. Root setup starts PostgreSQL 16 on port
103
+ **5434** and SpiceDB on **50051**, creates the shared auth database plus each
104
+ app database discovered from `packages/*/.env.local`, then runs migrations and
105
+ seeds.
105
106
 
106
- ### Create the database and run migrations
107
+ ### Run migrations
107
108
 
108
109
  ```bash
109
- pnpm db:setup-and-migrate
110
+ pnpm db:migrate
110
111
  ```
111
112
 
112
- This creates the database if it doesn't exist and applies all pending migrations.
113
+ This applies all pending migrations. Database creation is owned by the local
114
+ infrastructure setup, not by the app migration command.
113
115
 
114
116
  ### Inspect the database
115
117
 
@@ -119,27 +121,25 @@ pnpm db:studio
119
121
 
120
122
  Opens Drizzle Studio in the browser — a visual database explorer.
121
123
 
122
- ### Stop PostgreSQL
124
+ ### Stop local services
123
125
 
124
126
  ```bash
125
- pnpm docker:down
127
+ pnpm --dir ../.. run docker:down
126
128
  ```
127
129
 
128
130
  ## Environment Variables
129
131
 
130
132
  | Variable | Default | Description |
131
133
  |----------|---------|-------------|
132
- | `DATABASE_HOST` | `localhost` | PostgreSQL host |
133
- | `DATABASE_PORT` | `5434` | PostgreSQL port |
134
- | `DATABASE_USERNAME` | `postgres` | Database user |
135
- | `DATABASE_PASSWORD` | `postgres` | Database password |
136
- | `DATABASE_NAME` | `__DB_NAME__` | Database name |
137
- | `DATABASE_SCHEMA` | - | Optional Postgres schema/search path |
138
- | `DATABASE_USE_SSL` | `false` | Enable SSL connections |
134
+ | `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5434/__DB_NAME__` | App PostgreSQL connection URL |
135
+
136
+ Shared auth data belongs to the customer monorepo auth package. Deployed apps
137
+ should receive `AUTH_DATABASE_URL` from the monorepo auth Secret; do not reuse
138
+ `DATABASE_URL` for auth.
139
139
 
140
140
  ## Key Concepts
141
141
 
142
142
  - **Schemas are TypeScript, migrations are SQL.** You define tables in TS, then `pnpm db:generate` creates the SQL diff. Never hand-write migration SQL.
143
143
  - **DatabaseService is a singleton.** Call `DatabaseService.create()` anywhere — it always returns the same instance with the same connection pool.
144
144
  - **Transaction propagation is automatic.** Code inside `createTransaction` gets a transaction; code outside gets the raw connection. `getDatabase()` returns whichever is active.
145
- - **Port 5434.** The local Docker Compose uses port 5434 to avoid conflicting with any system PostgreSQL on 5432.
145
+ - **Root-owned local infra.** The monorepo root Docker Compose uses port 5434 to avoid conflicting with any system PostgreSQL on 5432.
@@ -60,11 +60,11 @@ This is a singleton — `create()` returns the same instance every time. It grac
60
60
 
61
61
  ## Getting Langfuse Keys
62
62
 
63
- ### Percepta's Internal Instance
63
+ ### Shared Instances
64
64
 
65
- Percepta runs a shared Langfuse instance. For `percepta-test` deploys, the generated Ryvn installation uses the shared demo project by inheriting `LANGFUSE_PUBLIC_KEY` and sensitive `LANGFUSE_SECRET_KEY` from the `demos-commons` variable group.
66
-
67
- For non-demo environments, verify the target Ryvn environment has a Langfuse installation or shared Langfuse project, then store the project keys in an environment-scoped variable group or installation secrets.
65
+ If the target stack provides a shared Langfuse instance, store
66
+ `LANGFUSE_PUBLIC_KEY` and sensitive `LANGFUSE_SECRET_KEY` through that
67
+ platform's environment-scoped variable or secret mechanism.
68
68
 
69
69
  ### Self-Hosted / Langfuse Cloud
70
70
 
@@ -74,17 +74,17 @@ For external projects, you can:
74
74
 
75
75
  ## Running Locally
76
76
 
77
- ### Option 1: Use Percepta's Langfuse (recommended)
77
+ ### Option 1: Use a Shared Langfuse Instance
78
78
 
79
79
  Set the keys in `.env.local` if you want local traces to go to the shared instance:
80
80
 
81
81
  ```bash
82
- LANGFUSE_BASE_URL=https://langfuse.percepta-test.aitco.dev
82
+ LANGFUSE_BASE_URL=https://langfuse.example.com
83
83
  LANGFUSE_PUBLIC_KEY=pk-lf-...
84
84
  LANGFUSE_SECRET_KEY=sk-lf-...
85
85
  ```
86
86
 
87
- Traces from local dev appear in the shared dashboard under your project.
87
+ Traces from local dev appear in the configured shared dashboard under your project.
88
88
 
89
89
  ### Option 2: Run Langfuse locally
90
90
 
@@ -40,7 +40,9 @@ The shared `@percepta/ai` provider helper chooses a provider at call time:
40
40
 
41
41
  ## Deployment
42
42
 
43
- For `percepta-test`, `deploy:percepta-test` attaches the `demos-commons` Ryvn variable group. That group provides the shared `ANTHROPIC_API_KEY` and Langfuse project keys, so generated demo apps do not need per-app LLM secrets.
43
+ Provider keys and Langfuse credentials should come from the target deployment
44
+ platform. Do not commit shared provider keys or generated environment-specific
45
+ secret files to the app repo.
44
46
 
45
47
  ## Local Development
46
48
 
@@ -54,6 +56,6 @@ ANTHROPIC_API_KEY=sk-ant-...
54
56
 
55
57
  ## Observability
56
58
 
57
- `LLMService` enables AI SDK telemetry by default and attaches provider/model metadata to each call. When `LANGFUSE_*` values are configured, the template's OpenTelemetry bootstrap sends LLM spans to Langfuse. In `percepta-test`, those values are inherited from the environment.
59
+ `LLMService` enables AI SDK telemetry by default and attaches provider/model metadata to each call. When `LANGFUSE_*` values are configured, the template's OpenTelemetry bootstrap sends LLM spans to Langfuse.
58
60
 
59
61
  Use a stable `telemetryFunctionId` for every meaningful LLM operation, such as `extract-invoice-fields` or `draft-member-message`.