@percepta/create 3.1.0 → 3.1.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.
Files changed (62) hide show
  1. package/README.md +15 -10
  2. package/dist/{chunk-7NPWSTCY.js → chunk-CO3YWUD6.js} +31 -2
  3. package/dist/{chunk-WMJT7CB5.js → chunk-V5EJIUBJ.js} +5 -2
  4. package/dist/index.js +93 -73
  5. package/dist/{init-NP6GRXLL.js → init-EQZ2TCSJ.js} +2 -2
  6. package/dist/{status-BTHGN6QH.js → status-QW5TQDYY.js} +1 -1
  7. package/dist/{sync-3Q27L7XZ.js → sync-RLBZDOFB.js} +1 -1
  8. package/dist/{upstream-C5KFAHVR.js → upstream-TQFVPMEG.js} +1 -1
  9. package/package.json +3 -2
  10. package/templates/monorepo/.dockerignore +18 -0
  11. package/templates/monorepo/gitignore.template +1 -0
  12. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +6 -2
  13. package/templates/webapp/.github/workflows/__APP_NAME__-terraform-ryvn-release.yaml +98 -0
  14. package/templates/webapp/AGENTS.md +17 -5
  15. package/templates/webapp/Dockerfile +16 -7
  16. package/templates/webapp/README.md +64 -2
  17. package/templates/webapp/agent-skills/deploy.md +50 -51
  18. package/templates/webapp/agent-skills/inngest.md +4 -4
  19. package/templates/webapp/agent-skills/langfuse.md +15 -14
  20. package/templates/webapp/agent-skills/llm.md +59 -0
  21. package/templates/webapp/agent-skills/oneshot.md +14 -1
  22. package/templates/webapp/agent-skills/ryvn.md +1 -1
  23. package/templates/webapp/deploy/README.md +41 -16
  24. package/templates/webapp/deploy/ryvn/__APP_NAME__-terraform.service.yaml +10 -0
  25. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +2 -2
  26. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__-terraform.env.percepta-test.serviceinstallation.yaml +11 -0
  27. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +60 -11
  28. package/templates/webapp/env.example.template +20 -2
  29. package/templates/webapp/eslint.config.mjs +7 -0
  30. package/templates/webapp/gitignore.template +1 -0
  31. package/templates/webapp/next.config.ts +9 -0
  32. package/templates/webapp/package.json.template +6 -2
  33. package/templates/webapp/scripts/deploy-percepta-test.ts +837 -0
  34. package/templates/webapp/scripts/migrate.ts +3 -0
  35. package/templates/webapp/scripts/open-ryvn-deploy-pr.ts +152 -32
  36. package/templates/webapp/scripts/seed.ts +1 -1
  37. package/templates/webapp/scripts/setup-database.ts +2 -1
  38. package/templates/webapp/scripts/start.sh +3 -2
  39. package/templates/webapp/scripts/with-local-env.ts +75 -0
  40. package/templates/webapp/src/app/(app)/layout.tsx +1 -5
  41. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +11 -1
  42. package/templates/webapp/src/app/(auth)/auth/signup/CredentialsSignUpForm.tsx +113 -0
  43. package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +30 -0
  44. package/templates/webapp/src/app/global-error.tsx +1 -1
  45. package/templates/webapp/src/components/FaroProvider.tsx +2 -4
  46. package/templates/webapp/src/components/form/FormItem.tsx +2 -2
  47. package/templates/webapp/src/config/getEnvConfig.ts +14 -0
  48. package/templates/webapp/src/drizzle/db.ts +2 -1
  49. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +3 -3
  50. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +7 -19
  51. package/templates/webapp/src/drizzle/ssl.ts +5 -0
  52. package/templates/webapp/src/instrumentation.ts +102 -10
  53. package/templates/webapp/src/lib/auth/index.ts +1 -1
  54. package/templates/webapp/src/lib/auth-client.ts +1 -1
  55. package/templates/webapp/src/services/llm/LLMService.ts +88 -0
  56. package/templates/webapp/src/services/llm/LlmProviderService.ts +85 -0
  57. package/templates/webapp/src/services/observability/initFaro.ts +1 -1
  58. package/templates/webapp/terraform/schema/main.tf +4 -0
  59. package/templates/webapp/terraform/schema/outputs.tf +9 -0
  60. package/templates/webapp/terraform/schema/variables.tf +19 -0
  61. package/templates/webapp/terraform/schema/versions.tf +38 -0
  62. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +0 -28
