create-better-notify 0.0.2-alpha.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/LICENSE +21 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +30048 -0
- package/dist/templates/hono-orpc/.env.example +13 -0
- package/dist/templates/hono-orpc/_gitignore +3 -0
- package/dist/templates/hono-orpc/package.json +28 -0
- package/dist/templates/hono-orpc/src/emails/welcome.tsx +126 -0
- package/dist/templates/hono-orpc/src/notify.ts +50 -0
- package/dist/templates/hono-orpc/src/rpc.ts +31 -0
- package/dist/templates/hono-orpc/src/server.ts +27 -0
- package/dist/templates/hono-orpc/tsconfig.json +16 -0
- package/package.json +40 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tsx watch src/server.ts",
|
|
7
|
+
"start": "tsx src/server.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@betternotify/core": "0.0.4-alpha.0",
|
|
11
|
+
"@betternotify/email": "0.0.4-alpha.0",
|
|
12
|
+
"@betternotify/react-email": "0.0.4-alpha.0",
|
|
13
|
+
"@betternotify/resend": "0.0.4-alpha.0",
|
|
14
|
+
"@betternotify/smtp": "0.0.4-alpha.0",
|
|
15
|
+
"@hono/node-server": "2.0.1",
|
|
16
|
+
"@orpc/server": "1.14.1",
|
|
17
|
+
"hono": "4.12.17",
|
|
18
|
+
"react": "19.2.5",
|
|
19
|
+
"react-email": "6.0.8",
|
|
20
|
+
"zod": "4.4.3"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "25.6.0",
|
|
24
|
+
"@types/react": "19.2.14",
|
|
25
|
+
"tsx": "4.21.0",
|
|
26
|
+
"typescript": "6.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Button,
|
|
4
|
+
Container,
|
|
5
|
+
Head,
|
|
6
|
+
Heading,
|
|
7
|
+
Hr,
|
|
8
|
+
Html,
|
|
9
|
+
Link,
|
|
10
|
+
Preview,
|
|
11
|
+
Section,
|
|
12
|
+
Tailwind,
|
|
13
|
+
Text,
|
|
14
|
+
} from 'react-email';
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
name: string;
|
|
18
|
+
verifyUrl: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const theme = {
|
|
22
|
+
theme: {
|
|
23
|
+
extend: {
|
|
24
|
+
colors: {
|
|
25
|
+
navy: {
|
|
26
|
+
50: '#f7f6fe',
|
|
27
|
+
100: '#edeafc',
|
|
28
|
+
200: '#d9d4f9',
|
|
29
|
+
300: '#b8aef3',
|
|
30
|
+
400: '#8e7eeb',
|
|
31
|
+
500: '#5145c7',
|
|
32
|
+
600: '#3d2fb3',
|
|
33
|
+
700: '#2f2590',
|
|
34
|
+
800: '#1f1a64',
|
|
35
|
+
900: '#1a1552',
|
|
36
|
+
950: '#110f3a',
|
|
37
|
+
},
|
|
38
|
+
slate: {
|
|
39
|
+
50: '#fafafa',
|
|
40
|
+
100: '#f5f5f6',
|
|
41
|
+
200: '#eeeff0',
|
|
42
|
+
300: '#dcdde0',
|
|
43
|
+
400: '#a8a9ae',
|
|
44
|
+
500: '#838489',
|
|
45
|
+
600: '#626369',
|
|
46
|
+
700: '#484950',
|
|
47
|
+
800: '#2e2f34',
|
|
48
|
+
900: '#1c1d21',
|
|
49
|
+
950: '#121316',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const APP_NAME = '{{name}}';
|
|
57
|
+
|
|
58
|
+
export const Welcome = ({ name, verifyUrl }: Props) => {
|
|
59
|
+
return (
|
|
60
|
+
<Html>
|
|
61
|
+
<Head />
|
|
62
|
+
<Preview>Welcome! Verify your email to finish setting up your account.</Preview>
|
|
63
|
+
<Tailwind config={theme}>
|
|
64
|
+
<Body className="m-0 bg-slate-100 px-4 py-10 font-sans">
|
|
65
|
+
<Container className="mx-auto max-w-[560px] overflow-hidden rounded-2xl bg-white">
|
|
66
|
+
<Section className="bg-navy-600 px-8 py-6">
|
|
67
|
+
<Text className="m-0 text-sm font-bold uppercase tracking-[0.16em] text-white">
|
|
68
|
+
{APP_NAME}
|
|
69
|
+
</Text>
|
|
70
|
+
<Text className="mb-0 mt-2 text-sm text-navy-200">Account verification</Text>
|
|
71
|
+
</Section>
|
|
72
|
+
|
|
73
|
+
<Section className="px-8 pb-3 pt-10">
|
|
74
|
+
<Text className="m-0 inline-block rounded-full bg-navy-50 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-navy-600">
|
|
75
|
+
Verify your email
|
|
76
|
+
</Text>
|
|
77
|
+
<Heading className="mb-0 mt-6 text-3xl font-bold tracking-tight text-slate-950">
|
|
78
|
+
Welcome, {name}.
|
|
79
|
+
</Heading>
|
|
80
|
+
<Text className="mb-0 mt-4 text-base leading-7 text-slate-600">
|
|
81
|
+
Confirm your email address to finish setting up your account.
|
|
82
|
+
</Text>
|
|
83
|
+
</Section>
|
|
84
|
+
|
|
85
|
+
<Section className="px-8 py-7">
|
|
86
|
+
<Text className="m-0 text-base leading-7 text-slate-700">
|
|
87
|
+
Thanks for signing up. Verify your email address to activate your account and make
|
|
88
|
+
sure we can reach you with important updates.
|
|
89
|
+
</Text>
|
|
90
|
+
|
|
91
|
+
<Button
|
|
92
|
+
href={verifyUrl}
|
|
93
|
+
className="mt-8 rounded-xl bg-navy-600 px-8 py-4 text-center text-sm font-bold uppercase tracking-[0.16em] text-white"
|
|
94
|
+
>
|
|
95
|
+
Verify Email
|
|
96
|
+
</Button>
|
|
97
|
+
|
|
98
|
+
<Text className="mb-0 mt-7 text-sm leading-6 text-slate-500">
|
|
99
|
+
If the button does not work, paste this link into your browser:
|
|
100
|
+
</Text>
|
|
101
|
+
<Link
|
|
102
|
+
href={verifyUrl}
|
|
103
|
+
className="mt-2 inline-block break-all rounded-md bg-navy-50 px-1.5 py-1 text-sm font-medium text-navy-600 no-underline"
|
|
104
|
+
>
|
|
105
|
+
{verifyUrl}
|
|
106
|
+
</Link>
|
|
107
|
+
|
|
108
|
+
<Hr className="my-8 border-slate-200" />
|
|
109
|
+
|
|
110
|
+
<Text className="m-0 text-sm leading-6 text-slate-500">
|
|
111
|
+
If you did not create this account, you can safely ignore this message.
|
|
112
|
+
</Text>
|
|
113
|
+
</Section>
|
|
114
|
+
|
|
115
|
+
<Section className="bg-slate-50 px-8 py-7">
|
|
116
|
+
<Text className="m-0 text-sm font-semibold text-slate-700">{APP_NAME}</Text>
|
|
117
|
+
<Text className="mb-0 mt-4 text-xs leading-5 text-slate-500">
|
|
118
|
+
You are receiving this email because an account was created with this address.
|
|
119
|
+
</Text>
|
|
120
|
+
</Section>
|
|
121
|
+
</Container>
|
|
122
|
+
</Body>
|
|
123
|
+
</Tailwind>
|
|
124
|
+
</Html>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createNotify, createClient } from '@betternotify/core';
|
|
2
|
+
import { emailChannel } from '@betternotify/email';
|
|
3
|
+
import { multiTransport } from '@betternotify/core/transports';
|
|
4
|
+
import { smtpTransport } from '@betternotify/smtp';
|
|
5
|
+
import { resendTransport } from '@betternotify/resend';
|
|
6
|
+
import { reactEmail } from '@betternotify/react-email';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { Welcome } from './emails/welcome';
|
|
9
|
+
|
|
10
|
+
const transport = multiTransport({
|
|
11
|
+
name: 'failover',
|
|
12
|
+
strategy: 'failover',
|
|
13
|
+
transports: [
|
|
14
|
+
{
|
|
15
|
+
transport: smtpTransport({
|
|
16
|
+
host: process.env.SMTP_HOST!,
|
|
17
|
+
port: Number(process.env.SMTP_PORT ?? 587),
|
|
18
|
+
auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
transport: resendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const ch = emailChannel({
|
|
28
|
+
defaults: {
|
|
29
|
+
from: {
|
|
30
|
+
name: process.env.FROM_NAME ?? 'My App',
|
|
31
|
+
email: process.env.FROM_EMAIL ?? 'noreply@example.com',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const rpc = createNotify({ channels: { email: ch } });
|
|
37
|
+
|
|
38
|
+
export const catalog = rpc.catalog({
|
|
39
|
+
welcome: rpc
|
|
40
|
+
.email()
|
|
41
|
+
.input(z.object({ name: z.string(), verifyUrl: z.string().url() }))
|
|
42
|
+
.subject(({ input }) => `Welcome, ${input.name}!`)
|
|
43
|
+
.template(({ input }) => reactEmail(Welcome, input)),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const notificationService = createClient({
|
|
47
|
+
catalog,
|
|
48
|
+
channels: { email: ch },
|
|
49
|
+
transportsByChannel: { email: transport },
|
|
50
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { os } from '@orpc/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { notificationService } from './notify';
|
|
4
|
+
|
|
5
|
+
const injectNotify = os.middleware(async ({ next }) => {
|
|
6
|
+
return next({ context: { notificationService } });
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const base = os.use(injectNotify);
|
|
10
|
+
|
|
11
|
+
export const sendWelcome = base
|
|
12
|
+
.input(
|
|
13
|
+
z.object({
|
|
14
|
+
to: z.string().email(),
|
|
15
|
+
name: z.string(),
|
|
16
|
+
verifyUrl: z.string().url(),
|
|
17
|
+
}),
|
|
18
|
+
)
|
|
19
|
+
.handler(async ({ input, context }) => {
|
|
20
|
+
const result = await context.notificationService.welcome.send({
|
|
21
|
+
to: input.to,
|
|
22
|
+
input: { name: input.name, verifyUrl: input.verifyUrl },
|
|
23
|
+
});
|
|
24
|
+
return { messageId: result.messageId };
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const router = {
|
|
28
|
+
notification: {
|
|
29
|
+
sendWelcome,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { serve } from '@hono/node-server';
|
|
3
|
+
import { RPCHandler } from '@orpc/server/fetch';
|
|
4
|
+
import { router } from './rpc';
|
|
5
|
+
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
|
|
8
|
+
app.get('/', (c) => c.json({ status: 'ok', message: 'better-notify + hono + orpc' }));
|
|
9
|
+
|
|
10
|
+
const handler = new RPCHandler(router);
|
|
11
|
+
|
|
12
|
+
app.use('/rpc/*', async (c, next) => {
|
|
13
|
+
const { matched, response } = await handler.handle(c.req.raw, {
|
|
14
|
+
prefix: '/rpc',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (matched) {
|
|
18
|
+
return c.newResponse(response.body, response);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await next();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
25
|
+
|
|
26
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
27
|
+
serve({ fetch: app.fetch, port });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"jsx": "react-jsx"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"]
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-better-notify",
|
|
3
|
+
"version": "0.0.2-alpha.0",
|
|
4
|
+
"description": "Scaffold a better-notify project with your preferred framework and RPC layer.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/better-notify/better-notify",
|
|
9
|
+
"directory": "packages/create-better-notify"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"create-better-notify": "dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@clack/prompts": "1.3.0",
|
|
22
|
+
"incur": "0.4.5"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"rolldown": "1.0.0-rc.17",
|
|
26
|
+
"tsx": "4.21.0",
|
|
27
|
+
"typescript": "6.0.3",
|
|
28
|
+
"vitest": "2.1.9",
|
|
29
|
+
"@internal/rolldown-config": "0.0.0",
|
|
30
|
+
"@internal/tsconfig": "0.0.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "NODE_OPTIONS='--import tsx/esm' rolldown -c && node --import tsx/esm scripts/copy-templates.ts",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "vitest run"
|
|
39
|
+
}
|
|
40
|
+
}
|