@percepta/create 4.1.15 → 4.2.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 (38) hide show
  1. package/dist/index.js +24 -152
  2. package/dist/index.js.map +1 -1
  3. package/dist/{register-app-BeSQEsel.js → register-app-BtvxQeo0.js} +91 -1
  4. package/dist/register-app-BtvxQeo0.js.map +1 -0
  5. package/package.json +5 -2
  6. package/template-versions.json +2 -2
  7. package/templates/monorepo/README.md +28 -1
  8. package/templates/monorepo/auth/package.json +1 -1
  9. package/templates/monorepo/auth/src/auth.ts +7 -103
  10. package/templates/monorepo/authentik/blueprints/local-dev.yaml +123 -0
  11. package/templates/monorepo/authentik/initdb/00-authentik.sql +5 -0
  12. package/templates/monorepo/docker-compose.yml +70 -0
  13. package/templates/monorepo/oxlint.config.ts.template +5 -1
  14. package/templates/monorepo/package.json.template +2 -1
  15. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +22 -89
  16. package/templates/webapp/AGENTS.md +4 -4
  17. package/templates/webapp/README.md +22 -33
  18. package/templates/webapp/agent-skills/langfuse.md +8 -11
  19. package/templates/webapp/agent-skills/oneshot.md +1 -1
  20. package/templates/webapp/e2e/rbac.spec.ts +28 -4
  21. package/templates/webapp/env.example.template +8 -12
  22. package/templates/webapp/package.json.template +4 -5
  23. package/templates/webapp/scripts/seed.ts +28 -52
  24. package/templates/webapp/scripts/with-local-env.ts +12 -64
  25. package/templates/webapp/src/app/(auth)/auth/signin/SignInForm.tsx +59 -0
  26. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +3 -3
  27. package/templates/webapp/src/drizzle/db.ts +5 -9
  28. package/templates/webapp/src/instrumentation.ts +5 -72
  29. package/templates/webapp/src/lib/auth/index.ts +1 -2
  30. package/templates/webapp/src/services/DatabaseService.ts +3 -51
  31. package/templates/webapp/src/services/observability/initFaro.ts +5 -17
  32. package/dist/register-app-BeSQEsel.js.map +0 -1
  33. package/templates/monorepo/scripts/setup-local-databases.mjs +0 -183
  34. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +0 -179
  35. package/templates/webapp/src/app/(auth)/auth/signup/CredentialsSignUpForm.tsx +0 -135
  36. package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +0 -53
  37. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +0 -25
  38. package/templates/webapp/src/lib/auth/app-auth-mode.ts +0 -20
