create-croissant 0.1.56 → 0.1.58

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 (78) hide show
  1. package/dist/add.js +9 -9
  2. package/dist/index.js +8 -8
  3. package/package.json +1 -1
  4. package/template/README.md +10 -1
  5. package/template/apps/desktop/.editorconfig +9 -0
  6. package/template/apps/desktop/.prettierignore +6 -0
  7. package/template/apps/desktop/.prettierrc.yaml +4 -0
  8. package/template/apps/desktop/.vscode/extensions.json +3 -0
  9. package/template/apps/desktop/.vscode/launch.json +39 -0
  10. package/template/apps/desktop/.vscode/settings.json +11 -0
  11. package/template/apps/desktop/README.md +34 -0
  12. package/template/apps/desktop/build/entitlements.mac.plist +12 -0
  13. package/template/apps/desktop/build/icon.icns +0 -0
  14. package/template/apps/desktop/build/icon.ico +0 -0
  15. package/template/apps/desktop/build/icon.png +0 -0
  16. package/template/apps/desktop/dev-app-update.yml +3 -0
  17. package/template/apps/desktop/electron-builder.yml +45 -0
  18. package/template/apps/desktop/electron.vite.config.ts +16 -0
  19. package/template/apps/desktop/eslint.config.mjs +32 -0
  20. package/template/apps/desktop/package.json +57 -0
  21. package/template/apps/desktop/resources/icon.png +0 -0
  22. package/template/apps/desktop/src/main/index.ts +74 -0
  23. package/template/apps/desktop/src/preload/index.d.ts +8 -0
  24. package/template/apps/desktop/src/preload/index.ts +22 -0
  25. package/template/apps/desktop/src/renderer/index.html +17 -0
  26. package/template/apps/desktop/src/renderer/src/App.tsx +35 -0
  27. package/template/apps/desktop/src/renderer/src/assets/base.css +67 -0
  28. package/template/apps/desktop/src/renderer/src/assets/electron.svg +10 -0
  29. package/template/apps/desktop/src/renderer/src/assets/main.css +171 -0
  30. package/template/apps/desktop/src/renderer/src/assets/wavy-lines.svg +25 -0
  31. package/template/apps/desktop/src/renderer/src/components/Versions.tsx +15 -0
  32. package/template/apps/desktop/src/renderer/src/env.d.ts +1 -0
  33. package/template/apps/desktop/src/renderer/src/main.tsx +11 -0
  34. package/template/apps/desktop/tsconfig.json +4 -0
  35. package/template/apps/desktop/tsconfig.node.json +8 -0
  36. package/template/apps/desktop/tsconfig.web.json +19 -0
  37. package/template/apps/mobile/app/(tabs)/_layout.tsx +11 -10
  38. package/template/apps/mobile/app/(tabs)/explore.tsx +29 -27
  39. package/template/apps/mobile/app/(tabs)/index.tsx +25 -24
  40. package/template/apps/mobile/app/_layout.tsx +8 -8
  41. package/template/apps/mobile/app/modal.tsx +6 -6
  42. package/template/apps/mobile/components/external-link.tsx +5 -5
  43. package/template/apps/mobile/components/haptic-tab.tsx +4 -4
  44. package/template/apps/mobile/components/hello-wave.tsx +5 -4
  45. package/template/apps/mobile/components/parallax-scroll-view.tsx +15 -13
  46. package/template/apps/mobile/components/themed-text.tsx +14 -14
  47. package/template/apps/mobile/components/themed-view.tsx +3 -3
  48. package/template/apps/mobile/components/ui/collapsible.tsx +14 -13
  49. package/template/apps/mobile/components/ui/icon-symbol.ios.tsx +4 -4
  50. package/template/apps/mobile/components/ui/icon-symbol.tsx +9 -9
  51. package/template/apps/mobile/constants/theme.ts +19 -19
  52. package/template/apps/mobile/hooks/use-color-scheme.ts +1 -1
  53. package/template/apps/mobile/hooks/use-color-scheme.web.ts +3 -3
  54. package/template/apps/mobile/hooks/use-theme-color.ts +4 -4
  55. package/template/apps/mobile/package.json +7 -7
  56. package/template/apps/mobile/scripts/reset-project.js +2 -2
  57. package/template/apps/mobile/tsconfig.json +3 -13
  58. package/template/apps/platform/src/components/access-denied.tsx +32 -0
  59. package/template/apps/platform/src/components/app-sidebar.tsx +30 -50
  60. package/template/apps/platform/src/components/login-form.tsx +4 -1
  61. package/template/apps/platform/src/components/signup-form.tsx +15 -12
  62. package/template/apps/platform/src/lib/auth-utils.ts +17 -3
  63. package/template/apps/platform/src/routes/__root.tsx +8 -1
  64. package/template/apps/platform/src/routes/_auth/account.tsx +7 -9
  65. package/template/apps/platform/src/routes/_auth/dashboard.tsx +8 -7
  66. package/template/apps/platform/src/routes/_auth/examples/client-orpc-auth.tsx +6 -9
  67. package/template/apps/platform/src/routes/_auth/examples/ssr-orpc-auth.tsx +8 -9
  68. package/template/apps/platform/src/routes/_auth.tsx +2 -2
  69. package/template/apps/platform/src/routes/_public/examples/client-orpc.tsx +12 -3
  70. package/template/apps/platform/src/routes/_public/examples/ssr-orpc.tsx +2 -1
  71. package/template/apps/platform/src/routes/_public.tsx +2 -2
  72. package/template/apps/platform/src/routes/api/auth/$.ts +25 -15
  73. package/template/apps/platform/src/routes/api/rpc.$.ts +15 -8
  74. package/template/package.json +3 -9
  75. package/template/packages/auth/package.json +3 -2
  76. package/template/packages/auth/src/lib/auth.ts +11 -0
  77. package/template/packages/orpc/package.json +4 -4
  78. package/template/pnpm-workspace.yaml +5 -5
