@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.
- package/dist/index.js +24 -152
- package/dist/index.js.map +1 -1
- package/dist/{register-app-BeSQEsel.js → register-app-BtvxQeo0.js} +91 -1
- package/dist/register-app-BtvxQeo0.js.map +1 -0
- package/package.json +5 -2
- package/template-versions.json +2 -2
- package/templates/monorepo/README.md +28 -1
- package/templates/monorepo/auth/package.json +1 -1
- package/templates/monorepo/auth/src/auth.ts +7 -103
- package/templates/monorepo/authentik/blueprints/local-dev.yaml +123 -0
- package/templates/monorepo/authentik/initdb/00-authentik.sql +5 -0
- package/templates/monorepo/docker-compose.yml +70 -0
- package/templates/monorepo/oxlint.config.ts.template +5 -1
- package/templates/monorepo/package.json.template +2 -1
- package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +22 -89
- package/templates/webapp/AGENTS.md +4 -4
- package/templates/webapp/README.md +22 -33
- package/templates/webapp/agent-skills/langfuse.md +8 -11
- package/templates/webapp/agent-skills/oneshot.md +1 -1
- package/templates/webapp/e2e/rbac.spec.ts +28 -4
- package/templates/webapp/env.example.template +8 -12
- package/templates/webapp/package.json.template +4 -5
- package/templates/webapp/scripts/seed.ts +28 -52
- package/templates/webapp/scripts/with-local-env.ts +12 -64
- package/templates/webapp/src/app/(auth)/auth/signin/SignInForm.tsx +59 -0
- package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +3 -3
- package/templates/webapp/src/drizzle/db.ts +5 -9
- package/templates/webapp/src/instrumentation.ts +5 -72
- package/templates/webapp/src/lib/auth/index.ts +1 -2
- package/templates/webapp/src/services/DatabaseService.ts +3 -51
- package/templates/webapp/src/services/observability/initFaro.ts +5 -17
- package/dist/register-app-BeSQEsel.js.map +0 -1
- package/templates/monorepo/scripts/setup-local-databases.mjs +0 -183
- package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +0 -179
- package/templates/webapp/src/app/(auth)/auth/signup/CredentialsSignUpForm.tsx +0 -135
- package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +0 -53
- package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +0 -25
- 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();
|