@@ -1,179 +0,0 @@
1
- "use client";
2
-
3
- import { zodResolver } from "@hookform/resolvers/zod";
4
- import { Button, Input } from "@percepta/design";
5
- import { ArrowRight } from "lucide-react";
6
- import Link from "next/link";
7
- import { useRouter, useSearchParams } from "next/navigation";
8
- import React, { useCallback, useState } from "react";
9
- import { Controller, useForm } from "react-hook-form";
10
- import { toast } from "sonner";
11
- import z from "zod";
12
- import { FormItem } from "../../../../components/form/FormItem";
13
- import { IS_DEV } from "../../../../config/isDev";
14
- import { authClient } from "../../../../lib/auth-client";
15
-
16
- const CREDENTIALS_SCHEMA = z.object({
17
- email: z.string().min(1, "Email is required"),
18
- password: z.string().min(1, "Password is required"),
19
- });
20
-
21
- type Credentials = z.infer<typeof CREDENTIALS_SCHEMA>;
22
- type AuthMode = "username-password" | "google" | "okta";
23
-
24
- // Defaults are empty in production so deployed instances don't suggest seeded
25
- // credentials. In dev we prefill with the seeded user from `pnpm db:seed` so a
26
- // fresh scaffold is one click away from authed.
27
- const DEFAULTS: Credentials = IS_DEV
28
- ? { email: "app-admin@example.com", password: "password" }
29
- : { email: "", password: "" };
30
-
31
- export function CredentialsSignInForm({ authMode }: { authMode: AuthMode }) {
32
- const router = useRouter();
33
- const searchParams = useSearchParams();
34
- const [isProviderSubmitting, setIsProviderSubmitting] = useState(false);
35
-
36
- const {
37
- control,
38
- formState: { isSubmitting },
39
- handleSubmit,
40
- } = useForm<Credentials>({
41
- resolver: zodResolver(CREDENTIALS_SCHEMA),
42
- defaultValues: DEFAULTS,
43
- });
44
-
45
- const callbackUrl = searchParams.get("callbackUrl") ?? "/";
46
- const usesCredentials = authMode === "username-password";
47
- const providerName = authMode === "google" ? "Google" : "Okta";
48
-
49
- const submit = useCallback(
50
- async ({ email, password }: Credentials): Promise<void> => {
51
- const { error } = await authClient.signIn.email({
52
- email,
53
- password,
54
- callbackURL: callbackUrl,
55
- });
56
-
57
- if (error != null) {
58
- toast.error(
59
- error.message ??
60
- "Invalid email or password. Please check your credentials and try again.",
61
- );
62
- return;
63
- }
64
-
65
- router.push(callbackUrl);
66
- router.refresh();
67
- },
68
- [callbackUrl, router],
69
- );
70
-
71
- const signInWithProvider = useCallback(async (): Promise<void> => {
72
- setIsProviderSubmitting(true);
73
- try {
74
- const { error } =
75
- authMode === "google"
76
- ? await authClient.signIn.social({
77
- provider: "google",
78
- callbackURL: callbackUrl,
79
- })
80
- : await authClient.signIn.oauth2({
81
- providerId: "okta",
82
- callbackURL: callbackUrl,
83
- });
84
-
85
- if (error != null) {
86
- toast.error(error.message ?? `Unable to sign in with ${providerName}.`);
87
- }
88
- } finally {
89
- setIsProviderSubmitting(false);
90
- }
91
- }, [authMode, callbackUrl, providerName]);
92
-
93
- return (
94
- <div className="grid gap-8">
95
- <div className="grid gap-3">
96
- <p className="app-auth-kicker">
97
- <span>01</span>
98
- <span>Sign in</span>
99
- </p>
100
- <h1 className="app-auth-title">Welcome back.</h1>
101
- {usesCredentials ? (
102
- <p className="app-auth-copy text-sm">
103
- Need an account?{" "}
104
- <Link
105
- className="app-auth-link"
106
- href={`/auth/signup?callbackUrl=${encodeURIComponent(callbackUrl)}`}
107
- >
108
- Create one
109
- </Link>
110
- </p>
111
- ) : (
112
- <p className="app-auth-copy text-sm">
113
- Use your {providerName} account to continue.
114
- </p>
115
- )}
116
- </div>
117
- {usesCredentials ? (
118
- <form className="app-auth-form" onSubmit={handleSubmit(submit)}>
119
- <div className="app-auth-fields">
120
- <Controller
121
- control={control}
122
- name="email"
123
- render={({ field, fieldState }) => (
124
- <FormItem
125
- className="app-auth-field"
126
- label="Email"
127
- fieldState={fieldState}
128
- >
129
- <Input {...field} type="email" autoComplete="email" />
130
- </FormItem>
131
- )}
132
- />
133
- <Controller
134
- control={control}
135
- name="password"
136
- render={({ field, fieldState }) => (
137
- <FormItem
138
- className="app-auth-field"
139
- label="Password"
140
- fieldState={fieldState}
141
- >
142
- <Input
143
- {...field}
144
- type="password"
145
- autoComplete="current-password"
146
- />
147
- </FormItem>
148
- )}
149
- />
150
- </div>
151
- <div className="flex justify-end">
152
- <Button
153
- className="app-auth-submit"
154
- type="submit"
155
- loading={isSubmitting}
156
- >
157
- <span>Sign In</span>
158
- <ArrowRight aria-hidden={true} />
159
- </Button>
160
- </div>
161
- </form>
162
- ) : (
163
- <div className="app-auth-form">
164
- <div className="flex justify-end">
165
- <Button
166
- className="app-auth-submit"
167
- type="button"
168
- loading={isProviderSubmitting}
169
- onClick={signInWithProvider}
170
- >
171
- <span>Continue with {providerName}</span>
172
- <ArrowRight aria-hidden={true} />
173
- </Button>
174
- </div>
175
- </div>
176
- )}
177
- </div>
178
- );
179
- }
@@ -1,135 +0,0 @@
1
- "use client";
2
-
3
- import { zodResolver } from "@hookform/resolvers/zod";
4
- import { Button, Input } from "@percepta/design";
5
- import { ArrowRight } from "lucide-react";
6
- import Link from "next/link";
7
- import { useRouter, useSearchParams } from "next/navigation";
8
- import React, { useCallback } from "react";
9
- import { Controller, useForm } from "react-hook-form";
10
- import { toast } from "sonner";
11
- import z from "zod";
12
- import { FormItem } from "../../../../components/form/FormItem";
13
- import { authClient } from "../../../../lib/auth-client";
14
-
15
- const CREDENTIALS_SCHEMA = z.object({
16
- name: z.string().min(1, "Name is required"),
17
- email: z.string().email("Enter a valid email address"),
18
- password: z.string().min(8, "Password must be at least 8 characters"),
19
- });
20
-
21
- type Credentials = z.infer<typeof CREDENTIALS_SCHEMA>;
22
-
23
- export function CredentialsSignUpForm() {
24
- const router = useRouter();
25
- const searchParams = useSearchParams();
26
-
27
- const {
28
- control,
29
- formState: { isSubmitting },
30
- handleSubmit,
31
- } = useForm<Credentials>({
32
- resolver: zodResolver(CREDENTIALS_SCHEMA),
33
- defaultValues: {
34
- name: "",
35
- email: "",
36
- password: "",
37
- },
38
- });
39
-
40
- const callbackUrl = searchParams.get("callbackUrl") ?? "/";
41
-
42
- const submit = useCallback(
43
- async ({ name, email, password }: Credentials): Promise<void> => {
44
- const { error } = await authClient.signUp.email({
45
- name,
46
- email,
47
- password,
48
- callbackURL: callbackUrl,
49
- });
50
-
51
- if (error != null) {
52
- toast.error(error.message ?? "Unable to create account.");
53
- return;
54
- }
55
-
56
- router.push(callbackUrl);
57
- router.refresh();
58
- },
59
- [callbackUrl, router],
60
- );
61
-
62
- return (
63
- <div className="grid gap-8">
64
- <div className="grid gap-3">
65
- <p className="app-auth-kicker">
66
- <span>01</span>
67
- <span>Request access</span>
68
- </p>
69
- <h1 className="app-auth-title">Create account.</h1>
70
- <p className="app-auth-copy text-sm">
71
- Already have an account?{" "}
72
- <Link
73
- className="app-auth-link"
74
- href={`/auth/signin?callbackUrl=${encodeURIComponent(callbackUrl)}`}
75
- >
76
- Sign in
77
- </Link>
78
- </p>
79
- </div>
80
- <form className="app-auth-form" onSubmit={handleSubmit(submit)}>
81
- <div className="app-auth-fields">
82
- <Controller
83
- control={control}
84
- name="name"
85
- render={({ field, fieldState }) => (
86
- <FormItem
87
- className="app-auth-field"
88
- label="Name"
89
- fieldState={fieldState}
90
- >
91
- <Input {...field} autoComplete="name" />
92
- </FormItem>
93
- )}
94
- />
95
- <Controller
96
- control={control}
97
- name="email"
98
- render={({ field, fieldState }) => (
99
- <FormItem
100
- className="app-auth-field"
101
- label="Email"
102
- fieldState={fieldState}
103
- >
104
- <Input {...field} type="email" autoComplete="email" />
105
- </FormItem>
106
- )}
107
- />
108
- <Controller
109
- control={control}
110
- name="password"
111
- render={({ field, fieldState }) => (
112
- <FormItem
113
- className="app-auth-field"
114
- label="Password"
115
- fieldState={fieldState}
116
- >
117
- <Input {...field} type="password" autoComplete="new-password" />
118
- </FormItem>
119
- )}
120
- />
121
- </div>
122
- <div className="flex justify-end">
123
- <Button
124
- className="app-auth-submit"
125
- type="submit"
126
- loading={isSubmitting}
127
- >
128
- <span>Create Account</span>
129
- <ArrowRight aria-hidden={true} />
130
- </Button>
131
- </div>
132
- </form>
133
- </div>
134
- );
135
- }
@@ -1,53 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { Suspense } from "react";
4
- import { AUTH_MODE, getServerSession } from "../../../../lib/auth";
5
- import { CredentialsSignUpForm } from "./CredentialsSignUpForm";
6
-
7
- type SearchParamValue = string | string[] | undefined;
8
-
9
- export const metadata: Metadata = {
10
- title: "Create Account — __APP_TITLE__",
11
- };
12
-
13
- function getFirstSearchParam(value: SearchParamValue): string | undefined {
14
- return Array.isArray(value) ? value[0] : value;
15
- }
16
-
17
- function getSignInPath(searchParams: Record<string, SearchParamValue>): string {
18
- const callbackUrl = getFirstSearchParam(searchParams.callbackUrl);
19
- if (callbackUrl == null || callbackUrl.length === 0) {
20
- return "/auth/signin";
21
- }
22
-
23
- return `/auth/signin?callbackUrl=${encodeURIComponent(callbackUrl)}`;
24
- }
25
-
26
- export default async function SignUpPage({
27
- searchParams,
28
- }: {
29
- searchParams?:
30
- | Promise<Record<string, SearchParamValue>>
31
- | Record<string, SearchParamValue>;
32
- } = {}) {
33
- const resolvedSearchParams = (await searchParams) ?? {};
34
- const session = await getServerSession();
35
-
36
- if (session?.user != null) {
37
- redirect("/");
38
- }
39
-
40
- if (AUTH_MODE !== "username-password") {
41
- redirect(getSignInPath(resolvedSearchParams));
42
- }
43
-
44
- return (
45
- <Suspense
46
- fallback={
47
- <p className="text-center text-sm text-muted-foreground">Loading...</p>
48
- }
49
- >
50
- <CredentialsSignUpForm />
51
- </Suspense>
52
- );
53
- }
@@ -1,25 +0,0 @@
1
- import { customType } from "drizzle-orm/pg-core";
2
- import type { z } from "zod";
3
-
4
- export function jsonbFromZod<TValue>(schema: z.Schema<TValue>): ReturnType<
5
- typeof customType<{
6
- data: TValue;
7
- driverData: string;
8
- }>
9
- > {
10
- return customType<{
11
- data: TValue;
12
- driverData: string;
13
- }>({
14
- dataType() {
15
- return "jsonb";
16
- },
17
- toDriver(value) {
18
- return JSON.stringify(schema.parse(value));
19
- },
20
- fromDriver(raw) {
21
- const parsed = schema.parse(raw);
22
- return parsed;
23
- },
24
- });
25
- }
@@ -1,20 +0,0 @@
1
- export type AuthMode = "username-password" | "google" | "okta";
2
-
3
- const APP_AUTH_MODE: AuthMode = "__AUTH_MODE__" as AuthMode;
4
-
5
- function isAuthMode(value: string | undefined): value is AuthMode {
6
- return (
7
- value === "username-password" || value === "google" || value === "okta"
8
- );
9
- }
10
-
11
- export function ensureAppAuthModeEnv(): AuthMode {
12
- if (isAuthMode(process.env.AUTH_MODE)) {
13
- return process.env.AUTH_MODE;
14
- }
15
-
16
- process.env.AUTH_MODE = APP_AUTH_MODE;
17
- return APP_AUTH_MODE;
18
- }
19
-
20
- export const AUTH_MODE = ensureAppAuthModeEnv();