@percepta/create 3.6.1 → 3.6.3
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 +37 -6
- package/dist/{git-ops-C2CIjuce.js → git-ops-BD7JNnal.js} +1 -1
- package/dist/{git-ops-C2CIjuce.js.map → git-ops-BD7JNnal.js.map} +1 -1
- package/dist/github-RCIMUq70.js +131 -0
- package/dist/github-RCIMUq70.js.map +1 -0
- package/dist/index.js +63 -122
- package/dist/index.js.map +1 -1
- package/dist/{init-sI9aIrkU.js → init-COp0nGdk.js} +4 -2
- package/dist/{init-sI9aIrkU.js.map → init-COp0nGdk.js.map} +1 -1
- package/dist/manifest-CqIDnbgs.js +58 -0
- package/dist/manifest-CqIDnbgs.js.map +1 -0
- package/dist/register-app-C7ZBpAaZ.js +103 -0
- package/dist/register-app-C7ZBpAaZ.js.map +1 -0
- package/dist/register-os-blueprint-DGjBUZYa.js +90 -0
- package/dist/register-os-blueprint-DGjBUZYa.js.map +1 -0
- package/dist/{status-CKe4aKso.js → status-BXYaQ4a2.js} +3 -3
- package/dist/{status-CKe4aKso.js.map → status-BXYaQ4a2.js.map} +1 -1
- package/dist/{sync-D1vkoofl.js → sync-BayU4w1j.js} +3 -3
- package/dist/{sync-D1vkoofl.js.map → sync-BayU4w1j.js.map} +1 -1
- package/dist/template-versions-CEIP9vhl.js +35 -0
- package/dist/template-versions-CEIP9vhl.js.map +1 -0
- package/dist/{upstream-gUHLWSR1.js → upstream-CZEzLrS4.js} +3 -3
- package/dist/{upstream-gUHLWSR1.js.map → upstream-CZEzLrS4.js.map} +1 -1
- package/dist/validate-dssldJAj.js +14 -0
- package/dist/validate-dssldJAj.js.map +1 -0
- package/package.json +1 -1
- package/template-versions.json +2 -2
- package/templates/infra/os.blueprint.yaml.template +138 -0
- package/templates/monorepo/README.md +41 -3
- package/templates/monorepo/auth/README.md +6 -3
- package/templates/monorepo/auth/package.json +2 -4
- package/templates/monorepo/auth/src/config/database.ts +1 -1
- package/templates/{webapp → monorepo}/docker-compose.yml +2 -2
- package/templates/monorepo/package.json.template +5 -2
- package/templates/monorepo/pnpm-workspace.yaml +4 -0
- package/templates/monorepo/scripts/setup-local-databases.mjs +183 -0
- package/templates/webapp/AGENTS.md +13 -20
- package/templates/webapp/README.md +32 -36
- package/templates/webapp/agent-skills/database.md +21 -21
- package/templates/webapp/agent-skills/langfuse.md +7 -7
- package/templates/webapp/agent-skills/llm.md +4 -2
- package/templates/webapp/agent-skills/oneshot.md +7 -6
- package/templates/webapp/agent-skills/ryvn.md +12 -16
- package/templates/webapp/deploy/README.md +10 -51
- package/templates/webapp/drizzle.config.ts +2 -23
- package/templates/webapp/env.example.template +8 -14
- package/templates/webapp/package.json.template +8 -15
- package/templates/webapp/scripts/start.sh +12 -16
- package/templates/webapp/src/config/getEnvConfig.ts +4 -10
- package/templates/webapp/src/drizzle/db.ts +6 -21
- package/templates/webapp/src/startup-checks.ts +28 -7
- package/templates/monorepo/auth/scripts/setup-database.ts +0 -11
- package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +0 -92
- package/templates/webapp/agent-skills/deploy.md +0 -92
- package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +0 -10
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +0 -11
- package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +0 -154
- package/templates/webapp/terraform/README.md +0 -147
- package/templates/webapp/terraform/deploy.sh +0 -97
- package/templates/webapp/terraform/main.tf +0 -101
- package/templates/webapp/terraform/modules/cloudtrail/main.tf +0 -27
- package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +0 -10
- package/templates/webapp/terraform/modules/cloudtrail/variables.tf +0 -15
- package/templates/webapp/terraform/modules/networking/main.tf +0 -118
- package/templates/webapp/terraform/modules/networking/outputs.tf +0 -38
- package/templates/webapp/terraform/modules/networking/variables.tf +0 -24
- package/templates/webapp/terraform/modules/rds/main.tf +0 -227
- package/templates/webapp/terraform/modules/rds/outputs.tf +0 -73
- package/templates/webapp/terraform/modules/rds/variables.tf +0 -61
- package/templates/webapp/terraform/modules/s3-logging/main.tf +0 -148
- package/templates/webapp/terraform/modules/s3-logging/outputs.tf +0 -10
- package/templates/webapp/terraform/modules/s3-logging/variables.tf +0 -16
- package/templates/webapp/terraform/modules/secrets/main.tf +0 -39
- package/templates/webapp/terraform/modules/secrets/outputs.tf +0 -9
- package/templates/webapp/terraform/modules/secrets/variables.tf +0 -51
- package/templates/webapp/terraform/outputs.tf +0 -102
- package/templates/webapp/terraform/providers.tf +0 -32
- package/templates/webapp/terraform/schema/main.tf +0 -4
- package/templates/webapp/terraform/schema/outputs.tf +0 -9
- package/templates/webapp/terraform/schema/variables.tf +0 -19
- package/templates/webapp/terraform/schema/versions.tf +0 -38
- package/templates/webapp/terraform/terraform.tfvars.example +0 -65
- package/templates/webapp/terraform/variables.tf +0 -129
|
@@ -11,7 +11,7 @@ pnpm install
|
|
|
11
11
|
# Run development mode for all packages
|
|
12
12
|
pnpm dev
|
|
13
13
|
|
|
14
|
-
# Set up local services, access-control topology,
|
|
14
|
+
# Set up root local services, databases, access-control topology, and seed users
|
|
15
15
|
pnpm run setup
|
|
16
16
|
|
|
17
17
|
# Add another Mosaic package using this workspace's pinned generator
|
|
@@ -35,10 +35,23 @@ pnpm lint
|
|
|
35
35
|
```
|
|
36
36
|
access/ # Customer-level SpiceDB fixtures and generated merge artifacts
|
|
37
37
|
auth/ # Shared Better Auth users/groups package for this customer
|
|
38
|
+
docker-compose.yml # Root-owned local PostgreSQL and SpiceDB services
|
|
39
|
+
scripts/ # Root-owned local infrastructure helpers
|
|
38
40
|
packages/
|
|
39
41
|
└── your-package/ # Application and library packages
|
|
40
42
|
```
|
|
41
43
|
|
|
44
|
+
## Local Infrastructure
|
|
45
|
+
|
|
46
|
+
Local development infrastructure is owned by the monorepo root. `pnpm run setup`
|
|
47
|
+
starts the root Docker Compose services, creates the shared `auth` database,
|
|
48
|
+
discovers app `DATABASE_URL`s from `packages/*/.env.local`, creates those app
|
|
49
|
+
databases, runs auth and app migrations, and seeds local users/grants.
|
|
50
|
+
|
|
51
|
+
When you add another webapp with `pnpm mosaic add webapp my-app`, the generated
|
|
52
|
+
app gets its own local `DATABASE_URL`. Re-running `pnpm run setup` creates that
|
|
53
|
+
database before running migrations.
|
|
54
|
+
|
|
42
55
|
## Access Control
|
|
43
56
|
|
|
44
57
|
Application builders define app-local Zed schemas in each package's
|
|
@@ -56,7 +69,7 @@ PR CI merges the customer schema and runs static schema/manifest validation.
|
|
|
56
69
|
`access:apply` is reserved for trusted deploy jobs and should run once per
|
|
57
70
|
target environment with that environment's SpiceDB credentials.
|
|
58
71
|
|
|
59
|
-
For local development, `pnpm run setup` seeds
|
|
72
|
+
For local development, `pnpm run setup` seeds each generated app's default dev
|
|
60
73
|
users and access grants. For additional local grants, copy the example fixture
|
|
61
74
|
files in `access/`, fill in customer-global user/group IDs from `auth/`, then
|
|
62
75
|
run:
|
|
@@ -83,4 +96,29 @@ pnpm mosaic add webapp my-app
|
|
|
83
96
|
pnpm mosaic add library my-lib
|
|
84
97
|
```
|
|
85
98
|
|
|
86
|
-
The compatibility metadata
|
|
99
|
+
The customer slug and compatibility metadata live in `.mosaic-workspace.json`.
|
|
100
|
+
|
|
101
|
+
## Registering the customer OS blueprint
|
|
102
|
+
|
|
103
|
+
To register this customer monorepo's OS blueprint in `Percepta-Core/infra`,
|
|
104
|
+
run:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pnpm mosaic infra register-os-blueprint
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The command reads the customer slug from `.mosaic-workspace.json`, opens or
|
|
111
|
+
updates an infra PR that writes
|
|
112
|
+
`ryvn/definitions/<customer>/blueprints/<customer>-os.blueprint.yaml`, and does
|
|
113
|
+
not create environment installations. It authenticates with `GITHUB_TOKEN`,
|
|
114
|
+
`GH_TOKEN`, or the GitHub CLI's `gh auth token`.
|
|
115
|
+
|
|
116
|
+
After adding a webapp, register its app database in the customer OS blueprint:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pnpm mosaic infra register-app my-app
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This opens or updates an infra PR that adds `my-app: {}` to the customer OS
|
|
123
|
+
blueprint's `app_databases` default. Merge the OS blueprint PR before
|
|
124
|
+
registering apps.
|
|
@@ -22,6 +22,9 @@ lands.
|
|
|
22
22
|
|
|
23
23
|
## Database
|
|
24
24
|
|
|
25
|
-
By default this package uses
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
By default this package uses a shared `auth` database with local PostgreSQL
|
|
26
|
+
defaults. Remote deployments should inject `AUTH_DATABASE_URL` from the
|
|
27
|
+
monorepo auth database Secret.
|
|
28
|
+
|
|
29
|
+
Do not use app `DATABASE_URL` for this package. That belongs to each webapp's
|
|
30
|
+
own database.
|
|
@@ -13,12 +13,10 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc -p tsconfig.json",
|
|
15
15
|
"db:generate": "drizzle-kit generate",
|
|
16
|
-
"db:migrate": "drizzle-kit migrate"
|
|
17
|
-
"db:setup": "tsx ./scripts/setup-database.ts",
|
|
18
|
-
"db:setup-and-migrate": "pnpm db:setup && pnpm db:migrate"
|
|
16
|
+
"db:migrate": "drizzle-kit migrate"
|
|
19
17
|
},
|
|
20
18
|
"dependencies": {
|
|
21
|
-
"@percepta/auth": "0.1.
|
|
19
|
+
"@percepta/auth": "0.1.4",
|
|
22
20
|
"drizzle-orm": "^0.45.2"
|
|
23
21
|
},
|
|
24
22
|
"devDependencies": {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
export type { AuthDatabaseConfig } from "@percepta/auth";
|
|
8
8
|
|
|
9
9
|
export function getAuthDatabaseConfig(): AuthDatabaseConfig {
|
|
10
|
-
return readAuthDatabaseConfig({ defaultDatabaseName: "
|
|
10
|
+
return readAuthDatabaseConfig({ defaultDatabaseName: "auth" });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function getAuthDatabaseConnectionString(): string {
|
|
@@ -22,11 +22,11 @@ services:
|
|
|
22
22
|
environment:
|
|
23
23
|
POSTGRES_USER: postgres
|
|
24
24
|
POSTGRES_PASSWORD: postgres
|
|
25
|
-
POSTGRES_DB:
|
|
25
|
+
POSTGRES_DB: postgres
|
|
26
26
|
volumes:
|
|
27
27
|
- postgres_data:/var/lib/postgresql/data
|
|
28
28
|
healthcheck:
|
|
29
|
-
test: ["CMD-SHELL", "pg_isready -U postgres -d
|
|
29
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
|
30
30
|
interval: 5s
|
|
31
31
|
timeout: 5s
|
|
32
32
|
retries: 5
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"preinstall": "npx only-allow pnpm",
|
|
8
8
|
"mosaic": "pnpm dlx __CREATE_PACKAGE__@__CREATE_VERSION__",
|
|
9
|
-
"setup": "pnpm
|
|
9
|
+
"setup": "pnpm run docker:up && pnpm run db:setup-local && pnpm run auth:db:migrate && pnpm run access:apply-local && pnpm -r --filter './packages/*' --if-present run db:migrate && pnpm -r --filter './packages/*' --if-present run db:seed",
|
|
10
|
+
"docker:up": "docker compose up -d --wait",
|
|
11
|
+
"docker:down": "docker compose down",
|
|
12
|
+
"db:setup-local": "node scripts/setup-local-databases.mjs",
|
|
10
13
|
"dev": "pnpm -r --parallel --if-present run dev",
|
|
11
14
|
"build": "pnpm -r --if-present run build",
|
|
12
15
|
"clean": "pnpm -r --if-present run clean",
|
|
@@ -22,7 +25,7 @@
|
|
|
22
25
|
"access:bootstrap-customer-admin": "percepta-access-control bootstrap-customer-admin",
|
|
23
26
|
"access:apply-bootstrap-grants": "percepta-access-control apply-bootstrap-grants --fixture access/bootstrap-grants.yaml",
|
|
24
27
|
"access:reconcile": "percepta-access-control reconcile --input access/reconcile.yaml",
|
|
25
|
-
"auth:db:
|
|
28
|
+
"auth:db:migrate": "pnpm --dir auth run db:migrate"
|
|
26
29
|
},
|
|
27
30
|
"engines": {
|
|
28
31
|
"node": ">=20",
|
|
@@ -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
|
+
}
|
|
@@ -8,20 +8,18 @@ Next.js 15 full-stack application scaffolded from the Mosaic webapp template via
|
|
|
8
8
|
- `pnpm build` — production build
|
|
9
9
|
- `pnpm lint` — run ESLint
|
|
10
10
|
- `pnpm test` — run Vitest tests
|
|
11
|
-
- `pnpm docker:up` / `pnpm docker:down` — start PostgreSQL and SpiceDB, waiting for health, or stop them
|
|
12
11
|
- `pnpm access:validate` — validate access schema and manifest
|
|
13
12
|
- `pnpm access:apply-local` — apply merged customer access schema to local SpiceDB
|
|
14
|
-
- `pnpm auth:db:
|
|
13
|
+
- `pnpm auth:db:migrate` — run shared customer auth database migrations
|
|
15
14
|
- `pnpm inngest:dev` — start local Inngest dev server when working on background jobs
|
|
16
15
|
- `pnpm db:generate` — generate Drizzle migrations
|
|
17
16
|
- `pnpm db:migrate` — apply migrations
|
|
18
|
-
- `pnpm db:
|
|
19
|
-
- `pnpm db:studio` — setup/migrate the database, then open Drizzle Studio
|
|
17
|
+
- `pnpm db:studio` — run migrations, then open Drizzle Studio
|
|
20
18
|
- `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
19
|
|
|
22
20
|
**Package manager**: Always use `pnpm`, never `npm` or `yarn`.
|
|
23
21
|
|
|
24
|
-
Local
|
|
22
|
+
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
23
|
If local LLM calls are needed, `pnpm dev` loads shared provider keys from `~/.config/percepta/create.env` when it exists.
|
|
26
24
|
|
|
27
25
|
## Code Style
|
|
@@ -58,13 +56,9 @@ src/ # Application source
|
|
|
58
56
|
│ └── observability/ # OpenTelemetry setup
|
|
59
57
|
└── utils/ # Helpers (cn, pathEncryption, etc.)
|
|
60
58
|
|
|
61
|
-
deploy/ #
|
|
59
|
+
deploy/ # Optional release metadata
|
|
62
60
|
└── ryvn/
|
|
63
|
-
|
|
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
|
|
61
|
+
└── __APP_NAME__.service.yaml # Ryvn web service
|
|
68
62
|
```
|
|
69
63
|
|
|
70
64
|
## @percepta Packages
|
|
@@ -200,8 +194,7 @@ Detailed how-to guides for each major stack component. Read the relevant guide w
|
|
|
200
194
|
| LLM Observability (Langfuse) | [agent-skills/langfuse.md](agent-skills/langfuse.md) | App uses LLMs and needs trace/eval monitoring |
|
|
201
195
|
| Database (Drizzle) | [agent-skills/database.md](agent-skills/database.md) | Adding tables, writing migrations, querying data |
|
|
202
196
|
| 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
|
|
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 |
|
|
197
|
+
| Deployment (Ryvn) | [agent-skills/ryvn.md](agent-skills/ryvn.md) | Ryvn service release notes; environment installs live in infra |
|
|
205
198
|
| Build App (Oneshot) | [agent-skills/oneshot.md](agent-skills/oneshot.md) | Building a complete app from requirements end-to-end |
|
|
206
199
|
|
|
207
200
|
## Key Patterns
|
|
@@ -221,14 +214,14 @@ Client-side usage via `src/lib/trpc.ts`.
|
|
|
221
214
|
|
|
222
215
|
### Authentication
|
|
223
216
|
|
|
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.
|
|
217
|
+
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
218
|
|
|
226
219
|
- **Server-side**: `auth.api.getSession({ headers: await headers() })` — get session in server components or tRPC context
|
|
227
220
|
- **Client-side**: `authClient.useSession()` — React hook from `src/lib/auth-client.ts`
|
|
228
221
|
- **Sign in**: `authClient.signIn.email({ email, password })` — client-side
|
|
229
222
|
- **Sign out**: `authClient.signOut()` — client-side
|
|
230
223
|
- **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`)
|
|
224
|
+
- **Env vars**: `BETTER_AUTH_SECRET` (required), `BETTER_AUTH_URL` (defaults to `http://localhost:3000`), `AUTH_DATABASE_URL` for deployed shared auth DB wiring
|
|
232
225
|
|
|
233
226
|
### Background Jobs
|
|
234
227
|
|
|
@@ -248,11 +241,11 @@ OpenTelemetry initialized in `src/instrumentation.ts`. Server traces and metrics
|
|
|
248
241
|
|
|
249
242
|
## Deployment
|
|
250
243
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
244
|
+
Blueberry does not generate environment-specific deployments. Customer and
|
|
245
|
+
stack-specific infrastructure, app database registration, runtime services,
|
|
246
|
+
ingress, secrets, and service installations belong in the infra repo. The
|
|
247
|
+
release CI/CD workflow is included under `.github/workflows/` for stacks that
|
|
248
|
+
use Ryvn releases.
|
|
256
249
|
|
|
257
250
|
## Template Sync
|
|
258
251
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# __APP_TITLE__
|
|
2
2
|
|
|
3
|
-
A production-ready Next.js application with authentication, database, logging, background jobs
|
|
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
|
|
|
@@ -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
|
-
- **
|
|
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
|
|
33
|
-
|
|
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
|
|
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
|
|
|
@@ -88,17 +88,13 @@ src/
|
|
|
88
88
|
| `pnpm build` | Build for production |
|
|
89
89
|
| `pnpm start` | Start production server |
|
|
90
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 |
|
|
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:
|
|
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:
|
|
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
|
-
| `
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
| `
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|