create-pilotprojects-app 0.1.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 (96) hide show
  1. package/dist/helpers/copy.js +32 -0
  2. package/dist/helpers/install.js +18 -0
  3. package/dist/helpers/package-json.js +25 -0
  4. package/dist/index.js +54 -0
  5. package/dist/prompts.js +75 -0
  6. package/dist/scaffold.js +84 -0
  7. package/dist/types.js +1 -0
  8. package/package.json +39 -0
  9. package/templates/apps/mobile/.env.example +15 -0
  10. package/templates/apps/mobile/app/(auth)/login.tsx +86 -0
  11. package/templates/apps/mobile/app/(tabs)/_layout.tsx +10 -0
  12. package/templates/apps/mobile/app/(tabs)/index.tsx +30 -0
  13. package/templates/apps/mobile/app/_layout.tsx +58 -0
  14. package/templates/apps/mobile/app.config.ts +73 -0
  15. package/templates/apps/mobile/babel.config.js +9 -0
  16. package/templates/apps/mobile/eas.json +48 -0
  17. package/templates/apps/mobile/global.css +44 -0
  18. package/templates/apps/mobile/lib/supabase.ts +15 -0
  19. package/templates/apps/mobile/lib/trpc/client.ts +30 -0
  20. package/templates/apps/mobile/package.json +48 -0
  21. package/templates/apps/mobile/tsconfig.json +16 -0
  22. package/templates/apps/web/.env.development.example +22 -0
  23. package/templates/apps/web/app/(auth)/login/page.tsx +102 -0
  24. package/templates/apps/web/app/(dashboard)/dashboard/page.tsx +21 -0
  25. package/templates/apps/web/app/(dashboard)/layout.tsx +14 -0
  26. package/templates/apps/web/app/api/trpc/[trpc]/route.ts +32 -0
  27. package/templates/apps/web/app/globals.css +51 -0
  28. package/templates/apps/web/app/layout.tsx +25 -0
  29. package/templates/apps/web/lib/trpc/client.tsx +7 -0
  30. package/templates/apps/web/lib/trpc/provider.tsx +37 -0
  31. package/templates/apps/web/lib/trpc/server.ts +20 -0
  32. package/templates/apps/web/middleware.ts +10 -0
  33. package/templates/apps/web/package.json +44 -0
  34. package/templates/apps/web/postcss.config.js +6 -0
  35. package/templates/apps/web/sentry.client.config.ts +10 -0
  36. package/templates/apps/web/sentry.edge.config.ts +6 -0
  37. package/templates/apps/web/sentry.server.config.ts +7 -0
  38. package/templates/apps/web/tailwind.config.ts +12 -0
  39. package/templates/apps/web/tsconfig.json +16 -0
  40. package/templates/apps/web/types/css.d.ts +1 -0
  41. package/templates/base/.prettierrc.js +9 -0
  42. package/templates/base/_gitignore +30 -0
  43. package/templates/base/package.json +30 -0
  44. package/templates/base/turbo.json +33 -0
  45. package/templates/packages/api/package.json +23 -0
  46. package/templates/packages/api/src/features/auth/auth.router.ts +8 -0
  47. package/templates/packages/api/src/features/auth/auth.schema.ts +9 -0
  48. package/templates/packages/api/src/features/auth/auth.service.ts +24 -0
  49. package/templates/packages/api/src/features/user/user.router.ts +21 -0
  50. package/templates/packages/api/src/features/user/user.schema.ts +13 -0
  51. package/templates/packages/api/src/features/user/user.service.ts +41 -0
  52. package/templates/packages/api/src/index.ts +9 -0
  53. package/templates/packages/api/src/root.ts +10 -0
  54. package/templates/packages/api/src/trpc.ts +42 -0
  55. package/templates/packages/api/tsconfig.json +4 -0
  56. package/templates/packages/auth/package.json +26 -0
  57. package/templates/packages/auth/src/client.ts +8 -0
  58. package/templates/packages/auth/src/index.ts +3 -0
  59. package/templates/packages/auth/src/middleware.ts +25 -0
  60. package/templates/packages/auth/src/server.ts +15 -0
  61. package/templates/packages/auth/tsconfig.json +4 -0
  62. package/templates/packages/config/eslint/base.js +28 -0
  63. package/templates/packages/config/eslint/nextjs.js +10 -0
  64. package/templates/packages/config/eslint/react-native.js +18 -0
  65. package/templates/packages/config/package.json +13 -0
  66. package/templates/packages/config/typescript/base.json +17 -0
  67. package/templates/packages/config/typescript/nextjs.json +18 -0
  68. package/templates/packages/config/typescript/react-native.json +11 -0
  69. package/templates/packages/db/.env.example +1 -0
  70. package/templates/packages/db/drizzle.config.ts +13 -0
  71. package/templates/packages/db/package.json +28 -0
  72. package/templates/packages/db/src/index.ts +14 -0
  73. package/templates/packages/db/src/schema/index.ts +1 -0
  74. package/templates/packages/db/src/schema/users.ts +13 -0
  75. package/templates/packages/db/tsconfig.json +4 -0
  76. package/templates/packages/email/package.json +23 -0
  77. package/templates/packages/email/src/index.ts +2 -0
  78. package/templates/packages/email/src/resend.ts +8 -0
  79. package/templates/packages/email/src/templates/welcome.tsx +54 -0
  80. package/templates/packages/email/tsconfig.json +8 -0
  81. package/templates/packages/ui/package.json +38 -0
  82. package/templates/packages/ui/src/lib/utils.ts +6 -0
  83. package/templates/packages/ui/src/native/button.tsx +58 -0
  84. package/templates/packages/ui/src/native/card.tsx +41 -0
  85. package/templates/packages/ui/src/native/input.tsx +24 -0
  86. package/templates/packages/ui/src/native.d.ts +24 -0
  87. package/templates/packages/ui/src/tailwind-preset.ts +50 -0
  88. package/templates/packages/ui/src/web/button.tsx +49 -0
  89. package/templates/packages/ui/src/web/card.tsx +51 -0
  90. package/templates/packages/ui/src/web/input.tsx +22 -0
  91. package/templates/packages/ui/tsconfig.json +8 -0
  92. package/templates/packages/validators/package.json +20 -0
  93. package/templates/packages/validators/src/auth.ts +15 -0
  94. package/templates/packages/validators/src/index.ts +2 -0
  95. package/templates/packages/validators/src/user.ts +8 -0
  96. package/templates/packages/validators/tsconfig.json +4 -0
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "scripts": {
5
+ "build": "turbo build",
6
+ "dev": "turbo dev",
7
+ "lint": "turbo lint",
8
+ "type-check": "turbo type-check",
9
+ "test": "turbo test",
10
+ "format": "prettier --write \"**/*.{ts,tsx,md}\""
11
+ },
12
+ "devDependencies": {
13
+ "prettier": "^3.3.0",
14
+ "turbo": "^2.0.0",
15
+ "typescript": "^5.5.0"
16
+ },
17
+ "engines": {
18
+ "node": ">=20.0.0",
19
+ "pnpm": ">=9.0.0"
20
+ },
21
+ "packageManager": "pnpm@9.0.0",
22
+ "pnpm": {
23
+ "overrides": {
24
+ "nativewind": "~4.1.0",
25
+ "react-native-css-interop": "~0.1.0",
26
+ "react-native-screens": "~4.4.0",
27
+ "react-native-reanimated": "~3.16.0"
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "globalEnv": [
4
+ "NODE_ENV",
5
+ "SUPABASE_URL",
6
+ "SUPABASE_ANON_KEY",
7
+ "NEXT_PUBLIC_SUPABASE_URL",
8
+ "NEXT_PUBLIC_SUPABASE_ANON_KEY"
9
+ ],
10
+ "tasks": {
11
+ "build": {
12
+ "dependsOn": ["^build"],
13
+ "outputs": [".next/**", "dist/**", "!.next/cache/**"]
14
+ },
15
+ "dev": {
16
+ "cache": false,
17
+ "persistent": true
18
+ },
19
+ "lint": {
20
+ "dependsOn": ["^build"]
21
+ },
22
+ "type-check": {
23
+ "dependsOn": ["^build"]
24
+ },
25
+ "test": {
26
+ "dependsOn": ["^build"],
27
+ "outputs": ["coverage/**"]
28
+ },
29
+ "db:generate": { "cache": false },
30
+ "db:migrate": { "cache": false },
31
+ "db:studio": { "cache": false, "persistent": true }
32
+ }
33
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/api",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "exports": {
6
+ ".": "./src/index.ts"
7
+ },
8
+ "scripts": {
9
+ "type-check": "tsc --noEmit",
10
+ "lint": "eslint src"
11
+ },
12
+ "dependencies": {
13
+ "{{PACKAGE_SCOPE}}/db": "workspace:*",
14
+ "{{PACKAGE_SCOPE}}/validators": "workspace:*",
15
+ "@trpc/server": "^11.0.0",
16
+ "superjson": "^2.2.0",
17
+ "zod": "^3.23.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^20.0.0",
21
+ "typescript": "^5.5.0"
22
+ }
23
+ }
@@ -0,0 +1,8 @@
1
+ import { createTRPCRouter, protectedProcedure } from "../../trpc";
2
+ import { syncUserOnSignIn } from "./auth.service";
3
+
4
+ export const authRouter = createTRPCRouter({
5
+ syncUser: protectedProcedure.mutation(({ ctx }) =>
6
+ syncUserOnSignIn(ctx.db, ctx.user),
7
+ ),
8
+ });
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+
3
+ export const signUpSchema = z.object({
4
+ email: z.string().email(),
5
+ password: z.string().min(8),
6
+ displayName: z.string().min(1).max(50),
7
+ });
8
+
9
+ export type SignUpInput = z.infer<typeof signUpSchema>;
@@ -0,0 +1,24 @@
1
+ import { TRPCError } from "@trpc/server";
2
+
3
+ import type { Db } from "{{PACKAGE_SCOPE}}/db";
4
+
5
+ import { upsertUser } from "../user/user.service";
6
+
7
+ export async function syncUserOnSignIn(
8
+ db: Db,
9
+ user: { id: string; email?: string; user_metadata?: Record<string, unknown> },
10
+ ) {
11
+ if (!user.email) throw new TRPCError({ code: "BAD_REQUEST", message: "User has no email" });
12
+
13
+ const displayName =
14
+ typeof user.user_metadata?.["full_name"] === "string"
15
+ ? user.user_metadata["full_name"]
16
+ : user.email.split("@")[0];
17
+
18
+ return upsertUser(db, { id: user.id, email: user.email, displayName });
19
+ }
20
+
21
+ export async function requireAuth(userId: string | undefined): Promise<string> {
22
+ if (!userId) throw new TRPCError({ code: "UNAUTHORIZED" });
23
+ return userId;
24
+ }
@@ -0,0 +1,21 @@
1
+ import { createTRPCRouter, protectedProcedure } from "../../trpc";
2
+ import { getUserSchema, updateUserSchema } from "./user.schema";
3
+ import { deleteUser, getUserById, updateUser } from "./user.service";
4
+
5
+ export const userRouter = createTRPCRouter({
6
+ me: protectedProcedure.query(({ ctx }) =>
7
+ getUserById(ctx.db, ctx.user.id),
8
+ ),
9
+
10
+ byId: protectedProcedure
11
+ .input(getUserSchema)
12
+ .query(({ ctx, input }) => getUserById(ctx.db, input.userId)),
13
+
14
+ update: protectedProcedure
15
+ .input(updateUserSchema)
16
+ .mutation(({ ctx, input }) => updateUser(ctx.db, ctx.user.id, input)),
17
+
18
+ delete: protectedProcedure.mutation(({ ctx }) =>
19
+ deleteUser(ctx.db, ctx.user.id),
20
+ ),
21
+ });
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+
3
+ export const updateUserSchema = z.object({
4
+ displayName: z.string().min(1).max(50),
5
+ avatarUrl: z.string().url().optional(),
6
+ });
7
+
8
+ export const getUserSchema = z.object({
9
+ userId: z.string().uuid(),
10
+ });
11
+
12
+ export type UpdateUserInput = z.infer<typeof updateUserSchema>;
13
+ export type GetUserInput = z.infer<typeof getUserSchema>;
@@ -0,0 +1,41 @@
1
+ import { TRPCError } from "@trpc/server";
2
+ import { eq } from "drizzle-orm";
3
+
4
+ import { users, type Db } from "{{PACKAGE_SCOPE}}/db";
5
+
6
+ import type { UpdateUserInput } from "./user.schema";
7
+
8
+ export async function getUserById(db: Db, userId: string) {
9
+ const [user] = await db.select().from(users).where(eq(users.id, userId));
10
+ if (!user) throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
11
+ return user;
12
+ }
13
+
14
+ export async function updateUser(db: Db, userId: string, input: UpdateUserInput) {
15
+ const [updated] = await db
16
+ .update(users)
17
+ .set({ ...input, updatedAt: new Date() })
18
+ .where(eq(users.id, userId))
19
+ .returning();
20
+ if (!updated) throw new TRPCError({ code: "NOT_FOUND" });
21
+ return updated;
22
+ }
23
+
24
+ export async function deleteUser(db: Db, userId: string) {
25
+ await db.delete(users).where(eq(users.id, userId));
26
+ }
27
+
28
+ export async function upsertUser(
29
+ db: Db,
30
+ data: { id: string; email: string; displayName?: string },
31
+ ) {
32
+ const [user] = await db
33
+ .insert(users)
34
+ .values({ id: data.id, email: data.email, displayName: data.displayName })
35
+ .onConflictDoUpdate({
36
+ target: users.id,
37
+ set: { email: data.email, updatedAt: new Date() },
38
+ })
39
+ .returning();
40
+ return user!;
41
+ }
@@ -0,0 +1,9 @@
1
+ export { appRouter } from "./root";
2
+ export type { AppRouter } from "./root";
3
+ export type { TRPCContext, AuthedContext } from "./trpc";
4
+
5
+ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
6
+ import type { AppRouter } from "./root";
7
+
8
+ export type RouterInputs = inferRouterInputs<AppRouter>;
9
+ export type RouterOutputs = inferRouterOutputs<AppRouter>;
@@ -0,0 +1,10 @@
1
+ import { createTRPCRouter } from "./trpc";
2
+ import { authRouter } from "./features/auth/auth.router";
3
+ import { userRouter } from "./features/user/user.router";
4
+
5
+ export const appRouter = createTRPCRouter({
6
+ auth: authRouter,
7
+ user: userRouter,
8
+ });
9
+
10
+ export type AppRouter = typeof appRouter;
@@ -0,0 +1,42 @@
1
+ import { initTRPC, TRPCError } from "@trpc/server";
2
+ import superjson from "superjson";
3
+ import { ZodError } from "zod";
4
+
5
+ import type { db } from "{{PACKAGE_SCOPE}}/db";
6
+ import type { Session, User } from "@supabase/supabase-js";
7
+
8
+ export type TRPCContext = {
9
+ db: typeof db;
10
+ session: Session | null;
11
+ user: User | null;
12
+ };
13
+
14
+ export type AuthedContext = TRPCContext & {
15
+ session: Session;
16
+ user: User;
17
+ };
18
+
19
+ const t = initTRPC.context<TRPCContext>().create({
20
+ transformer: superjson,
21
+ errorFormatter({ shape, error }) {
22
+ return {
23
+ ...shape,
24
+ data: {
25
+ ...shape.data,
26
+ zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
27
+ },
28
+ };
29
+ },
30
+ });
31
+
32
+ export const createTRPCRouter = t.router;
33
+ export const publicProcedure = t.procedure;
34
+
35
+ const enforceAuth = t.middleware(({ ctx, next }) => {
36
+ if (!ctx.session || !ctx.user) {
37
+ throw new TRPCError({ code: "UNAUTHORIZED" });
38
+ }
39
+ return next({ ctx: { ...ctx, session: ctx.session, user: ctx.user } });
40
+ });
41
+
42
+ export const protectedProcedure = t.procedure.use(enforceAuth);
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "{{PACKAGE_SCOPE}}/config/typescript/base",
3
+ "include": ["src"]
4
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/auth",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./server": "./src/server.ts",
9
+ "./client": "./src/client.ts",
10
+ "./middleware": "./src/middleware.ts"
11
+ },
12
+ "scripts": {
13
+ "type-check": "tsc --noEmit",
14
+ "lint": "eslint src"
15
+ },
16
+ "dependencies": {
17
+ "@supabase/supabase-js": "^2.45.0",
18
+ "@supabase/ssr": "^0.5.0"
19
+ },
20
+ "devDependencies": {
21
+ "{{PACKAGE_SCOPE}}/config": "workspace:*",
22
+ "@types/node": "^20.0.0",
23
+ "next": "15.0.0",
24
+ "typescript": "^5.5.0"
25
+ }
26
+ }
@@ -0,0 +1,8 @@
1
+ import { createBrowserClient as _create } from "@supabase/ssr";
2
+
3
+ export function createBrowserClient() {
4
+ return _create(
5
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
7
+ );
8
+ }
@@ -0,0 +1,3 @@
1
+ export { createServerClient } from "./server";
2
+ export { createBrowserClient } from "./client";
3
+ export { updateSession } from "./middleware";
@@ -0,0 +1,25 @@
1
+ import { createServerClient, type CookieOptions } from "@supabase/ssr";
2
+ import { NextResponse, type NextRequest } from "next/server";
3
+
4
+ export async function updateSession(request: NextRequest) {
5
+ let response = NextResponse.next({ request });
6
+
7
+ const supabase = createServerClient(
8
+ process.env.SUPABASE_URL!,
9
+ process.env.SUPABASE_ANON_KEY!,
10
+ {
11
+ cookies: {
12
+ getAll: () => request.cookies.getAll(),
13
+ setAll: (cookiesToSet: { name: string; value: string; options: CookieOptions }[]) => {
14
+ cookiesToSet.forEach(({ name, value, options }) => {
15
+ request.cookies.set(name, value);
16
+ response.cookies.set(name, value, options);
17
+ });
18
+ },
19
+ },
20
+ },
21
+ );
22
+
23
+ await supabase.auth.getUser();
24
+ return response;
25
+ }
@@ -0,0 +1,15 @@
1
+ import { createServerClient as _create, type CookieOptions } from "@supabase/ssr";
2
+ import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
3
+
4
+ export function createServerClient(cookieStore: ReadonlyRequestCookies) {
5
+ return _create(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
6
+ cookies: {
7
+ getAll: () => cookieStore.getAll(),
8
+ setAll: (cookiesToSet: { name: string; value: string; options: CookieOptions }[]) => {
9
+ cookiesToSet.forEach(({ name, value, options }) =>
10
+ cookieStore.set(name, value, options),
11
+ );
12
+ },
13
+ },
14
+ });
15
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "{{PACKAGE_SCOPE}}/config/typescript/base",
3
+ "include": ["src"]
4
+ }
@@ -0,0 +1,28 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = {
3
+ extends: [
4
+ "eslint:recommended",
5
+ "plugin:@typescript-eslint/recommended",
6
+ "plugin:import/recommended",
7
+ "plugin:import/typescript",
8
+ "prettier",
9
+ ],
10
+ plugins: ["@typescript-eslint", "import"],
11
+ parser: "@typescript-eslint/parser",
12
+ rules: {
13
+ "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
14
+ "@typescript-eslint/consistent-type-imports": [
15
+ "error",
16
+ { prefer: "type-imports", fixStyle: "inline-type-imports" },
17
+ ],
18
+ "import/order": [
19
+ "error",
20
+ {
21
+ groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
22
+ "newlines-between": "always",
23
+ alphabetize: { order: "asc" },
24
+ },
25
+ ],
26
+ "no-console": ["warn", { allow: ["warn", "error"] }],
27
+ },
28
+ };
@@ -0,0 +1,10 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = {
3
+ extends: [
4
+ "./base.js",
5
+ "next/core-web-vitals",
6
+ ],
7
+ rules: {
8
+ "@next/next/no-html-link-for-pages": "off",
9
+ },
10
+ };
@@ -0,0 +1,18 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = {
3
+ extends: [
4
+ "./base.js",
5
+ "plugin:react/recommended",
6
+ "plugin:react-hooks/recommended",
7
+ "plugin:react-native/all",
8
+ ],
9
+ plugins: ["react", "react-hooks", "react-native"],
10
+ settings: {
11
+ react: { version: "detect" },
12
+ },
13
+ rules: {
14
+ "react/react-in-jsx-scope": "off",
15
+ "react-native/no-raw-text": "off",
16
+ "react-native/no-color-literals": "warn",
17
+ },
18
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/config",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "exports": {
6
+ "./eslint/base": "./eslint/base.js",
7
+ "./eslint/nextjs": "./eslint/nextjs.js",
8
+ "./eslint/react-native": "./eslint/react-native.js",
9
+ "./typescript/base": "./typescript/base.json",
10
+ "./typescript/nextjs": "./typescript/nextjs.json",
11
+ "./typescript/react-native": "./typescript/react-native.json"
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "allowImportingTsExtensions": true,
9
+ "noEmit": true,
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true
15
+ },
16
+ "exclude": ["node_modules", "dist"]
17
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "target": "ES2017",
6
+ "lib": ["dom", "dom.iterable", "ES2022"],
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "jsx": "preserve",
10
+ "allowJs": true,
11
+ "incremental": true,
12
+ "plugins": [{ "name": "next" }],
13
+ "paths": {
14
+ "@/*": ["./*"]
15
+ }
16
+ },
17
+ "exclude": ["node_modules", ".next", "dist"]
18
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./base.json",
4
+ "compilerOptions": {
5
+ "target": "ES2020",
6
+ "lib": ["ES2020"],
7
+ "jsx": "react-native",
8
+ "allowSyntheticDefaultImports": true
9
+ },
10
+ "exclude": ["node_modules", "dist", ".expo"]
11
+ }
@@ -0,0 +1 @@
1
+ DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres
@@ -0,0 +1,13 @@
1
+ import "dotenv/config";
2
+ import { defineConfig } from "drizzle-kit";
3
+
4
+ export default defineConfig({
5
+ schema: "./src/schema/index.ts",
6
+ out: "./src/migrations",
7
+ dialect: "postgresql",
8
+ dbCredentials: {
9
+ url: process.env.DATABASE_URL!,
10
+ },
11
+ verbose: true,
12
+ strict: true,
13
+ });
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/db",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./schema": "./src/schema/index.ts"
9
+ },
10
+ "scripts": {
11
+ "db:generate": "drizzle-kit generate",
12
+ "db:migrate": "drizzle-kit migrate",
13
+ "db:studio": "drizzle-kit studio",
14
+ "db:push": "drizzle-kit push",
15
+ "type-check": "tsc --noEmit"
16
+ },
17
+ "dependencies": {
18
+ "dotenv": "^16.4.0",
19
+ "drizzle-orm": "^0.33.0",
20
+ "postgres": "^3.4.0"
21
+ },
22
+ "devDependencies": {
23
+ "{{PACKAGE_SCOPE}}/config": "workspace:*",
24
+ "@types/node": "^20.0.0",
25
+ "drizzle-kit": "^0.24.0",
26
+ "typescript": "^5.5.0"
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ import { drizzle } from "drizzle-orm/postgres-js";
2
+ import postgres from "postgres";
3
+
4
+ import * as schema from "./schema";
5
+
6
+ const client = postgres(process.env.DATABASE_URL!, {
7
+ max: 1,
8
+ ssl: process.env.NODE_ENV === "production" ? "require" : false,
9
+ });
10
+
11
+ export const db = drizzle(client, { schema });
12
+
13
+ export type Db = typeof db;
14
+ export * from "./schema";
@@ -0,0 +1 @@
1
+ export * from "./users";
@@ -0,0 +1,13 @@
1
+ import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
2
+
3
+ export const users = pgTable("users", {
4
+ id: uuid("id").primaryKey(),
5
+ email: text("email").notNull().unique(),
6
+ displayName: text("display_name"),
7
+ avatarUrl: text("avatar_url"),
8
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
9
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
10
+ });
11
+
12
+ export type User = typeof users.$inferSelect;
13
+ export type NewUser = typeof users.$inferInsert;
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "{{PACKAGE_SCOPE}}/config/typescript/base",
3
+ "include": ["src"]
4
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/email",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "exports": {
6
+ ".": "./src/index.ts",
7
+ "./templates/*": "./src/templates/*.tsx"
8
+ },
9
+ "scripts": {
10
+ "email:dev": "react-email dev --dir src/templates --port 3100",
11
+ "type-check": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@react-email/components": "latest",
15
+ "react": "^18.3.0",
16
+ "react-email": "latest",
17
+ "resend": "^4.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^18.3.0",
21
+ "typescript": "^5.5.0"
22
+ }
23
+ }
@@ -0,0 +1,2 @@
1
+ export { resend, FROM } from "./resend";
2
+ export { WelcomeEmail } from "./templates/welcome";
@@ -0,0 +1,8 @@
1
+ import { Resend } from "resend";
2
+
3
+ export const resend = new Resend(process.env.RESEND_API_KEY);
4
+
5
+ export const FROM = {
6
+ noreply: "{{PROJECT_NAME}} <noreply@yourapp.com>",
7
+ support: "{{PROJECT_NAME}} Support <support@yourapp.com>",
8
+ } as const;