@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.
Files changed (138) hide show
  1. package/README.md +93 -0
  2. package/dist/chunk-GEVZERMP.js +108 -0
  3. package/dist/chunk-R4FWPE4A.js +49 -0
  4. package/dist/chunk-WMJT7CB5.js +57 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +974 -0
  7. package/dist/init-Z4VGBHAK.js +96 -0
  8. package/dist/status-MITGDLTT.js +76 -0
  9. package/dist/sync-J4SFZHDX.js +136 -0
  10. package/dist/upstream-AQI7P4EU.js +144 -0
  11. package/package.json +58 -0
  12. package/template-versions.json +4 -0
  13. package/templates/library/README.md +30 -0
  14. package/templates/library/eslint.config.js +10 -0
  15. package/templates/library/gitignore.template +18 -0
  16. package/templates/library/package.json.template +29 -0
  17. package/templates/library/src/index.ts +9 -0
  18. package/templates/library/tsconfig.json +19 -0
  19. package/templates/monorepo/README.md +41 -0
  20. package/templates/monorepo/eslint.config.js +10 -0
  21. package/templates/monorepo/gitignore.template +31 -0
  22. package/templates/monorepo/npmrc.template +4 -0
  23. package/templates/monorepo/package.json.template +25 -0
  24. package/templates/monorepo/packages/.gitkeep +0 -0
  25. package/templates/monorepo/pnpm-workspace.yaml +2 -0
  26. package/templates/monorepo/tsconfig.json +16 -0
  27. package/templates/webapp/.claude/commands/sync.md +19 -0
  28. package/templates/webapp/.claude/commands/upstream.md +17 -0
  29. package/templates/webapp/.dockerignore +59 -0
  30. package/templates/webapp/.gitattributes +1 -0
  31. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +114 -0
  32. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +28 -0
  33. package/templates/webapp/.github/workflows/ci.yml +149 -0
  34. package/templates/webapp/.node-version +2 -0
  35. package/templates/webapp/.prettierrc.mjs +5 -0
  36. package/templates/webapp/AGENTS.md +240 -0
  37. package/templates/webapp/Dockerfile +64 -0
  38. package/templates/webapp/README.md +200 -0
  39. package/templates/webapp/agent-skills/database.md +140 -0
  40. package/templates/webapp/agent-skills/deploy.md +94 -0
  41. package/templates/webapp/agent-skills/inngest.md +147 -0
  42. package/templates/webapp/agent-skills/langfuse.md +117 -0
  43. package/templates/webapp/agent-skills/oneshot.md +216 -0
  44. package/templates/webapp/agent-skills/ryvn.md +25 -0
  45. package/templates/webapp/deploy/README.md +39 -0
  46. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +11 -0
  47. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +121 -0
  48. package/templates/webapp/docker-compose.yml +19 -0
  49. package/templates/webapp/drizzle.config.ts +30 -0
  50. package/templates/webapp/env.example.template +44 -0
  51. package/templates/webapp/eslint.config.mjs +52 -0
  52. package/templates/webapp/gitignore.template +53 -0
  53. package/templates/webapp/next.config.ts +8 -0
  54. package/templates/webapp/npmrc.template +4 -0
  55. package/templates/webapp/package.json.template +122 -0
  56. package/templates/webapp/postcss.config.mjs +5 -0
  57. package/templates/webapp/scripts/create-user.ts +47 -0
  58. package/templates/webapp/scripts/migrate.ts +18 -0
  59. package/templates/webapp/scripts/seed.ts +62 -0
  60. package/templates/webapp/scripts/setup-database.ts +57 -0
  61. package/templates/webapp/scripts/setup-readonly-user.ts +193 -0
  62. package/templates/webapp/scripts/start.sh +52 -0
  63. package/templates/webapp/src/app/(app)/layout.tsx +21 -0
  64. package/templates/webapp/src/app/(app)/page.tsx +30 -0
  65. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +103 -0
  66. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +30 -0
  67. package/templates/webapp/src/app/(auth)/layout.tsx +15 -0
  68. package/templates/webapp/src/app/api/auth/[...all]/route.ts +4 -0
  69. package/templates/webapp/src/app/api/healthz/route.ts +10 -0
  70. package/templates/webapp/src/app/api/inngest/route.ts +31 -0
  71. package/templates/webapp/src/app/api/readyz/route.ts +31 -0
  72. package/templates/webapp/src/app/api/trpc/[trpc]/route.ts +21 -0
  73. package/templates/webapp/src/app/favicon.ico +0 -0
  74. package/templates/webapp/src/app/global-error.tsx +27 -0
  75. package/templates/webapp/src/app/layout.tsx +18 -0
  76. package/templates/webapp/src/components/FaroProvider.tsx +37 -0
  77. package/templates/webapp/src/components/Header.tsx +70 -0
  78. package/templates/webapp/src/components/Providers.tsx +45 -0
  79. package/templates/webapp/src/components/form/FormItem.tsx +82 -0
  80. package/templates/webapp/src/config/clientEnvConfig.ts +11 -0
  81. package/templates/webapp/src/config/getEnvConfig.ts +62 -0
  82. package/templates/webapp/src/config/isDev.ts +7 -0
  83. package/templates/webapp/src/drizzle/db.ts +28 -0
  84. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +57 -0
  85. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +376 -0
  86. package/templates/webapp/src/drizzle/migrations/meta/_journal.json +13 -0
  87. package/templates/webapp/src/drizzle/schema/auth/accounts.ts +33 -0
  88. package/templates/webapp/src/drizzle/schema/auth/sessions.ts +25 -0
  89. package/templates/webapp/src/drizzle/schema/auth/users.ts +38 -0
  90. package/templates/webapp/src/drizzle/schema/auth/verifications.ts +19 -0
  91. package/templates/webapp/src/drizzle/schema/index.ts +4 -0
  92. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +25 -0
  93. package/templates/webapp/src/instrumentation.ts +35 -0
  94. package/templates/webapp/src/lib/auth/index.ts +85 -0
  95. package/templates/webapp/src/lib/auth-client.ts +6 -0
  96. package/templates/webapp/src/lib/trpc.ts +15 -0
  97. package/templates/webapp/src/server/api/root.ts +5 -0
  98. package/templates/webapp/src/server/trpc.ts +61 -0
  99. package/templates/webapp/src/services/AuthContextService.ts +63 -0
  100. package/templates/webapp/src/services/DatabaseService.ts +54 -0
  101. package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +5 -0
  102. package/templates/webapp/src/services/inngest/InngestService.ts +71 -0
  103. package/templates/webapp/src/services/inngest/events/AppEvents.ts +34 -0
  104. package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +14 -0
  105. package/templates/webapp/src/services/langfuse/LangfuseService.ts +80 -0
  106. package/templates/webapp/src/services/logger/AppLogger.ts +61 -0
  107. package/templates/webapp/src/services/logger/withRequestContext.ts +27 -0
  108. package/templates/webapp/src/services/observability/initFaro.ts +22 -0
  109. package/templates/webapp/src/startup-checks.ts +32 -0
  110. package/templates/webapp/src/styles/globals.css +27 -0
  111. package/templates/webapp/src/utils/__tests__/cn.test.ts +20 -0
  112. package/templates/webapp/src/utils/cn.ts +6 -0
  113. package/templates/webapp/src/utils/syncInngestApp.ts +62 -0
  114. package/templates/webapp/terraform/README.md +147 -0
  115. package/templates/webapp/terraform/deploy.sh +97 -0
  116. package/templates/webapp/terraform/main.tf +101 -0
  117. package/templates/webapp/terraform/modules/cloudtrail/main.tf +27 -0
  118. package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +10 -0
  119. package/templates/webapp/terraform/modules/cloudtrail/variables.tf +15 -0
  120. package/templates/webapp/terraform/modules/networking/main.tf +118 -0
  121. package/templates/webapp/terraform/modules/networking/outputs.tf +38 -0
  122. package/templates/webapp/terraform/modules/networking/variables.tf +24 -0
  123. package/templates/webapp/terraform/modules/rds/main.tf +227 -0
  124. package/templates/webapp/terraform/modules/rds/outputs.tf +73 -0
  125. package/templates/webapp/terraform/modules/rds/variables.tf +61 -0
  126. package/templates/webapp/terraform/modules/s3-logging/main.tf +148 -0
  127. package/templates/webapp/terraform/modules/s3-logging/outputs.tf +10 -0
  128. package/templates/webapp/terraform/modules/s3-logging/variables.tf +16 -0
  129. package/templates/webapp/terraform/modules/secrets/main.tf +39 -0
  130. package/templates/webapp/terraform/modules/secrets/outputs.tf +9 -0
  131. package/templates/webapp/terraform/modules/secrets/variables.tf +51 -0
  132. package/templates/webapp/terraform/outputs.tf +102 -0
  133. package/templates/webapp/terraform/providers.tf +32 -0
  134. package/templates/webapp/terraform/terraform.tfvars.example +65 -0
  135. package/templates/webapp/terraform/variables.tf +129 -0
  136. package/templates/webapp/tsconfig.json +14 -0
  137. package/templates/webapp/vitest.config.ts +9 -0
  138. 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,8 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ // Enable standalone output for Docker:
5
+ output: "standalone",
6
+ };
7
+
8
+ export default nextConfig;
@@ -0,0 +1,4 @@
1
+ @percepta:registry=https://registry.npmjs.org/
2
+ //registry.npmjs.org/:_authToken=${NPM_TOKEN}
3
+
4
+ engine-strict=true
@@ -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,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
@@ -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
+ });