package/dist/add.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import s from"path";import{fileURLToPath as x}from"url";import i from"chalk";import{Command as k}from"commander";import{execa as y}from"execa";import t from"fs-extra";import m from"inquirer";import u from"ora";var v=x(import.meta.url),f=s.dirname(v),b=new k;b.name("croissant-add").description("Add a mobile app to an existing Croissant Stack project").action(async()=>{console.log(i.bold.yellow(`
2
+ import i from"path";import{fileURLToPath as P}from"url";import s from"chalk";import{Command as j}from"commander";import{execa as E}from"execa";import t from"fs-extra";import k from"inquirer";import g from"ora";var A=P(import.meta.url),x=i.dirname(A),v=new j;v.name("croissant-add").description("Add a mobile or desktop app to an existing Croissant Stack project").action(async()=>{console.log(s.bold.yellow(`
3
3
  \u{1F950} Croissant Stack: Add App
4
- `));let n=s.join(process.cwd(),"package.json");await t.pathExists(n)||(console.error(i.red(`
4
+ `));let p=i.join(process.cwd(),"package.json");await t.pathExists(p)||(console.error(s.red(`
5
5
  Error: package.json not found. Are you in the root of your project?
6
- `)),process.exit(1));let o=await t.readJson(n);(!o.scripts||!o.scripts.dev&&!o.scripts.build)&&(console.error(i.red(`
6
+ `)),process.exit(1));let e=await t.readJson(p);(!e.scripts||!e.scripts.dev&&!e.scripts.build)&&(console.error(s.red(`
7
7
  Error: This doesn't look like a Croissant Stack project.
8
- `)),process.exit(1));let c=s.join(process.cwd(),"apps/mobile");if(await t.pathExists(c)){console.log(i.blue(`Mobile app is already present. Nothing to add!
9
- `));return}let{confirm:h}=await m.prompt([{type:"confirm",name:"confirm",message:"Would you like to add the Mobile App (Expo)?",default:!0}]);if(!h)return;let r=process.cwd(),d=u("Adding mobile app...").start();try{let a=s.join(f,"..","template");await t.pathExists(a)||(a=s.resolve(f,"../../.."));let g=s.join(a,"apps/mobile");await t.copy(g,c),o.scripts=o.scripts||{},o.scripts["dev:mobile"]="turbo run dev --filter=mobile",o.scripts["dev:ios"]="turbo run dev --filter=mobile -- --ios",o.scripts["dev:android"]="turbo run dev --filter=mobile -- --android",o.scripts["build:mobile"]="turbo run build --filter=mobile",await t.writeJson(n,o,{spaces:2});let p=s.join(r,"packages/auth/src/lib/auth.ts");if(await t.pathExists(p)){let e=await t.readFile(p,"utf8");e.includes("@better-auth/expo")||(e=`import { expo } from "@better-auth/expo";
10
- ${e}`),e.includes("plugins: [expo()]")||(e=e.replace("emailAndPassword: { enabled: true },",`emailAndPassword: { enabled: true },
8
+ `)),process.exit(1));let u=i.join(process.cwd(),"apps/mobile"),h=i.join(process.cwd(),"apps/desktop"),b=await t.pathExists(u),f=await t.pathExists(h);if(b&&f){console.log(s.blue(`Both mobile and desktop apps are already present. Nothing to add!
9
+ `));return}let l=[];b||l.push({name:"Mobile App (Expo)",value:"mobile"}),f||l.push({name:"Desktop App (Tauri)",value:"desktop"});let{type:a}=await k.prompt([{type:"list",name:"type",message:"Which app would you like to add?",choices:l}]),c=process.cwd(),w=g(`Adding ${a} app...`).start();try{let r=i.join(x,"..","template");if(await t.pathExists(r)||(r=i.resolve(x,"../../..")),a==="mobile"){let n=i.join(r,"apps/mobile");await t.copy(n,u),e.scripts=e.scripts||{},e.scripts["dev:mobile"]="turbo run dev --filter=mobile",e.scripts["dev:ios"]="turbo run dev --filter=mobile -- --ios",e.scripts["dev:android"]="turbo run dev --filter=mobile -- --android",e.scripts["build:mobile"]="turbo run build --filter=mobile",await t.writeJson(p,e,{spaces:2});let d=i.join(c,"packages/auth/src/lib/auth.ts");if(await t.pathExists(d)){let o=await t.readFile(d,"utf8");o.includes("@better-auth/expo")||(o=`import { expo } from "@better-auth/expo";
10
+ ${o}`),o.includes("plugins: [expo()]")||(o=o.replace("emailAndPassword: { enabled: true },",`emailAndPassword: { enabled: true },
11
11
  plugins: [expo()],
12
12
  trustedOrigins: [
13
13
  "mobile://",
@@ -19,7 +19,7 @@ ${e}`),e.includes("plugins: [expo()]")||(e=e.replace("emailAndPassword: { enable
19
19
  "http://localhost:8081",
20
20
  ]
21
21
  : []),
22
- ],`)),await t.writeFile(p,e)}let l=s.join(r,"packages/auth/package.json");if(await t.pathExists(l)){let e=await t.readJson(l);e.dependencies=e.dependencies||{},e.dependencies["@better-auth/expo"]||(e.dependencies["@better-auth/expo"]="latest",await t.writeJson(l,e,{spaces:2}))}d.succeed(i.green("Mobile app added successfully!"));let{install:w}=await m.prompt([{type:"confirm",name:"install",message:"Would you like to install dependencies now?",default:!0}]);if(w){let e=u("Installing dependencies...").start();try{await y("pnpm",["install"],{cwd:r}),e.succeed(i.green("Dependencies installed!"))}catch{e.fail(i.red("Failed to install dependencies. Run pnpm install manually."))}}console.log(i.bold.cyan(`
23
- Next steps:`)),console.log(" pnpm run dev:mobile # Start Expo development server"),console.log(i.yellow(`
22
+ ],`)),await t.writeFile(d,o)}let m=i.join(c,"packages/auth/package.json");if(await t.pathExists(m)){let o=await t.readJson(m);o.dependencies=o.dependencies||{},o.dependencies["@better-auth/expo"]||(o.dependencies["@better-auth/expo"]="latest",await t.writeJson(m,o,{spaces:2}))}}else if(a==="desktop"){let n=i.join(r,"apps/desktop");await t.copy(n,h),e.scripts=e.scripts||{},e.scripts["dev:desktop"]="turbo run dev --filter=desktop",e.scripts["build:desktop"]="turbo run build --filter=desktop",await t.writeJson(p,e,{spaces:2})}w.succeed(s.green(`${a==="mobile"?"Mobile":"Desktop"} app added successfully!`));let{install:y}=await k.prompt([{type:"confirm",name:"install",message:"Would you like to install dependencies now?",default:!0}]);if(y){let n=g("Installing dependencies...").start();try{await E("pnpm",["install"],{cwd:c}),n.succeed(s.green("Dependencies installed!"))}catch{n.fail(s.red("Failed to install dependencies. Run pnpm install manually."))}}console.log(s.bold.cyan(`
23
+ Next steps:`)),console.log(a==="mobile"?" pnpm run dev:mobile # Start Expo development server":" pnpm run dev:desktop # Start Tauri development server"),console.log(s.yellow(`
24
24
  Happy hacking! \u{1F950}
25
- `))}catch(a){d.fail(i.red("An error occurred while adding the mobile app.")),console.error(a),process.exit(1)}});b.parse(process.argv);
25
+ `))}catch(r){w.fail(s.red(`An error occurred while adding the ${a} app.`)),console.error(r),process.exit(1)}});v.parse(process.argv);
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import n from"path";import{fileURLToPath as b}from"url";import r from"chalk";import{Command as k}from"commander";import{execa as y}from"execa";import e from"fs-extra";import P from"inquirer";import g from"ora";var j=b(import.meta.url),w=n.dirname(j),x=new k;x.name("create-croissant").description("Scaffold a new project using the Croissant Stack").argument("[project-name]","Name of the project").action(async m=>{console.log(r.bold.yellow(`
2
+ import i from"path";import{fileURLToPath as k}from"url";import p from"chalk";import{Command as b}from"commander";import{execa as P}from"execa";import e from"fs-extra";import y from"inquirer";import g from"ora";var j=k(import.meta.url),w=i.dirname(j),x=new b;x.name("create-croissant").description("Scaffold a new project using the Croissant Stack").argument("[project-name]","Name of the project").action(async u=>{console.log(p.bold.yellow(`
3
3
  \u{1F950} Welcome to the Croissant Stack!
4
- `));let u=await P.prompt([{type:"input",name:"name",message:"What is your project named?",default:m||"my-croissant-app",when:!m},{type:"confirm",name:"install",message:"Would you like to install dependencies?",default:!0},{type:"confirm",name:"mobile",message:"Would you like to include the mobile app (Expo)?",default:!0}]),p=m||u.name,s=n.resolve(process.cwd(),p);e.existsSync(s)&&(console.error(r.red(`
5
- Error: Directory ${p} already exists.
6
- `)),process.exit(1));let f=g("Scaffolding your project...").start();try{await e.ensureDir(s);let l=n.join(w,"..","template");if(await e.pathExists(l))await e.copy(l,s);else{let o=n.resolve(w,"../../.."),i=["node_modules","dist",".git","packages/create-croissant",".turbo","pnpm-lock.yaml"];await e.copy(o,s,{filter:t=>{let c=n.relative(o,t);return!i.some(a=>c.startsWith(a))}})}if(u.mobile){let o=n.join(s,"packages/auth/src/lib/auth.ts");if(await e.pathExists(o)){let t=await e.readFile(o,"utf8");t.includes("@better-auth/expo")||(t=`import { expo } from "@better-auth/expo";
4
+ `));let l=await y.prompt([{type:"input",name:"name",message:"What is your project named?",default:u||"my-croissant-app",when:!u},{type:"confirm",name:"install",message:"Would you like to install dependencies?",default:!0},{type:"confirm",name:"mobile",message:"Would you like to include the mobile app (Expo)?",default:!0},{type:"confirm",name:"desktop",message:"Would you like to include the desktop app (Tauri)?",default:!0}]),c=u||l.name,s=i.resolve(process.cwd(),c);e.existsSync(s)&&(console.error(p.red(`
5
+ Error: Directory ${c} already exists.
6
+ `)),process.exit(1));let f=g("Scaffolding your project...").start();try{await e.ensureDir(s);let d=i.join(w,"..","template");if(await e.pathExists(d))await e.copy(d,s);else{let a=i.resolve(w,"../../.."),n=["node_modules","dist",".git","packages/create-croissant",".turbo","pnpm-lock.yaml"];await e.copy(a,s,{filter:t=>{let r=i.relative(a,t);return!n.some(o=>r.startsWith(o))}})}if(l.mobile){let a=i.join(s,"packages/auth/src/lib/auth.ts");if(await e.pathExists(a)){let t=await e.readFile(a,"utf8");t.includes("@better-auth/expo")||(t=`import { expo } from "@better-auth/expo";
7
7
  ${t}`),t.includes("plugins: [expo()]")||(t=t.replace("emailAndPassword: { enabled: true },",`emailAndPassword: { enabled: true },
8
8
  plugins: [expo()],
9
9
  trustedOrigins: [
@@ -16,7 +16,7 @@ ${t}`),t.includes("plugins: [expo()]")||(t=t.replace("emailAndPassword: { enable
16
16
  "http://localhost:8081",
17
17
  ]
18
18
  : []),
19
- ],`)),await e.writeFile(o,t)}let i=n.join(s,"packages/auth/package.json");if(await e.pathExists(i)){let t=await e.readJson(i);t.dependencies=t.dependencies||{},t.dependencies["@better-auth/expo"]||(t.dependencies["@better-auth/expo"]="latest",await e.writeJson(i,t,{spaces:2}))}}else{let o=n.join(s,"apps/mobile");if(await e.pathExists(o)){await e.remove(o);let i=n.join(s,"package.json");if(await e.pathExists(i)){let a=await e.readJson(i);a.scripts&&Object.keys(a.scripts).forEach(d=>{(d.includes("mobile")||d.includes("ios")||d.includes("android"))&&delete a.scripts[d]}),await e.writeJson(i,a,{spaces:2})}let t=n.join(s,"packages/auth/src/lib/auth.ts");if(await e.pathExists(t)){let a=await e.readFile(t,"utf8");a=a.replace(/import \{ expo \} from "@better-auth\/expo";\n/,""),a=a.replace(/\s+plugins: \[expo\(\)\],/,""),a=a.replace(/\s+trustedOrigins: \[[\s\S]*?\],/,""),await e.writeFile(t,a)}let c=n.join(s,"packages/auth/package.json");if(await e.pathExists(c)){let a=await e.readJson(c);a.dependencies&&a.dependencies["@better-auth/expo"]&&(delete a.dependencies["@better-auth/expo"],await e.writeJson(c,a,{spaces:2}))}}}let h=n.join(s,"package.json");if(await e.pathExists(h)){let o=await e.readJson(h);o.name=p,o.version="0.1.0",delete o.private,await e.writeJson(h,o,{spaces:2})}if(f.succeed(r.green(`Project ${p} created at ${s}`)),u.install){let o=g("Installing dependencies...").start();try{await y("pnpm",["install"],{cwd:s}),o.succeed(r.green("Dependencies installed!"))}catch{o.fail(r.red("Failed to install dependencies. You may need to run pnpm install manually."))}}console.log(r.bold.cyan(`
20
- Next steps:`)),console.log(` cd ${p}`),console.log(" pnpm run db:up # Start your PostgreSQL database"),console.log(` pnpm run dev # Start development server
21
- `),console.log(r.yellow(`Happy hacking! \u{1F950}
22
- `))}catch(l){f.fail(r.red("An error occurred during scaffolding.")),console.error(l),process.exit(1)}});x.parse(process.argv);
19
+ ],`)),await e.writeFile(a,t)}let n=i.join(s,"packages/auth/package.json");if(await e.pathExists(n)){let t=await e.readJson(n);t.dependencies=t.dependencies||{},t.dependencies["@better-auth/expo"]||(t.dependencies["@better-auth/expo"]="latest",await e.writeJson(n,t,{spaces:2}))}}else{let a=i.join(s,"apps/mobile");if(await e.pathExists(a)){await e.remove(a);let n=i.join(s,"package.json");if(await e.pathExists(n)){let o=await e.readJson(n);o.scripts&&Object.keys(o.scripts).forEach(m=>{(m.includes("mobile")||m.includes("ios")||m.includes("android"))&&delete o.scripts[m]}),await e.writeJson(n,o,{spaces:2})}let t=i.join(s,"packages/auth/src/lib/auth.ts");if(await e.pathExists(t)){let o=await e.readFile(t,"utf8");o=o.replace(/import \{ expo \} from "@better-auth\/expo";\n/,""),o=o.replace(/\s+plugins: \[expo\(\)\],/,""),o=o.replace(/\s+trustedOrigins: \[[\s\S]*?\],/,""),await e.writeFile(t,o)}let r=i.join(s,"packages/auth/package.json");if(await e.pathExists(r)){let o=await e.readJson(r);o.dependencies&&o.dependencies["@better-auth/expo"]&&(delete o.dependencies["@better-auth/expo"],await e.writeJson(r,o,{spaces:2}))}}}if(!l.desktop){let a=i.join(s,"apps/desktop");if(await e.pathExists(a)){await e.remove(a);let n=i.join(s,"package.json");if(await e.pathExists(n)){let t=await e.readJson(n);t.scripts&&Object.keys(t.scripts).forEach(r=>{r.includes("desktop")&&delete t.scripts[r]}),await e.writeJson(n,t,{spaces:2})}}}let h=i.join(s,"package.json");if(await e.pathExists(h)){let a=await e.readJson(h);a.name=c,a.version="0.1.0",delete a.private,await e.writeJson(h,a,{spaces:2})}if(f.succeed(p.green(`Project ${c} created at ${s}`)),l.install){let a=g("Installing dependencies...").start();try{await P("pnpm",["install"],{cwd:s}),a.succeed(p.green("Dependencies installed!"))}catch{a.fail(p.red("Failed to install dependencies. You may need to run pnpm install manually."))}}console.log(p.bold.cyan(`
20
+ Next steps:`)),console.log(` cd ${c}`),console.log(" pnpm run db:up # Start your PostgreSQL database"),console.log(` pnpm run dev # Start development server
21
+ `),console.log(p.yellow(`Happy hacking! \u{1F950}
22
+ `))}catch(d){f.fail(p.red("An error occurred during scaffolding.")),console.error(d),process.exit(1)}});x.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-croissant",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
4
4
  "description": "Scaffold a new project using the Croissant Stack",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,12 +16,13 @@ pnpm dlx create-croissant@latest
