@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,121 @@
|
|
|
1
|
+
kind: ServiceInstallation
|
|
2
|
+
metadata:
|
|
3
|
+
name: __APP_NAME__
|
|
4
|
+
spec:
|
|
5
|
+
service: __APP_NAME__
|
|
6
|
+
environment: percepta-test
|
|
7
|
+
config: |
|
|
8
|
+
replicaCount: 1
|
|
9
|
+
|
|
10
|
+
service:
|
|
11
|
+
port: 3000
|
|
12
|
+
|
|
13
|
+
livenessEnabled: true
|
|
14
|
+
readinessEnabled: true
|
|
15
|
+
|
|
16
|
+
resources:
|
|
17
|
+
requests:
|
|
18
|
+
cpu: "100m"
|
|
19
|
+
memory: "256Mi"
|
|
20
|
+
limits:
|
|
21
|
+
cpu: "500m"
|
|
22
|
+
memory: "512Mi"
|
|
23
|
+
|
|
24
|
+
ingress:
|
|
25
|
+
enabled: true
|
|
26
|
+
className: external-nginx
|
|
27
|
+
annotations:
|
|
28
|
+
cert-manager.io/cluster-issuer: external-issuer
|
|
29
|
+
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
30
|
+
hosts:
|
|
31
|
+
- host: __APP_NAME__.percepta-test.aitco.dev
|
|
32
|
+
paths:
|
|
33
|
+
- path: /
|
|
34
|
+
pathType: Prefix
|
|
35
|
+
tls:
|
|
36
|
+
- secretName: __APP_NAME__-tls
|
|
37
|
+
hosts:
|
|
38
|
+
- __APP_NAME__.percepta-test.aitco.dev
|
|
39
|
+
|
|
40
|
+
env:
|
|
41
|
+
# Database (shared percepta-test RDS, provisioned by percepta-internal-terraform)
|
|
42
|
+
- name: DATABASE_HOST
|
|
43
|
+
valueFrom:
|
|
44
|
+
secretKeyRef:
|
|
45
|
+
name: "{{ .ryvn.installations.percepta_internal_terraform.outputs.postgresql_secret_name }}"
|
|
46
|
+
key: host
|
|
47
|
+
- name: DATABASE_PORT
|
|
48
|
+
valueFrom:
|
|
49
|
+
secretKeyRef:
|
|
50
|
+
name: "{{ .ryvn.installations.percepta_internal_terraform.outputs.postgresql_secret_name }}"
|
|
51
|
+
key: port
|
|
52
|
+
- name: DATABASE_USERNAME
|
|
53
|
+
valueFrom:
|
|
54
|
+
secretKeyRef:
|
|
55
|
+
name: "{{ .ryvn.installations.percepta_internal_terraform.outputs.postgresql_secret_name }}"
|
|
56
|
+
key: username
|
|
57
|
+
- name: DATABASE_PASSWORD
|
|
58
|
+
valueFrom:
|
|
59
|
+
secretKeyRef:
|
|
60
|
+
name: "{{ .ryvn.installations.percepta_internal_terraform.outputs.postgresql_secret_name }}"
|
|
61
|
+
key: password
|
|
62
|
+
- name: DATABASE_NAME
|
|
63
|
+
value: "__DB_NAME__"
|
|
64
|
+
- name: DATABASE_USE_SSL
|
|
65
|
+
value: "true"
|
|
66
|
+
|
|
67
|
+
- name: NODE_ENV
|
|
68
|
+
value: "production"
|
|
69
|
+
- name: PORT
|
|
70
|
+
value: "3000"
|
|
71
|
+
env:
|
|
72
|
+
# App identity
|
|
73
|
+
- key: APP_BASE_URL
|
|
74
|
+
value: https://__APP_NAME__.percepta-test.aitco.dev
|
|
75
|
+
- key: BETTER_AUTH_URL
|
|
76
|
+
value: https://__APP_NAME__.percepta-test.aitco.dev
|
|
77
|
+
|
|
78
|
+
# Auth — generated random, set in Ryvn UI (openssl rand -base64 32)
|
|
79
|
+
- key: BETTER_AUTH_SECRET
|
|
80
|
+
isSecret: true
|
|
81
|
+
|
|
82
|
+
# Encryption — generated random, set in Ryvn UI (openssl rand -hex 16)
|
|
83
|
+
- key: ENCRYPTION_SECRET_KEY
|
|
84
|
+
isSecret: true
|
|
85
|
+
|
|
86
|
+
# Inngest (shared percepta-test instance)
|
|
87
|
+
- key: INNGEST_BASE_URL
|
|
88
|
+
value: http://inngest.percepta-test.svc.cluster.local:8288
|
|
89
|
+
- key: INNGEST_EVENT_KEY
|
|
90
|
+
value: c0766e61c95af6afd18911698080b4fea4d311f60b02033d673234ded333ff39
|
|
91
|
+
- key: INNGEST_SIGNING_KEY
|
|
92
|
+
value: signkey-dev-7782b39265d2ca61d083fe1b230b468b10f01434d49486051fd108363da736f2
|
|
93
|
+
- key: INNGEST_APP_URL
|
|
94
|
+
value: http://__APP_NAME__-web-server.percepta-test.svc.cluster.local:3000/api/inngest
|
|
95
|
+
- key: INNGEST_SERVE_HOST
|
|
96
|
+
value: http://__APP_NAME__-web-server.percepta-test.svc.cluster.local:3000/api/inngest
|
|
97
|
+
|
|
98
|
+
# Langfuse (shared percepta-test instance) — secret key set in Ryvn UI from 1Password
|
|
99
|
+
- key: LANGFUSE_BASE_URL
|
|
100
|
+
value: https://percepta-test.aitco.dev/evals
|
|
101
|
+
- key: LANGFUSE_PUBLIC_KEY
|
|
102
|
+
value: pk-lf-cc45f2c9-5286-4966-849b-42fd45059390
|
|
103
|
+
- key: LANGFUSE_SECRET_KEY
|
|
104
|
+
isSecret: true
|
|
105
|
+
|
|
106
|
+
# OpenTelemetry
|
|
107
|
+
- key: OTEL_METRICS_EXPORTER_OTLP_ENDPOINT
|
|
108
|
+
value: http://otel-collector-opentelemetry-collector.percepta-test.svc.cluster.local:4318/v1/metrics
|
|
109
|
+
- key: OTEL_EXPORT_INTERVAL_MS
|
|
110
|
+
value: "60000"
|
|
111
|
+
- key: LOG_LEVEL
|
|
112
|
+
value: debug
|
|
113
|
+
|
|
114
|
+
# Set the values for these in the Ryvn UI after the infra PR merges:
|
|
115
|
+
# BETTER_AUTH_SECRET — generate: openssl rand -base64 32
|
|
116
|
+
# ENCRYPTION_SECRET_KEY — generate: openssl rand -hex 16
|
|
117
|
+
# LANGFUSE_SECRET_KEY — copy from 1Password "Percepta Test Secrets"
|
|
118
|
+
secrets:
|
|
119
|
+
- name: BETTER_AUTH_SECRET
|
|
120
|
+
- name: ENCRYPTION_SECRET_KEY
|
|
121
|
+
- name: LANGFUSE_SECRET_KEY
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16
|
|
4
|
+
ports:
|
|
5
|
+
- "5434:5432"
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_USER: postgres
|
|
8
|
+
POSTGRES_PASSWORD: postgres
|
|
9
|
+
POSTGRES_DB: __DB_NAME__
|
|
10
|
+
volumes:
|
|
11
|
+
- postgres_data:/var/lib/postgresql/data
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d __DB_NAME__"]
|
|
14
|
+
interval: 5s
|
|
15
|
+
timeout: 5s
|
|
16
|
+
retries: 5
|
|
17
|
+
|
|
18
|
+
volumes:
|
|
19
|
+
postgres_data: {}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { loadEnvConfig } from "@next/env";
|
|
2
|
+
import type { Config } from "drizzle-kit";
|
|
3
|
+
import { getEnvConfig } from "./src/config/getEnvConfig";
|
|
4
|
+
|
|
5
|
+
loadEnvConfig(process.cwd());
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
DATABASE_HOST: host,
|
|
9
|
+
DATABASE_PORT: port,
|
|
10
|
+
DATABASE_USERNAME: user,
|
|
11
|
+
DATABASE_PASSWORD: password,
|
|
12
|
+
DATABASE_NAME: database,
|
|
13
|
+
DATABASE_USE_SSL: useSSL,
|
|
14
|
+
} = getEnvConfig();
|
|
15
|
+
|
|
16
|
+
const config: Config = {
|
|
17
|
+
schema: "./src/drizzle/schema",
|
|
18
|
+
out: "./src/drizzle/migrations",
|
|
19
|
+
dialect: "postgresql",
|
|
20
|
+
dbCredentials: {
|
|
21
|
+
host,
|
|
22
|
+
port,
|
|
23
|
+
user,
|
|
24
|
+
password,
|
|
25
|
+
database,
|
|
26
|
+
ssl: useSSL ? "require" : false,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default config;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Application
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
APP_BASE_URL=http://localhost:3000
|
|
4
|
+
|
|
5
|
+
# Database
|
|
6
|
+
DATABASE_HOST=localhost
|
|
7
|
+
DATABASE_PORT=5434
|
|
8
|
+
DATABASE_USERNAME=postgres
|
|
9
|
+
DATABASE_PASSWORD=postgres
|
|
10
|
+
DATABASE_NAME=__DB_NAME__
|
|
11
|
+
DATABASE_USE_SSL=false
|
|
12
|
+
|
|
13
|
+
# Authentication (Better Auth)
|
|
14
|
+
BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32
|
|
15
|
+
BETTER_AUTH_URL=http://localhost:3000
|
|
16
|
+
|
|
17
|
+
# Security
|
|
18
|
+
ENCRYPTION_SECRET_KEY=generate-with-node-e-console-log-require-crypto-randomBytes-16-toString-hex
|
|
19
|
+
|
|
20
|
+
# Inngest (Background Jobs)
|
|
21
|
+
# INNGEST_BASE_URL=
|
|
22
|
+
# INNGEST_SIGNING_KEY=
|
|
23
|
+
# INNGEST_EVENT_KEY=
|
|
24
|
+
|
|
25
|
+
# Grafana Faro (client-side observability)
|
|
26
|
+
# Leave NEXT_PUBLIC_FARO_COLLECTOR_URL empty to disable in local development.
|
|
27
|
+
# In production, point to your Grafana Alloy receiver endpoint.
|
|
28
|
+
NEXT_PUBLIC_FARO_COLLECTOR_URL=
|
|
29
|
+
NEXT_PUBLIC_FARO_APP_NAME=__APP_NAME__
|
|
30
|
+
NEXT_PUBLIC_FARO_APP_VERSION=0.0.0
|
|
31
|
+
NEXT_PUBLIC_FARO_APP_ENVIRONMENT=development
|
|
32
|
+
|
|
33
|
+
# Langfuse (LLM Observability)
|
|
34
|
+
# LANGFUSE_BASE_URL=
|
|
35
|
+
# LANGFUSE_PUBLIC_KEY=
|
|
36
|
+
# LANGFUSE_SECRET_KEY=
|
|
37
|
+
|
|
38
|
+
# AWS (uses default credential chain in development)
|
|
39
|
+
# AWS_REGION=us-east-1
|
|
40
|
+
# AWS_ACCESS_KEY_ID=
|
|
41
|
+
# AWS_SECRET_ACCESS_KEY=
|
|
42
|
+
|
|
43
|
+
# NPM (for installing @percepta packages)
|
|
44
|
+
# NPM_TOKEN=
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import nextPlugin from "@next/eslint-plugin-next";
|
|
3
|
+
import createEslintConfig from "@percepta/build/eslint";
|
|
4
|
+
import nodePlugin from "eslint-plugin-n";
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
...createEslintConfig({
|
|
8
|
+
type: "react",
|
|
9
|
+
dirname: import.meta.dirname,
|
|
10
|
+
}),
|
|
11
|
+
{
|
|
12
|
+
ignores: [
|
|
13
|
+
"pnpm-lock.yaml",
|
|
14
|
+
".next/**",
|
|
15
|
+
"next-env.d.ts",
|
|
16
|
+
"public/**",
|
|
17
|
+
"terraform/**",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
plugins: {
|
|
22
|
+
"@next/next": nextPlugin,
|
|
23
|
+
},
|
|
24
|
+
rules: {
|
|
25
|
+
...nextPlugin.configs.recommended.rules,
|
|
26
|
+
...nextPlugin.configs["core-web-vitals"].rules,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
files: ["src/**/*"],
|
|
31
|
+
rules: {
|
|
32
|
+
"no-console": "error",
|
|
33
|
+
"no-restricted-syntax": [
|
|
34
|
+
"error",
|
|
35
|
+
{
|
|
36
|
+
selector:
|
|
37
|
+
"CallExpression[callee.property.name=/^(debug|info|warn|error)$/][callee.object.type='CallExpression'][callee.object.callee.name='getLogger'] > :nth-child(2):not(Literal)",
|
|
38
|
+
message:
|
|
39
|
+
"Logger message must be a plain string literal, not a variable or template. Use the first parameter (args) for dynamic data.",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
plugins: {
|
|
46
|
+
n: nodePlugin,
|
|
47
|
+
},
|
|
48
|
+
rules: {
|
|
49
|
+
"n/no-process-env": "error",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
!.env.example
|
|
36
|
+
|
|
37
|
+
# vercel
|
|
38
|
+
.vercel
|
|
39
|
+
|
|
40
|
+
# typescript
|
|
41
|
+
*.tsbuildinfo
|
|
42
|
+
next-env.d.ts
|
|
43
|
+
|
|
44
|
+
# terraform
|
|
45
|
+
.terraform
|
|
46
|
+
.terraform.lock.hcl
|
|
47
|
+
|
|
48
|
+
# IDE
|
|
49
|
+
.cursor/**/*.log
|
|
50
|
+
|
|
51
|
+
# mosaic sync (temporary context files)
|
|
52
|
+
.mosaic-sync-context.md
|
|
53
|
+
.mosaic-upstream-context.md
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=18.0.0"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "next dev --turbopack",
|
|
10
|
+
"build": "next build",
|
|
11
|
+
"start": "next start",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"setup": "pnpm docker:up && pnpm db:setup-and-migrate && pnpm db:seed",
|
|
14
|
+
"docker:up": "docker compose up -d",
|
|
15
|
+
"docker:down": "docker compose down",
|
|
16
|
+
"db:generate": "drizzle-kit generate",
|
|
17
|
+
"db:migrate": "tsx ./scripts/migrate.ts",
|
|
18
|
+
"db:setup": "tsx ./scripts/setup-database.ts",
|
|
19
|
+
"db:setup-and-migrate": "pnpm db:setup && pnpm db:migrate",
|
|
20
|
+
"db:setup-readonly": "tsx ./scripts/setup-readonly-user.ts",
|
|
21
|
+
"db:studio": "drizzle-kit studio",
|
|
22
|
+
"db:create-user": "tsx ./scripts/create-user.ts",
|
|
23
|
+
"db:seed": "tsx ./scripts/seed.ts",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@ai-sdk/openai": "^2.0.23",
|
|
29
|
+
"@aws-sdk/client-s3": "^3.888.0",
|
|
30
|
+
"@aws-sdk/client-secrets-manager": "^3.914.0",
|
|
31
|
+
"@aws-sdk/client-sts": "^3.913.0",
|
|
32
|
+
"@aws-sdk/credential-providers": "^3.913.0",
|
|
33
|
+
"@aws-sdk/s3-request-presigner": "^3.891.0",
|
|
34
|
+
"@dnd-kit/core": "^6.3.1",
|
|
35
|
+
"@dnd-kit/modifiers": "^9.0.0",
|
|
36
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
37
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
38
|
+
"@grafana/faro-react": "^1.14.0",
|
|
39
|
+
"@grafana/faro-web-sdk": "^1.14.0",
|
|
40
|
+
"@grafana/faro-web-tracing": "^1.14.0",
|
|
41
|
+
"@hookform/resolvers": "^5.2.2",
|
|
42
|
+
"@inngest/middleware-validation": "^0.0.5",
|
|
43
|
+
"@langfuse/otel": "^4.0.0",
|
|
44
|
+
"@langfuse/tracing": "^4.0.0",
|
|
45
|
+
"@mantine/hooks": "^8.3.1",
|
|
46
|
+
"@next/env": "^15.3.5",
|
|
47
|
+
"@opentelemetry/api": "^1.9.0",
|
|
48
|
+
"@opentelemetry/auto-instrumentations-node": "^0.62.1",
|
|
49
|
+
"@opentelemetry/sdk-node": "^0.203.0",
|
|
50
|
+
"@percepta/design": "^0.1.6",
|
|
51
|
+
"@percepta/components": "^0.0.6",
|
|
52
|
+
"@percepta/logger": "^0.0.4",
|
|
53
|
+
"@percepta/next-utils": "^0.1.0",
|
|
54
|
+
"@percepta/utils": "^0.1.5",
|
|
55
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
56
|
+
"@tanstack/react-query": "^5.81.5",
|
|
57
|
+
"@tanstack/react-virtual": "^3.13.12",
|
|
58
|
+
"@trpc/client": "^11.4.3",
|
|
59
|
+
"@trpc/server": "^11.4.3",
|
|
60
|
+
"@trpc/tanstack-react-query": "^11.4.3",
|
|
61
|
+
"ai": "^5.0.24",
|
|
62
|
+
"better-auth": "^1.6.4",
|
|
63
|
+
"clsx": "^2.1.1",
|
|
64
|
+
"date-fns": "^4.1.0",
|
|
65
|
+
"drizzle-orm": "^0.44.2",
|
|
66
|
+
"drizzle-zod": "^0.8.3",
|
|
67
|
+
"formidable": "^3.5.4",
|
|
68
|
+
"he": "^1.2.0",
|
|
69
|
+
"import-in-the-middle": "^1.14.2",
|
|
70
|
+
"inngest": "^3.44.3",
|
|
71
|
+
"keyed-type-union": "^0.1.0",
|
|
72
|
+
"langfuse": "^3.38.6",
|
|
73
|
+
"lodash-es": "^4.17.21",
|
|
74
|
+
"lucide-react": "^0.542.0",
|
|
75
|
+
"mime-types": "^3.0.1",
|
|
76
|
+
"next": "^15.3.5",
|
|
77
|
+
"numeral": "^2.0.6",
|
|
78
|
+
"pg": "^8.16.3",
|
|
79
|
+
"pino": "^10.1.0",
|
|
80
|
+
"pino-pretty": "^13.1.3",
|
|
81
|
+
"pluralize": "^8.0.0",
|
|
82
|
+
"react": "^19.0.0",
|
|
83
|
+
"react-dom": "^19.0.0",
|
|
84
|
+
"react-hook-form": "^7.62.0",
|
|
85
|
+
"react-markdown": "^10.1.0",
|
|
86
|
+
"remark-gfm": "^4.0.1",
|
|
87
|
+
"require-in-the-middle": "^7.5.2",
|
|
88
|
+
"scroll-into-view-if-needed": "^3.1.0",
|
|
89
|
+
"sonner": "^2.0.7",
|
|
90
|
+
"superjson": "^2.2.2",
|
|
91
|
+
"tailwind-merge": "^3.0.2",
|
|
92
|
+
"tsx": "^4.20.3",
|
|
93
|
+
"uuid": "^13.0.0",
|
|
94
|
+
"zod": "^4.1.5"
|
|
95
|
+
},
|
|
96
|
+
"devDependencies": {
|
|
97
|
+
"@next/eslint-plugin-next": "^15.3.5",
|
|
98
|
+
"@percepta/build": "^0.1.3",
|
|
99
|
+
"@tailwindcss/postcss": "^4.1.11",
|
|
100
|
+
"@types/formidable": "^3.4.5",
|
|
101
|
+
"@types/he": "^1.2.3",
|
|
102
|
+
"@types/lodash-es": "^4.17.12",
|
|
103
|
+
"@types/mime-types": "^3.0.1",
|
|
104
|
+
"@types/node": "^24.1.0",
|
|
105
|
+
"@types/numeral": "^2.0.5",
|
|
106
|
+
"@types/pg": "^8.15.4",
|
|
107
|
+
"@types/pluralize": "^0.0.33",
|
|
108
|
+
"@types/react": "^19.0.8",
|
|
109
|
+
"@types/react-dom": "^19.0.3",
|
|
110
|
+
"@types/yargs": "^17.0.33",
|
|
111
|
+
"drizzle-kit": "^0.31.4",
|
|
112
|
+
"eslint": "^9.18.0",
|
|
113
|
+
"eslint-plugin-n": "^17.23.1",
|
|
114
|
+
"eslint-plugin-react": "^7.37.4",
|
|
115
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
116
|
+
"husky": "^9.1.7",
|
|
117
|
+
"tailwindcss": "^4.0.12",
|
|
118
|
+
"typescript": "^5.7.3",
|
|
119
|
+
"vitest": "^3.2.1",
|
|
120
|
+
"yargs": "^17.7.2"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { loadEnvConfig } from "@next/env";
|
|
5
|
+
import yargs from "yargs";
|
|
6
|
+
import { hideBin } from "yargs/helpers";
|
|
7
|
+
|
|
8
|
+
async function main(): Promise<void> {
|
|
9
|
+
loadEnvConfig(process.cwd());
|
|
10
|
+
globalThis.AsyncLocalStorage = AsyncLocalStorage;
|
|
11
|
+
|
|
12
|
+
const { email, password, name } = yargs(hideBin(process.argv))
|
|
13
|
+
.usage(
|
|
14
|
+
"Usage: tsx scripts/create-user.ts <email> <password> [--name <name>]",
|
|
15
|
+
)
|
|
16
|
+
.command("$0 <email> <password>", "Create a new user.")
|
|
17
|
+
.positional("email", {
|
|
18
|
+
type: "string",
|
|
19
|
+
demandOption: true,
|
|
20
|
+
})
|
|
21
|
+
.positional("password", {
|
|
22
|
+
type: "string",
|
|
23
|
+
demandOption: true,
|
|
24
|
+
})
|
|
25
|
+
.option("name", {
|
|
26
|
+
alias: "n",
|
|
27
|
+
describe: "Display name for the user.",
|
|
28
|
+
type: "string",
|
|
29
|
+
demandOption: true,
|
|
30
|
+
})
|
|
31
|
+
.strict()
|
|
32
|
+
.help(false)
|
|
33
|
+
.parseSync();
|
|
34
|
+
|
|
35
|
+
// Dynamically load because we need to load the environment variables before importing modules that depend on them.
|
|
36
|
+
const { auth } = await import("../src/lib/auth");
|
|
37
|
+
|
|
38
|
+
// Create user via Better Auth's signUpEmail API (handles password hashing + account creation)
|
|
39
|
+
const { user } = await auth.api.signUpEmail({
|
|
40
|
+
body: { email, password, name },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(`User "${user.id}" created successfully.`);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void main();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { loadEnvConfig } from "@next/env";
|
|
2
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
3
|
+
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
4
|
+
|
|
5
|
+
async function main(): Promise<void> {
|
|
6
|
+
loadEnvConfig(process.cwd());
|
|
7
|
+
|
|
8
|
+
// Dynamically load because we need to load the environment variables before importing modules that depend on them.
|
|
9
|
+
const { client } = await import("../src/drizzle/db");
|
|
10
|
+
|
|
11
|
+
await migrate(drizzle(client), {
|
|
12
|
+
migrationsFolder: "./src/drizzle/migrations",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await client.end();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void main();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Development seed script.
|
|
5
|
+
* Creates a default admin user for local development.
|
|
6
|
+
*
|
|
7
|
+
* Usage: pnpm db:seed
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
+
import { loadEnvConfig } from "@next/env";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_USER = {
|
|
14
|
+
email: "admin@example.com",
|
|
15
|
+
password: "password",
|
|
16
|
+
name: "Admin User",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function main(): Promise<void> {
|
|
20
|
+
loadEnvConfig(process.cwd());
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
22
|
+
(globalThis as any).AsyncLocalStorage = AsyncLocalStorage;
|
|
23
|
+
|
|
24
|
+
const { auth } = await import("../src/lib/auth");
|
|
25
|
+
const { db } = await import("../src/drizzle/db");
|
|
26
|
+
const { users } = await import("../src/drizzle/schema/auth/users");
|
|
27
|
+
const { eq, sql } = await import("drizzle-orm");
|
|
28
|
+
|
|
29
|
+
// Check if user already exists
|
|
30
|
+
const [existing] = await db
|
|
31
|
+
.select({ id: users.id })
|
|
32
|
+
.from(users)
|
|
33
|
+
.where(eq(sql`lower(${users.email})`, sql`lower(${DEFAULT_USER.email})`))
|
|
34
|
+
.limit(1);
|
|
35
|
+
|
|
36
|
+
if (existing != null) {
|
|
37
|
+
await db
|
|
38
|
+
.update(users)
|
|
39
|
+
.set({ role: "admin" })
|
|
40
|
+
.where(eq(users.id, existing.id));
|
|
41
|
+
console.log(
|
|
42
|
+
`Seed user "${DEFAULT_USER.email}" already exists (id: ${existing.id}). Ensured admin role.`,
|
|
43
|
+
);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Use Better Auth's signUpEmail API to create the user with a hashed password
|
|
48
|
+
const res = await auth.api.signUpEmail({
|
|
49
|
+
body: DEFAULT_USER,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await db
|
|
53
|
+
.update(users)
|
|
54
|
+
.set({ role: "admin" })
|
|
55
|
+
.where(eq(users.id, res.user.id));
|
|
56
|
+
|
|
57
|
+
console.log(`Seed user created: ${DEFAULT_USER.email} (id: ${res.user.id})`);
|
|
58
|
+
console.log(` Password: ${DEFAULT_USER.password}`);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void main();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { loadEnvConfig } from "@next/env";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
import { getEnvConfig } from "../src/config/getEnvConfig";
|
|
4
|
+
|
|
5
|
+
async function main(): Promise<void> {
|
|
6
|
+
loadEnvConfig(process.cwd());
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
DATABASE_HOST: host,
|
|
10
|
+
DATABASE_PORT: port,
|
|
11
|
+
DATABASE_USERNAME: user,
|
|
12
|
+
DATABASE_PASSWORD: password,
|
|
13
|
+
DATABASE_NAME: database,
|
|
14
|
+
DATABASE_USE_SSL: useSSL,
|
|
15
|
+
} = getEnvConfig();
|
|
16
|
+
|
|
17
|
+
console.log(`🔧 Setting up database: ${database}`);
|
|
18
|
+
console.log(`📍 Host: ${host}:${port}`);
|
|
19
|
+
console.log(`👤 User: ${user}`);
|
|
20
|
+
|
|
21
|
+
// First, connect to the default 'postgres' database to create our target database
|
|
22
|
+
const adminClient = new Pool({
|
|
23
|
+
host,
|
|
24
|
+
port,
|
|
25
|
+
user,
|
|
26
|
+
password,
|
|
27
|
+
database: "postgres", // Connect to default postgres database
|
|
28
|
+
ssl: useSSL,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Check if the target database exists
|
|
33
|
+
const result = await adminClient.query(
|
|
34
|
+
"SELECT 1 FROM pg_database WHERE datname = $1",
|
|
35
|
+
[database],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (result.rows.length === 0) {
|
|
39
|
+
console.log(`📦 Creating database: ${database}`);
|
|
40
|
+
// Create the database (note: database names cannot be parameterized)
|
|
41
|
+
await adminClient.query(`CREATE DATABASE "${database}"`);
|
|
42
|
+
console.log(`✅ Database ${database} created successfully`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(`✅ Database ${database} already exists`);
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("❌ Error creating database:", error);
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
await adminClient.end();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void main().catch((error) => {
|
|
55
|
+
console.error("💥 Database setup failed:", error);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|