@@ -34,7 +34,7 @@ export const FormItem: React.FC<FormItemProps> = ({
34
34
  return (
35
35
  <p
36
36
  id={messageId}
37
- className="text-destructive-foreground text-sm"
37
+ className="text-sm text-destructive-foreground"
38
38
  data-slot="form-message"
39
39
  >
40
40
  {body}
@@ -70,7 +70,7 @@ export const FormItem: React.FC<FormItemProps> = ({
70
70
  {description != null && (
71
71
  <p
72
72
  id={descriptionId}
73
- className="text-muted-foreground text-sm"
73
+ className="text-sm text-muted-foreground"
74
74
  data-slot="form-description"
75
75
  >
76
76
  {description}
@@ -44,12 +44,26 @@ export const { getEnvConfig, schema: ENV_CONFIG_SCHEMA } = createEnvConfig(
44
44
  LANGFUSE_PUBLIC_KEY: z.string().optional(),
45
45
  LANGFUSE_SECRET_KEY: z.string().optional(),
46
46
 
47
+ // OpenTelemetry:
48
+ OTEL_SERVICE_NAME: z.string().optional(),
49
+ OTEL_RESOURCE_ATTRIBUTES: z.string().optional(),
50
+ OTEL_TRACES_EXPORTER: z.string().optional(),
51
+ OTEL_METRICS_EXPORTER: z.string().optional(),
52
+ OTEL_EXPORTER_OTLP_PROTOCOL: z.string().optional(),
53
+ OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(),
54
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: z.string().optional(),
55
+ OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: z.string().optional(),
56
+ OTEL_METRIC_EXPORT_INTERVAL: z.string().optional(),
57
+
47
58
  // Security:
48
59
  ENCRYPTION_SECRET_KEY: z.string().optional(),
49
60
  TRIGGER_ENDPOINT_API_KEY: z.string().optional(),
50
61
 
51
62
  // LLM/AI providers:
63
+ ANTHROPIC_API_KEY: z.string().optional(),
52
64
  OPENAI_API_KEY: z.string().optional(),
65
+ LLM_PROVIDER: z.enum(["anthropic", "openai"]).optional(),
66
+ LLM_MODEL: z.string().optional(),
53
67
 
54
68
  // Readonly database (scripts):
55
69
  READONLY_SECRET_NAME: z.string().optional(),
@@ -3,6 +3,7 @@ import { Pool } from "pg";
3
3
  import { getEnvConfig } from "../config/getEnvConfig";
4
4
  import * as schema from "./schema";
5
5
  import { getPgSearchPathOption } from "./searchPath";
6
+ import { getPgSslConfig } from "./ssl";
6
7
 
7
8
  export const { client, db } = createDb();
8
9
 
@@ -23,7 +24,7 @@ function createDb(): { client: Pool; db: NodePgDatabase<typeof schema> } {
23
24
  user,
24
25
  password,
25
26
  database,
26
- ssl: useSSL,
27
+ ssl: getPgSslConfig(useSSL),
27
28
  options: getPgSearchPathOption(databaseSchema),
28
29
  });
29
30
 
@@ -52,6 +52,6 @@ CREATE TABLE "verification" (
52
52
  "updated_at" timestamp
53
53
  );
54
54
  --> statement-breakpoint
55
- ALTER TABLE "account" ADD CONSTRAINT "account_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
56
- ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
57
- CREATE UNIQUE INDEX "lower_email_index" ON "users" USING btree (lower("email"));
55
+ ALTER TABLE "account" ADD CONSTRAINT "account_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
56
+ ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
57
+ CREATE UNIQUE INDEX "lower_email_index" ON "users" USING btree (lower("email"));
@@ -99,12 +99,8 @@
99
99
  "name": "account_user_id_users_id_fk",
100
100
  "tableFrom": "account",
101
101
  "tableTo": "users",
102
- "columnsFrom": [
103
- "user_id"
104
- ],
105
- "columnsTo": [
106
- "id"
107
- ],
102
+ "columnsFrom": ["user_id"],
103
+ "columnsTo": ["id"],
108
104
  "onDelete": "cascade",
109
105
  "onUpdate": "no action"
110
106
  }
@@ -180,12 +176,8 @@
180
176
  "name": "session_user_id_users_id_fk",
181
177
  "tableFrom": "session",
182
178
  "tableTo": "users",
183
- "columnsFrom": [
184
- "user_id"
185
- ],
186
- "columnsTo": [
187
- "id"
188
- ],
179
+ "columnsFrom": ["user_id"],
180
+ "columnsTo": ["id"],
189
181
  "onDelete": "cascade",
190
182
  "onUpdate": "no action"
191
183
  }
@@ -195,9 +187,7 @@
195
187
  "session_token_unique": {
196
188
  "name": "session_token_unique",
197
189
  "nullsNotDistinct": false,
198
- "columns": [
199
- "token"
200
- ]
190
+ "columns": ["token"]
201
191
  }
202
192
  },
203
193
  "policies": {},
@@ -303,9 +293,7 @@
303
293
  "users_email_unique": {
304
294
  "name": "users_email_unique",
305
295
  "nullsNotDistinct": false,
306
- "columns": [
307
- "email"
308
- ]
296
+ "columns": ["email"]
309
297
  }
310
298
  },
311
299
  "policies": {},
@@ -373,4 +361,4 @@
373
361
  "schemas": {},
374
362
  "tables": {}
375
363
  }
376
- }
364
+ }
@@ -0,0 +1,5 @@
1
+ export function getPgSslConfig(
2
+ useSSL: boolean,
3
+ ): false | { rejectUnauthorized: false } {
4
+ return useSSL ? { rejectUnauthorized: false } : false;
5
+ }
@@ -1,34 +1,126 @@
1
1
  import { LangfuseSpanProcessor } from "@langfuse/otel";