16
16
 
17
17
  - **Web (Platform)**: [TanStack Start](https://tanstack.com/start) for a seamless, type-safe React experience.
18
18
  - **Mobile**: [Expo](https://expo.dev/) (React Native) for cross-platform mobile development.
19
+ - **Desktop**: [Tauri](https://tauri.app/) for native desktop applications using web technologies.
19
20
  - **Authentication**: [Better Auth](https://www.better-auth.com/) with Drizzle adapter and PostgreSQL.
20
21
  - **API**: [oRPC](https://orpc.sh/) with a modular, namespaced router for end-to-end type-safety.
21
22
  - **Database**: [Drizzle ORM](https://orm.drizzle.team/) with PostgreSQL and Docker Compose setup.
22
23
  - **Styling**: [shadcn/ui](https://ui.shadcn.com/) components with Tailwind CSS.
23
24
  - **Monorepo Management**: Powered by [pnpm](https://pnpm.io/) and [Turborepo](https://turbo.build/) for lightning-fast builds and smart task orchestration.
24
- - **Developer Experience**:
25
+ - **Developer Experience**:
25
26
  - Path aliases (`@/`) for clean imports.
26
27
  - Strict TypeScript across the entire stack.
27
28
  - Ultra-fast linting and formatting with [Oxc](https://oxc.rs/) (`oxlint` and `oxfmt`).
@@ -29,10 +30,13 @@ pnpm dlx create-croissant@latest
29
30
  ## 📁 Project Structure
30
31
 
31
32
  ### Apps
33
+
32
34
  - `apps/platform`: The main TanStack Start web application.
33
35
  - `apps/mobile`: Expo-powered mobile application.
36
+ - `apps/desktop`: Tauri-powered desktop application.
34
37
 
35
38
  ### Packages
39
+
36
40
  - `packages/auth` (`@workspace/auth`): Authentication logic and Better Auth configuration.
37
41
  - `packages/db` (`@workspace/db`): Database schema, migrations, and Drizzle client.
38
42
  - `packages/orpc` (`@workspace/orpc`): Type-safe API router and definitions.
@@ -96,6 +100,7 @@ The web application will be available at `https://platform.local`.
96
100
  All scripts are orchestrated by Turborepo. You can run them from the root directory:
97
101
 
98
102
  ### Core Scripts
103
+
99
104
  - `pnpm run dev`: Start all applications in development mode.
100
105
  - `pnpm run build`: Build all applications for production.
101
106
  - `pnpm run quality`: Run all quality checks (linting and formatting) using Oxc.
@@ -104,7 +109,9 @@ All scripts are orchestrated by Turborepo. You can run them from the root direct
104
109
  - `pnpm run ci`: Run linting, type-checking, and build (used in CI/CD).
105
110
 
106
111
  ### 🗄️ Database Scripts
112
+
107
113
  These handle Docker and Drizzle operations:
114
+
108
115
  - `pnpm run db:up`: Start the PostgreSQL Docker container.
109
116
  - `pnpm run db:down`: Stop and remove the database container.
110
117
  - `pnpm run db:logs`: Tail logs from the database container.
@@ -114,6 +121,7 @@ These handle Docker and Drizzle operations:
114
121
  ## 🎯 Turborepo Power
115
122
 
116
123
  ### Filtering Tasks
124
+
117
125
  Turbo allows you to run tasks for specific packages using the `--filter` flag:
118
126
 
119
127
  ```bash
@@ -125,6 +133,7 @@ pnpm run build --filter @workspace/db...
125
133
  ```
126
134
 
127
135
  ### Smart Caching
136
+
128
137
  Tasks like `build` and `lint` are cached. If the code hasn't changed, Turbo will replay the logs and output instantly.
129
138
 
130
139
  ## 🔗 oRPC & Type Safety
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
@@ -0,0 +1,6 @@
1
+ out
2
+ dist
3
+ pnpm-lock.yaml
4
+ LICENSE.md
5
+ tsconfig.json
6
+ tsconfig.*.json
@@ -0,0 +1,4 @@
1
+ singleQuote: true
2
+ semi: false
3
+ printWidth: 100
4
+ trailingComma: none
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["dbaeumer.vscode-eslint"]
3
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Debug Main Process",
6
+ "type": "node",
7
+ "request": "launch",
8
+ "cwd": "${workspaceRoot}",
9
+ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
10
+ "windows": {
11
+ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
12
+ },
13
+ "runtimeArgs": ["--sourcemap"],
14
+ "env": {
15
+ "REMOTE_DEBUGGING_PORT": "9222"
16
+ }
17
+ },
18
+ {
19
+ "name": "Debug Renderer Process",
20
+ "port": 9222,
21
+ "request": "attach",
22
+ "type": "chrome",
23
+ "webRoot": "${workspaceFolder}/src/renderer",
24
+ "timeout": 60000,
25
+ "presentation": {
26
+ "hidden": true
27
+ }
28
+ }
29
+ ],
30
+ "compounds": [
31
+ {
32
+ "name": "Debug All",
33
+ "configurations": ["Debug Main Process", "Debug Renderer Process"],
34
+ "presentation": {
35
+ "order": 1
36
+ }
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "[typescript]": {
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
4
+ },
5
+ "[javascript]": {
6
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
7
+ },
8
+ "[json]": {
9
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
10
+ }
11
+ }
@@ -0,0 +1,34 @@
1
+ # desktop
2
+
3
+ An Electron application with React and TypeScript
4
+
5
+ ## Recommended IDE Setup
6
+
7
+ - [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
8
+
9
+ ## Project Setup
10
+
11
+ ### Install
12
+
13
+ ```bash
14
+ $ pnpm install
15
+ ```
16
+
17
+ ### Development
18
+
19
+ ```bash
20
+ $ pnpm dev
21
+ ```
22
+
23
+ ### Build
24
+
25
+ ```bash
26
+ # For windows
27
+ $ pnpm build:win
28
+
29
+ # For macOS
30
+ $ pnpm build:mac
31
+
32
+ # For Linux
33
+ $ pnpm build:linux
34
+ ```
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.cs.allow-jit</key>
6
+ <true/>
7
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
8
+ <true/>
9
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
10
+ <true/>
11
+ </dict>
12
+ </plist>
@@ -0,0 +1,3 @@
1
+ provider: generic
2
+ url: https://example.com/auto-updates
3
+ updaterCacheDirName: desktop-updater
@@ -0,0 +1,45 @@
1
+ appId: com.electron.app
2
+ productName: desktop
3
+ directories:
4
+ buildResources: build
5
+ files:
6
+ - '!**/.vscode/*'
7
+ - '!src/*'
8
+ - '!electron.vite.config.{js,ts,mjs,cjs}'
9
+ - '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
10
+ - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
11
+ - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
12
+ asarUnpack:
13
+ - resources/**
14
+ win:
15
+ executableName: desktop
16
+ nsis:
17
+ artifactName: ${name}-${version}-setup.${ext}
18
+ shortcutName: ${productName}
19
+ uninstallDisplayName: ${productName}
20
+ createDesktopShortcut: always
21
+ mac:
22
+ entitlementsInherit: build/entitlements.mac.plist
23
+ extendInfo:
24
+ - NSCameraUsageDescription: Application requests access to the device's camera.
25
+ - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
26
+ - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
27
+ - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
28
+ notarize: false
29
+ dmg:
30
+ artifactName: ${name}-${version}.${ext}
31
+ linux:
32
+ target:
33
+ - AppImage
34
+ - snap
35
+ - deb
36
+ maintainer: electronjs.org
37
+ category: Utility
38
+ appImage:
39
+ artifactName: ${name}-${version}.${ext}
40
+ npmRebuild: false
41
+ publish:
42
+ provider: generic
43
+ url: https://example.com/auto-updates
44
+ electronDownload:
45
+ mirror: https://npmmirror.com/mirrors/electron/
@@ -0,0 +1,16 @@
1
+ import { resolve } from 'path'
2
+ import { defineConfig } from 'electron-vite'
3
+ import react from '@vitejs/plugin-react'
4
+
5
+ export default defineConfig({
6
+ main: {},
7
+ preload: {},
8
+ renderer: {
9
+ resolve: {
10
+ alias: {
11
+ '@renderer': resolve('src/renderer/src')
12
+ }
13
+ },
14
+ plugins: [react()]
15
+ }
16
+ })
@@ -0,0 +1,32 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import tseslint from '@electron-toolkit/eslint-config-ts'
3
+ import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier'
4
+ import eslintPluginReact from 'eslint-plugin-react'
5
+ import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
6
+ import eslintPluginReactRefresh from 'eslint-plugin-react-refresh'
7
+
8
+ export default defineConfig(
9
+ { ignores: ['**/node_modules', '**/dist', '**/out'] },
10
+ tseslint.configs.recommended,
11
+ eslintPluginReact.configs.flat.recommended,
12
+ eslintPluginReact.configs.flat['jsx-runtime'],
13
+ {
14
+ settings: {
15
+ react: {
16
+ version: 'detect'
17
+ }
18
+ }
19
+ },
20
+ {
21
+ files: ['**/*.{ts,tsx}'],
22
+ plugins: {
23
+ 'react-hooks': eslintPluginReactHooks,
24
+ 'react-refresh': eslintPluginReactRefresh
25
+ },
26
+ rules: {
27
+ ...eslintPluginReactHooks.configs.recommended.rules,
28
+ ...eslintPluginReactRefresh.configs.vite.rules
29
+ }
30
+ },
31
+ eslintConfigPrettier
32
+ )
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "desktop",
3
+ "version": "1.0.0",
4
+ "description": "An Electron application with React and TypeScript",
5
+ "main": "./out/main/index.js",
6
+ "author": "example.com",
7
+ "homepage": "https://electron-vite.org",
8
+ "scripts": {
9
+ "format": "prettier --write .",
10
+ "lint": "eslint --cache .",
11
+ "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
12
+ "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
13
+ "typecheck": "npm run typecheck:node && npm run typecheck:web",
14
+ "start": "electron-vite preview",
15
+ "dev": "electron-vite dev",
16
+ "build": "npm run typecheck && electron-vite build",
17
+ "postinstall": "electron-builder install-app-deps",
18
+ "build:unpack": "npm run build && electron-builder --dir",
19
+ "build:win": "npm run build && electron-builder --win",
20
+ "build:mac": "electron-vite build && electron-builder --mac",
21
+ "build:linux": "electron-vite build && electron-builder --linux"
22
+ },
23
+ "dependencies": {
24
+ "@better-auth/electron": "^1.6.11",
25
+ "@electron-toolkit/preload": "^3.0.2",
26
+ "@electron-toolkit/utils": "^4.0.0",
27
+ "better-auth": "1.6.11",
28
+ "electron-updater": "^6.3.9"
29
+ },
30
+ "devDependencies": {
31
+ "@electron-toolkit/eslint-config-prettier": "^3.0.0",
32
+ "@electron-toolkit/eslint-config-ts": "^3.1.0",
33
+ "@electron-toolkit/tsconfig": "^2.0.0",
34
+ "@types/node": "^22.19.1",
35
+ "@types/react": "^19.2.7",
36
+ "@types/react-dom": "^19.2.3",
37
+ "@vitejs/plugin-react": "^5.1.1",
38
+ "electron": "^39.2.6",
39
+ "electron-builder": "^26.0.12",
40
+ "electron-vite": "^5.0.0",
41
+ "eslint": "^9.39.1",
42
+ "eslint-plugin-react": "^7.37.5",
43
+ "eslint-plugin-react-hooks": "^7.0.1",
44
+ "eslint-plugin-react-refresh": "^0.4.24",
45
+ "prettier": "^3.7.4",
46
+ "react": "^19.2.1",
47
+ "react-dom": "^19.2.1",
48
+ "typescript": "^5.9.3",
49
+ "vite": "^7.2.6"
50
+ },
51
+ "pnpm": {
52
+ "onlyBuiltDependencies": [
53
+ "electron",
54
+ "esbuild"
55
+ ]
56
+ }
57
+ }
@@ -0,0 +1,74 @@
1
+ import { app, shell, BrowserWindow, ipcMain } from 'electron'
2
+ import { join } from 'path'
3
+ import { electronApp, optimizer, is } from '@electron-toolkit/utils'
4
+ import icon from '../../resources/icon.png?asset'
5
+
6
+ function createWindow(): void {
7
+ // Create the browser window.
8
+ const mainWindow = new BrowserWindow({
9
+ width: 900,
10
+ height: 670,
11
+ show: false,
12
+ autoHideMenuBar: true,
13
+ ...(process.platform === 'linux' ? { icon } : {}),
14
+ webPreferences: {
15
+ preload: join(__dirname, '../preload/index.js'),
16
+ sandbox: false
17
+ }
18
+ })
19
+
20
+ mainWindow.on('ready-to-show', () => {
21
+ mainWindow.show()
22
+ })
23
+
24
+ mainWindow.webContents.setWindowOpenHandler((details) => {
25
+ shell.openExternal(details.url)
26
+ return { action: 'deny' }
27
+ })
28
+
29
+ // HMR for renderer base on electron-vite cli.
30
+ // Load the remote URL for development or the local html file for production.
31
+ if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
32
+ mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
33
+ } else {
34
+ mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
35
+ }
36
+ }
37
+
38
+ // This method will be called when Electron has finished
39
+ // initialization and is ready to create browser windows.
40
+ // Some APIs can only be used after this event occurs.
41
+ app.whenReady().then(() => {
42
+ // Set app user model id for windows
43
+ electronApp.setAppUserModelId('com.electron')
44
+
45
+ // Default open or close DevTools by F12 in development
46
+ // and ignore CommandOrControl + R in production.
47
+ // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
48
+ app.on('browser-window-created', (_, window) => {
49
+ optimizer.watchWindowShortcuts(window)
50
+ })
51
+
52
+ // IPC test
53
+ ipcMain.on('ping', () => console.log('pong'))
54
+
55
+ createWindow()
56
+
57
+ app.on('activate', function () {
58
+ // On macOS it's common to re-create a window in the app when the
59
+ // dock icon is clicked and there are no other windows open.
60
+ if (BrowserWindow.getAllWindows().length === 0) createWindow()
61
+ })
62
+ })
63
+
64
+ // Quit when all windows are closed, except on macOS. There, it's common
65
+ // for applications and their menu bar to stay active until the user quits
66
+ // explicitly with Cmd + Q.
67
+ app.on('window-all-closed', () => {
68
+ if (process.platform !== 'darwin') {
69
+ app.quit()
70
+ }
71
+ })
72
+
73
+ // In this file you can include the rest of your app's specific main process
74
+ // code. You can also put them in separate files and require them here.
@@ -0,0 +1,8 @@
1
+ import { ElectronAPI } from '@electron-toolkit/preload'
2
+
3
+ declare global {
4
+ interface Window {
5
+ electron: ElectronAPI
6
+ api: unknown
7
+ }
8
+ }
@@ -0,0 +1,22 @@
1
+ import { contextBridge } from 'electron'
2
+ import { electronAPI } from '@electron-toolkit/preload'
3
+
4
+ // Custom APIs for renderer
5
+ const api = {}
6
+
7
+ // Use `contextBridge` APIs to expose Electron APIs to
8
+ // renderer only if context isolation is enabled, otherwise
9
+ // just add to the DOM global.
10
+ if (process.contextIsolated) {
11
+ try {
12
+ contextBridge.exposeInMainWorld('electron', electronAPI)
13
+ contextBridge.exposeInMainWorld('api', api)
14
+ } catch (error) {
15
+ console.error(error)
16
+ }
17
+ } else {
18
+ // @ts-ignore (define in dts)
19
+ window.electron = electronAPI
20
+ // @ts-ignore (define in dts)
21
+ window.api = api
22
+ }
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Electron</title>
6
+ <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
7
+ <meta
8
+ http-equiv="Content-Security-Policy"
9
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
10
+ />
11
+ </head>
12
+
13
+ <body>
14
+ <div id="root"></div>
15
+ <script type="module" src="/src/main.tsx"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,35 @@
1
+ import Versions from './components/Versions'
2
+ import electronLogo from './assets/electron.svg'
3
+
4
+ function App(): React.JSX.Element {
5
+ const ipcHandle = (): void => window.electron.ipcRenderer.send('ping')
6
+
7
+ return (
8
+ <>
9
+ <img alt="logo" className="logo" src={electronLogo} />
10
+ <div className="creator">Powered by electron-vite</div>
11
+ <div className="text">
12
+ Build an Electron app with <span className="react">React</span>
13
+ &nbsp;and <span className="ts">TypeScript</span>
14
+ </div>
15
+ <p className="tip">
16
+ Please try pressing <code>F12</code> to open the devTool
17
+ </p>
18
+ <div className="actions">
19
+ <div className="action">
20
+ <a href="https://electron-vite.org/" target="_blank" rel="noreferrer">
21
+ Documentation
22
+ </a>
23
+ </div>
24
+ <div className="action">
25
+ <a target="_blank" rel="noreferrer" onClick={ipcHandle}>
26
+ Send IPC
27
+ </a>
28
+ </div>
29
+ </div>
30
+ <Versions></Versions>
31
+ </>
32
+ )
33
+ }
34
+
35
+ export default App