2
2
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
3
- import { NodeSDK } from "@opentelemetry/sdk-node";
3
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
4
+ import { NodeSDK, tracing } from "@opentelemetry/sdk-node";
4
5
  import { compact } from "lodash-es";
5
6
  import { getEnvConfig } from "./config/getEnvConfig";
6
7
  import { getLogger } from "./services/logger/AppLogger";
7
8
 
8
- function getSpanProcessor(): LangfuseSpanProcessor | undefined {
9
+ type SpanProcessor = tracing.SpanProcessor;
10
+ type ReadableSpan = Parameters<SpanProcessor["onEnd"]>[0];
11
+ type OnStartArgs = Parameters<SpanProcessor["onStart"]>;
12
+
13
+ class FilteringSpanProcessor implements SpanProcessor {
14
+ public constructor(
15
+ private delegate: SpanProcessor,
16
+ private shouldExport: (span: ReadableSpan) => boolean,
17
+ ) {}
18
+
19
+ public forceFlush(): Promise<void> {
20
+ return this.delegate.forceFlush();
21
+ }
22
+
23
+ public onStart(...args: OnStartArgs): void {
24
+ this.delegate.onStart(...args);
25
+ }
26
+
27
+ public onEnd(span: ReadableSpan): void {
28
+ if (this.shouldExport(span)) {
29
+ this.delegate.onEnd(span);
30
+ }
31
+ }
32
+
33
+ public shutdown(): Promise<void> {
34
+ return this.delegate.shutdown();
35
+ }
36
+ }
37
+
38
+ function isAiSdkSpan(span: ReadableSpan): boolean {
39
+ const operationId = span.attributes["ai.operationId"];
40
+ if (typeof operationId === "string" && operationId.startsWith("ai.")) {
41
+ return true;
42
+ }
43
+
44
+ if (span.name.startsWith("ai.")) {
45
+ return true;
46
+ }
47
+
48
+ return Object.keys(span.attributes).some((key) => key.startsWith("gen_ai."));
49
+ }
50
+
51
+ function getOtlpTracesEndpoint(): string | undefined {
52
+ const {
53
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: tracesEndpoint,
54
+ OTEL_EXPORTER_OTLP_ENDPOINT: baseEndpoint,
55
+ } = getEnvConfig();
56
+
57
+ if (tracesEndpoint) return tracesEndpoint;
58
+
59
+ if (!baseEndpoint) return undefined;
60
+
61
+ return `${baseEndpoint.replace(/\/$/, "")}/v1/traces`;
62
+ }
63
+
64
+ function getOtlpSpanProcessor(): tracing.BatchSpanProcessor | undefined {
65
+ const { OTEL_TRACES_EXPORTER: tracesExporter } = getEnvConfig();
66
+ if (tracesExporter === "none") {
67
+ getLogger().debug(
68
+ undefined,
69
+ "OTEL_TRACES_EXPORTER=none. Skipping OTLP trace export.",
70
+ );
71
+ return undefined;
72
+ }
73
+
74
+ const tracesEndpoint = getOtlpTracesEndpoint();
75
+ if (!tracesEndpoint) {
76
+ getLogger().debug(
77
+ undefined,
78
+ "No OTLP trace endpoint found. Skipping OTLP trace export.",
79
+ );
80
+ return undefined;
81
+ }
82
+
83
+ getLogger().debug(
84
+ { safe: { tracesEndpoint } },
85
+ "Registering OTLP trace exporter.",
86
+ );
87
+ return new tracing.BatchSpanProcessor(
88
+ new OTLPTraceExporter({ url: tracesEndpoint }),
89
+ );
90
+ }
91
+
92
+ function getLangfuseSpanProcessor(): SpanProcessor | undefined {
9
93
  const {
10
94
  LANGFUSE_BASE_URL: baseUrl,
11
95
  LANGFUSE_PUBLIC_KEY: publicKey,
12
96
  LANGFUSE_SECRET_KEY: secretKey,
13
97
  } = getEnvConfig();
14
- if (baseUrl == null) {
98
+ if (!baseUrl || !publicKey || !secretKey) {
15
99
  getLogger().debug(
16
100
  undefined,
17
- "No Langfuse base URL found. Skipping Langfuse OpenTelemetry.",
101
+ "Langfuse environment is incomplete. Skipping Langfuse OpenTelemetry.",
18
102
  );
19
103
  return undefined;
20
104
  }
21
105
 
22
106
  getLogger().debug(undefined, "Registering Langfuse OpenTelemetry.");
23
- return new LangfuseSpanProcessor({
24
- baseUrl,
25
- publicKey,
26
- secretKey,
27
- });
107
+ return new FilteringSpanProcessor(
108
+ new LangfuseSpanProcessor({
109
+ baseUrl,
110
+ publicKey,
111
+ secretKey,
112
+ }),
113
+ isAiSdkSpan,
114
+ );
28
115
  }
29
116
 
117
+ const spanProcessors: tracing.SpanProcessor[] = compact([
118
+ getOtlpSpanProcessor(),
119
+ getLangfuseSpanProcessor(),
120
+ ]);
121
+
30
122
  const sdk = new NodeSDK({
31
- spanProcessors: compact([getSpanProcessor()]),
123
+ spanProcessors,
32
124
  instrumentations: [getNodeAutoInstrumentations()],
33
125
  });
34
126
 
@@ -1,12 +1,12 @@
1
1
  import { betterAuth } from "better-auth";
2
2
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
3
  import { admin } from "better-auth/plugins";
4
+ import { getEnvConfig } from "../../config/getEnvConfig";
4
5
  import { db } from "../../drizzle/db";
5
6
  import { accounts } from "../../drizzle/schema/auth/accounts";
6
7
  import { sessions } from "../../drizzle/schema/auth/sessions";
7
8
  import { users } from "../../drizzle/schema/auth/users";
8
9
  import { verifications } from "../../drizzle/schema/auth/verifications";
9
- import { getEnvConfig } from "../../config/getEnvConfig";
10
10
  import { getLogger } from "../../services/logger/AppLogger";
11
11
 
12
12
  // eslint-disable-next-line n/no-process-env -- detecting Next.js build phase
@@ -1,5 +1,5 @@
1
- import { createAuthClient } from "better-auth/react";
2
1
  import { adminClient } from "better-auth/client/plugins";
2
+ import { createAuthClient } from "better-auth/react";
3
3
 
4
4
  export const authClient = createAuthClient({
5
5
  plugins: [adminClient()],
@@ -0,0 +1,88 @@
1
+ import { generateText, streamText } from "ai";
2
+ import { type LlmProviderName, LlmProviderService } from "./LlmProviderService";
3
+
4
+ type GenerateTextOptions = Omit<Parameters<typeof generateText>[0], "model"> & {
5
+ modelId?: string;
6
+ provider?: LlmProviderName;
7
+ telemetryFunctionId?: string;
8
+ };
9
+
10
+ type StreamTextOptions = Omit<Parameters<typeof streamText>[0], "model"> & {
11
+ modelId?: string;
12
+ provider?: LlmProviderName;
13
+ telemetryFunctionId?: string;
14
+ };
15
+
16
+ export class LLMService {
17
+ private static SINGLETON: LLMService | undefined;
18
+
19
+ public static create(): LLMService {
20
+ if (LLMService.SINGLETON == null) {
21
+ LLMService.SINGLETON = new LLMService(LlmProviderService.create());
22
+ }
23
+
24
+ return LLMService.SINGLETON;
25
+ }
26
+
27
+ private constructor(private llmProviderService: LlmProviderService) {}
28
+
29
+ public generateText(
30
+ options: GenerateTextOptions,
31
+ ): ReturnType<typeof generateText> {
32
+ const { modelId, provider, telemetryFunctionId, ...generateOptions } =
33
+ options;
34
+ const selection = this.llmProviderService.getLanguageModel({
35
+ modelId,
36
+ provider,
37
+ });
38
+
39
+ const aiOptions = {
40
+ ...generateOptions,
41
+ model: selection.model,
42
+ experimental_telemetry: {
43
+ ...generateOptions.experimental_telemetry,
44
+ isEnabled: generateOptions.experimental_telemetry?.isEnabled ?? true,
45
+ functionId:
46
+ telemetryFunctionId ??
47
+ generateOptions.experimental_telemetry?.functionId ??
48
+ "llm.generateText",
49
+ metadata: {
50
+ ...generateOptions.experimental_telemetry?.metadata,
51
+ llmProvider: selection.provider,
52
+ llmModel: selection.modelId,
53
+ },
54
+ },
55
+ } as Parameters<typeof generateText>[0];
56
+
57
+ return generateText(aiOptions);
58
+ }
59
+
60
+ public streamText(options: StreamTextOptions): ReturnType<typeof streamText> {
61
+ const { modelId, provider, telemetryFunctionId, ...streamOptions } =
62
+ options;
63
+ const selection = this.llmProviderService.getLanguageModel({
64
+ modelId,
65
+ provider,
66
+ });
67
+
68
+ const aiOptions = {
69
+ ...streamOptions,
70
+ model: selection.model,
71
+ experimental_telemetry: {
72
+ ...streamOptions.experimental_telemetry,
73
+ isEnabled: streamOptions.experimental_telemetry?.isEnabled ?? true,
74
+ functionId:
75
+ telemetryFunctionId ??
76
+ streamOptions.experimental_telemetry?.functionId ??
77
+ "llm.streamText",
78
+ metadata: {
79
+ ...streamOptions.experimental_telemetry?.metadata,
80
+ llmProvider: selection.provider,
81
+ llmModel: selection.modelId,
82
+ },
83
+ },
84
+ } as Parameters<typeof streamText>[0];
85
+
86
+ return streamText(aiOptions);
87
+ }
88
+ }
@@ -0,0 +1,85 @@
1
+ import { createAnthropic } from "@ai-sdk/anthropic";
2
+ import { createOpenAI } from "@ai-sdk/openai";
3
+ import { type LanguageModel } from "ai";
4
+ import { getEnvConfig } from "../../config/getEnvConfig";
5
+
6
+ export type LlmProviderName = "anthropic" | "openai";
7
+
8
+ export interface LanguageModelSelection {
9
+ model: LanguageModel;
10
+ modelId: string;
11
+ provider: LlmProviderName;
12
+ }
13
+
14
+ interface GetLanguageModelOptions {
15
+ modelId?: string;
16
+ provider?: LlmProviderName;
17
+ }
18
+
19
+ const DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-5-20250929";
20
+ const DEFAULT_OPENAI_MODEL = "gpt-4.1";
21
+
22
+ export class LlmProviderService {
23
+ private static SINGLETON: LlmProviderService | undefined;
24
+
25
+ public static create(): LlmProviderService {
26
+ if (LlmProviderService.SINGLETON == null) {
27
+ LlmProviderService.SINGLETON = new LlmProviderService();
28
+ }
29
+
30
+ return LlmProviderService.SINGLETON;
31
+ }
32
+
33
+ private constructor() {}
34
+
35
+ public getLanguageModel(
36
+ options: GetLanguageModelOptions = {},
37
+ ): LanguageModelSelection {
38
+ const config = getEnvConfig();
39
+ const provider =
40
+ options.provider ??
41
+ config.LLM_PROVIDER ??
42
+ (config.ANTHROPIC_API_KEY ? "anthropic" : undefined) ??
43
+ (config.OPENAI_API_KEY ? "openai" : undefined);
44
+
45
+ if (provider === "anthropic") {
46
+ if (!config.ANTHROPIC_API_KEY) {
47
+ throw new Error(
48
+ "LLM_PROVIDER=anthropic but ANTHROPIC_API_KEY is not configured.",
49
+ );
50
+ }
51
+
52
+ const modelId =
53
+ options.modelId ?? config.LLM_MODEL ?? DEFAULT_ANTHROPIC_MODEL;
54
+ return {
55
+ provider,
56
+ modelId,
57
+ model: createAnthropic({
58
+ apiKey: config.ANTHROPIC_API_KEY,
59
+ }).languageModel(modelId),
60
+ };
61
+ }
62
+
63
+ if (provider === "openai") {
64
+ if (!config.OPENAI_API_KEY) {
65
+ throw new Error(
66
+ "LLM_PROVIDER=openai but OPENAI_API_KEY is not configured.",
67
+ );
68
+ }
69
+
70
+ const modelId =
71
+ options.modelId ?? config.LLM_MODEL ?? DEFAULT_OPENAI_MODEL;
72
+ return {
73
+ provider,
74
+ modelId,
75
+ model: createOpenAI({
76
+ apiKey: config.OPENAI_API_KEY,
77
+ }).languageModel(modelId),
78
+ };
79
+ }
80
+
81
+ throw new Error(
82
+ "No LLM provider is configured. Set ANTHROPIC_API_KEY or OPENAI_API_KEY locally, or deploy to an environment with a shared LLM variable group.",
83
+ );
84
+ }
85
+ }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { createFaroInstance } from "@percepta/next-utils/faro";
4
3
  import { TracingInstrumentation } from "@grafana/faro-web-tracing";
4
+ import { createFaroInstance } from "@percepta/next-utils/faro";
5
5
  import { getClientEnvConfig } from "../../config/clientEnvConfig";
6
6
 
7
7
  const {
@@ -0,0 +1,4 @@
1
+ resource "postgresql_schema" "demo" {
2
+ database = var.database_name
3
+ name = var.schema_name
4
+ }
@@ -0,0 +1,9 @@
1
+ output "database_name" {
2
+ description = "Database containing the demo schema."
3
+ value = var.database_name
4
+ }
5
+
6
+ output "schema_name" {
7
+ description = "Created demo schema name."
8
+ value = postgresql_schema.demo.name
9
+ }
@@ -0,0 +1,19 @@
1
+ variable "aws_region" {
2
+ description = "AWS region containing the shared Percepta internal database secret."
3
+ type = string
4
+ }
5
+
6
+ variable "database_secret_name" {
7
+ description = "AWS Secrets Manager secret name containing shared Postgres credentials."
8
+ type = string
9
+ }
10
+
11
+ variable "database_name" {
12
+ description = "Database where the demo app schema should be created."
13
+ type = string
14
+ }
15
+
16
+ variable "schema_name" {
17
+ description = "Postgres schema name for this demo app."
18
+ type = string
19
+ }
@@ -0,0 +1,38 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = "~> 5.0"
8
+ }
9
+ postgresql = {
10
+ source = "cyrilgdn/postgresql"
11
+ version = "~> 1.22"
12
+ }
13
+ }
14
+
15
+ backend "kubernetes" {}
16
+ }
17
+
18
+ provider "aws" {
19
+ region = var.aws_region
20
+ }
21
+
22
+ data "aws_secretsmanager_secret_version" "database" {
23
+ secret_id = var.database_secret_name
24
+ }
25
+
26
+ locals {
27
+ database_credentials = jsondecode(data.aws_secretsmanager_secret_version.database.secret_string)
28
+ }
29
+
30
+ provider "postgresql" {
31
+ host = local.database_credentials.host
32
+ port = tonumber(local.database_credentials.port)
33
+ username = local.database_credentials.username
34
+ password = local.database_credentials.password
35
+ sslmode = "require"
36
+ connect_timeout = 15
37
+ superuser = false
38
+ }
@@ -1,28 +0,0 @@
1
- name: Terraform Validate (__APP_NAME__)
2
-
3
- on:
4
- push:
5
- paths:
6
- - "packages/__APP_NAME__/terraform/**"
7
- - ".github/workflows/__APP_NAME__-terraform.yml"
8
-
9
- jobs:
10
- terraform-validate:
11
- name: Validate Terraform
12
- runs-on: ubuntu-latest
13
-
14
- steps:
15
- - name: Checkout repository
16
- uses: actions/checkout@v4
17
-
18
- - name: Setup Terraform
19
- uses: hashicorp/setup-terraform@v3
20
-
21
- - name: Terraform Init
22
- run: terraform -chdir=packages/__APP_NAME__/terraform init -backend=false
23
-
24
- - name: Terraform Format Check
25
- run: terraform fmt -check -recursive packages/__APP_NAME__/terraform/
26
-
27
- - name: Terraform Validate
28
- run: terraform -chdir=packages/__APP_NAME__/terraform validate