create-tnt-stack 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 (30) hide show
  1. package/README.md +136 -0
  2. package/dist/index.js +54 -0
  3. package/package.json +73 -0
  4. package/template/base/README.md +36 -0
  5. package/template/base/_gitignore +41 -0
  6. package/template/base/eslint.config.mjs +16 -0
  7. package/template/base/next-env.d.ts +5 -0
  8. package/template/base/next.config.ts +7 -0
  9. package/template/base/package.json +27 -0
  10. package/template/base/postcss.config.mjs +5 -0
  11. package/template/base/public/favicon.ico +0 -0
  12. package/template/base/public/file.svg +1 -0
  13. package/template/base/public/globe.svg +1 -0
  14. package/template/base/public/next.svg +1 -0
  15. package/template/base/public/vercel.svg +1 -0
  16. package/template/base/public/window.svg +1 -0
  17. package/template/base/src/app/globals.css +39 -0
  18. package/template/base/src/app/layout.tsx +35 -0
  19. package/template/base/src/app/page.tsx +114 -0
  20. package/template/base/tsconfig.json +27 -0
  21. package/template/packages/prisma/schema/base.prisma +20 -0
  22. package/template/packages/prisma/schema/with-next-auth.prisma +85 -0
  23. package/template/packages/src/app/api/auth/[...nextauth]/route.ts +3 -0
  24. package/template/packages/src/env/with-db.js +44 -0
  25. package/template/packages/src/env/with-next-auth-db.js +52 -0
  26. package/template/packages/src/env/with-next-auth.js +51 -0
  27. package/template/packages/src/server/auth/config/next-auth-with-prisma.ts +60 -0
  28. package/template/packages/src/server/auth/config/next-auth.ts +57 -0
  29. package/template/packages/src/server/auth/next-auth.ts +10 -0
  30. package/template/packages/src/server/db/db-prisma.ts +23 -0
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://raw.githubusercontent.com/SlickYeet/create-tnt-stack/web/public/logo.light.png"
6
+ />
7
+ <img
8
+ src="https://raw.githubusercontent.com/SlickYeet/create-tnt-stack/web/public/logo.dark.png"
9
+ width="130"
10
+ alt="TNT-Powered logo"
11
+ />
12
+ </picture>
13
+
14
+ # 🚀 TNT-Powered
15
+
16
+ **The easiest way to scaffold a modern web app using the TNT-Powered stack.**
17
+
18
+ Simply run `npm create tnt-stack@latest` to get started!
19
+
20
+ [![PRs-Welcome][contribute-image]][contribute-url]
21
+ [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url]
22
+ [![License][license-image]][license-url]
23
+
24
+ </div>
25
+
26
+ ## 📚 Table of Contents
27
+
28
+ - <a href="#whats-included">What's Included</a>
29
+ - <a href="#getting-started">Getting Started</a>
30
+ - <a href="#features">Features</a>
31
+ - <a href="#documentation">Documentation</a>
32
+ - <a href="#credits">Credits</a>
33
+ - <a href="#contributing">Contributing</a>
34
+
35
+ <h2 id="whats-included">📦 What's Included</h2>
36
+
37
+ TNT-Powered installs **TypeScript, Next.js, Tailwind CSS**, along with:
38
+
39
+ - ✅ **Payload CMS** - A powerful headless CMS
40
+ - ✅ **NextAuth** - Authentication made easy
41
+ - ✅ **Lucia Auth** - Authentication following
42
+ [Luca Auth Guidelines](https://lucia-auth.com/)
43
+ - ✅ **Prisma ORM** - Database management with full type safety
44
+ - ✅ **Drizzle ORM** - (Coming soon) A powerful database ORM for TypeScript
45
+ - ✅ **And many more coming soon...**
46
+
47
+ <h2 id="getting-started">🚀 Getting Started</h2>
48
+
49
+ To create a new project, run the following command:
50
+
51
+ ### npm
52
+
53
+ ```bash
54
+ npm create tnt-stack@latest
55
+ ```
56
+
57
+ ### yarn
58
+
59
+ ```bash
60
+ yarn create tnt-stack
61
+ ```
62
+
63
+ ### pnpm
64
+
65
+ ```bash
66
+ pnpm create tnt-stack@latest
67
+ ```
68
+
69
+ ### bun
70
+
71
+ ```bash
72
+ bun create tnt-stack@latest
73
+ ```
74
+
75
+ For more information, visit the
76
+ [docs](https://create.tntstack.org/docs/installation).
77
+
78
+ <h2 id="features">🛠 Features</h2>
79
+
80
+ - 🎯 **Easy to Use** - Interactive CLI for selecting tools
81
+ - 📦 **Preconfigured Setup** - Comes ready with modern web dev tools
82
+ - 🚀 **Extendable** - More packages coming soon!
83
+
84
+ <h2 id="documentation">📖 Documentation</h2>
85
+
86
+ Full documentation is available at
87
+ [create.tntstack.org](https://create.tntstack.org/docs) (coming soon).
88
+
89
+ <h2 id="credits">❤️ Credits</h2>
90
+
91
+ Huge thanks to [Theo]() and the [T3 Stack]() for their amazing work. TNT-Powered
92
+ builds on the principles of create-t3-app, bringing even more flexibility.
93
+
94
+ If you like this project, giving it a ⭐ on GitHub!
95
+
96
+ <h2 id="contributing">Contributing</h2>
97
+
98
+ We 💖 contributors! Feel free to contribute to this project but **please read
99
+ the [Contributing Guidelines](CONTRIBUTING.md) before opening an issue or PR**
100
+ so you understand the branching strategy and local development environment.
101
+
102
+ <a href="https://github.com/slickyeet/create-tnt-stack/graphs/contributors">
103
+ <p align="center">
104
+ <img src="https://contrib.rocks/image?repo=slickyeet/create-tnt-stack" />
105
+ </p>
106
+ </a>
107
+
108
+ <p align="center">
109
+ Made with <a href="https://contrib.rocks" target="_blank" rel="noopener noreferrer">contrib.rocks</a>
110
+ </p>
111
+
112
+ <div align="center">
113
+ <a
114
+ href="https://vercel.com/?utm_source=famlam&utm_campaign=oss"
115
+ target="_blank"
116
+ rel="noopener noreferrer"
117
+ >
118
+ <img
119
+ height="34px"
120
+ src="https://www.datocms-assets.com/31049/1618983297-powered-by-vercel.svg"
121
+ alt="Powered by vercel"
122
+ />
123
+ </a>
124
+ </div>
125
+
126
+ [contribute-image]: https://img.shields.io/badge/PRs-welcome-blue.svg
127
+ [contribute-url]:
128
+ https://github.com/SlickYeet/create-tnt-stack/blob/main/CONTRIBUTING.md
129
+ [npm-image]:
130
+ https://img.shields.io/npm/v/create-tnt-stack?color=0b7285&logoColor=0b7285
131
+ [npm-url]: https://www.npmjs.com/package/create-tnt-stack
132
+ [license-image]:
133
+ https://img.shields.io/github/license/SlickYeet/create-tnt-stack?color=red
134
+ [license-url]: https://github.com/SlickYeet/create-tnt-stack/blob/main/LICENSE
135
+ [downloads-image]:
136
+ https://img.shields.io/npm/dm/tnt-stack?color=364fc7&logoColor=364fc7
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ #!/user/bin/env node
2
+ import ke from"path";import{execa as Qe}from"execa";import _e from"fs-extra";import*as c from"@clack/prompts";import{Command as De}from"commander";import B from"path";import{fileURLToPath as Ae}from"url";var Ie=Ae(import.meta.url),Se=B.dirname(Ie),u=B.join(Se,"../"),F=`
3
+ ___ ___ ___ _ _____ ___ _____ _ _ _____ _ ___ ___
4
+ / __| _ \\ __| /_\\_ _| __| |_ _| \\| |_ _| /_\\ | _ \\ _ \\
5
+ | (__| / _| / _ \\| | | _| | | | .\` | | | / _ \\| _/ _/
6
+ \\___|_|_\\___/_/ \\_\\_| |___| |_| |_|\\_| |_| /_/ \\_\\_| |_|
7
+ `,h="my-tnt-stack",G="create-tnt-stack";import Te from"node:crypto";import E from"path";import $ from"fs-extra";var H=({projectDir:e,packages:t,scopedAppName:n,databaseProvider:a})=>{let i=t?.nextAuth.inUse,o=t?.prisma.inUse,r=o,l=Ce(!!i,!!o,n,a),p="";if(r?i?p="with-next-auth-db.js":p="with-db.js":i&&(p="with-next-auth.js"),p!==""){let xe=E.join(u,"template/packages/src/env",p),Pe=E.join(e,"src/env.js");$.copyFileSync(xe,Pe)}let d=E.join(e,".env"),m=E.join(e,".env.example"),g=Ee+l,_=Buffer.from(Te.getRandomValues(new Uint8Array(32))).toString("base64"),D=l.replace('AUTH_SECRET=""',`AUTH_SECRET="${_}" # Generated by create-tnt-stack`);$.writeFileSync(d,D,"utf-8"),$.writeFileSync(m,g,"utf-8")};function Ce(e,t,n,a){let i=`
8
+ # When adding additional environment variables, the schema in "/src/env.js"
9
+ # should be updated accordingly.
10
+ `.trim().concat(`
11
+ `);return t&&(i+=`
12
+ # Prisma
13
+ # https://www.prisma.io/docs/reference/database-reference/connection-urls#env
14
+ `),t&&(a==="mysql"?i+=`DATABASE_URL="mysql://root:password@localhost:3306/${n}"`:a==="postgresql"?i+=`DATABASE_URL="postgresql://postgres:password@localhost:5432/${n}"`:a==="sqlite"&&(i+='DATABASE_URL="file:./db.sqlite"'),i+=`
15
+ `),e&&(i+=`
16
+ # Next Auth
17
+ # You can generate a new secret on the command line with:
18
+ # npx auth secret
19
+ # https://next-auth.js.org/configuration/options#secret
20
+ AUTH_SECRET=""
21
+
22
+ # Next Auth Discord Provider
23
+ AUTH_DISCORD_ID=""
24
+ AUTH_DISCORD_SECRET=""
25
+ `),!e&&!t&&(i+=`
26
+ # Example:
27
+ # SERVERVAR="foo"
28
+ # NEXT_PUBLIC_CLIENTVAR="bar"
29
+ `),i}var Ee=`
30
+ # Since the ".env" file is gitignored, you can use the ".env.example" file to
31
+ # build a new ".env" file when you clone the repo. Keep this file up-to-date
32
+ # when you add new variables to \`.env\`.
33
+
34
+ # This file will be committed to version control, so make sure not to have any
35
+ # secrets in it. If you are cloning this repo, create a copy of this file named
36
+ # ".env" and populate it with your secrets.
37
+ `.trim().concat(`
38
+
39
+ `);import y from"path";import j from"fs-extra";import Y from"path";import Q from"fs-extra";import Oe from"sort-package-json";var K={"next-auth":"^4.24.11","@auth/prisma-adapter":"^2.8.0",prisma:"^6.4.1","@prisma/client":"^6.4.1"};var A=e=>{let{dependencies:t,devMode:n,projectDir:a}=e,i=Q.readJsonSync(Y.join(a,"package.json"));t.forEach(r=>{let l=K[r];n&&i.devDependencies?i.devDependencies[r]=l:i.dependencies&&(i.dependencies[r]=l)});let o=Oe(i);Q.writeJsonSync(Y.join(a,"package.json"),o,{spaces:2})};var X=({projectDir:e,packages:t})=>{let n=t?.prisma.inUse,a=["next-auth"];n&&a.push("@auth/prisma-adapter"),A({projectDir:e,dependencies:a,devMode:!1});let i=y.join(u,"template/packages"),o="src/app/api/auth/[...nextauth]/route.ts",r=y.join(i,o),l=y.join(e,o),p=y.join(i,"src/server/auth/config",n?"next-auth-with-prisma.ts":"next-auth.ts"),d=y.join(e,"src/server/auth/config.ts"),m=y.join(i,"src/server/auth/next-auth.ts"),g=y.join(e,"src/server/auth/index.ts");j.copySync(r,l),j.copySync(p,d),j.copySync(m,g)};import b from"path";import x from"fs-extra";var Z=({projectDir:e,packages:t,databaseProvider:n})=>{A({projectDir:e,dependencies:["prisma"],devMode:!0}),A({projectDir:e,dependencies:["@prisma/client"],devMode:!1});let a=b.join(u,"template/packages"),i=b.join(a,"prisma/schema",`${t?.nextAuth.inUse?"with-next-auth":"base"}.prisma`),o=x.readFileSync(i,"utf-8");n!=="sqlite"&&(o=o.replace('provider = "sqlite"',`provider = "${{mysql:"mysql",postgresql:"postgresql"}[n]}"`),["mysql"].includes(n)&&(o=o.replace("// @db.Text","@db.Text")));let r=b.join(e,"prisma/schema.prisma");x.mkdirSync(b.dirname(r),{recursive:!0}),x.writeFileSync(r,o);let l=b.join(a,"src/server/db/db-prisma.ts"),p=b.join(e,"src/server/db/index.ts"),d=b.join(e,"package.json"),m=x.readJSONSync(d);m.scripts={...m.scripts,postinstall:"prisma generate","db:push":"prisma db push","db:studio":"prisma studio","db:generate":"prisma migrate dev","db:migrate":"prisma migrate deploy"},x.copy(l,p),x.writeJSONSync(d,m,{spaces:2})};var O=["sqlite","mysql","postgresql"],ee=e=>({nextAuth:{inUse:e.includes("nextAuth"),installer:X},prisma:{inUse:e.includes("prisma"),installer:Z},envVariables:{inUse:!0,installer:H}});import Ne from"path";import Me from"fs-extra";function P(){let e=Ne.join(u,"package.json");return Me.readJSONSync(e).version??"1.0.0"}var f=()=>{let e=process.env.npm_config_user_agent;return e?e.startsWith("yarn")?"yarn":e.startsWith("pnpm")?"pnpm":e.startsWith("bun")?"bun":"npm":"npm"};var I=class extends Error{constructor(t){super(t)}};import N from"chalk";var s={error(...e){console.log(N.red(...e))},warn(...e){console.log(N.yellow(...e))},info(...e){console.log(N.cyan(...e))},success(...e){console.log(N.green(...e))}};var M=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e);var Re=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;function te(e){let t=M(e),n=t.split("/"),a=n.findIndex(o=>o.startsWith("@")),i=n[n.length-1];if(n.findIndex(o=>o.startsWith("@"))!==-1&&(i=n.slice(a).join("/")),!(t=="."||Re.test(i??"")))return"App name must consist of only lowercase alphanumeric characters, '-', and '_'"}var ne=e=>{if(e.startsWith(".")||e.startsWith("/"))return"Import alias can't start with '.' or '/'"};var v={appName:h,packages:["prisma"],flags:{noGit:!1,noInstall:!1,default:!1,CI:!1,prisma:!1,nextAuth:!1,importAlias:"@/",dbProvider:"sqlite"},databaseProvider:"sqlite"};async function ae(){let e=v,t=new De().name(G).description("CLI for scaffolding new web apps with the TNT-Powered stack").version(P(),"-v, --version","Display the version number").argument("[dir]","The name of the application, as well as the name of the directory to create").option("--noGit","Explicitly tell the CLI to not initialize a new git repo in the project",!1).option("--noInstall","Explicitly tell the CLI to not run the package manager's install command",!1).option("-y, --default","Bypass the CLI and use all default options to bootstrap a new tnt-stack",!1).option("--CI","Boolean value if we're running in CI",!1).option("--nextAuth [boolean]","Experimental: Boolean value if we should install NextAuth.js. Must be used in conjunction with `--CI`.",a=>!!a&&a!=="false").option("--prisma [boolean]","Experimental: Boolean value if we should install Prisma. Must be used in conjunction with `--CI`.",a=>!!a&&a!=="false").option("-i, --import-alias","Explicitly tell the CLI to use a custom import alias",v.flags.importAlias).option("--dbProvider [provider]",`Choose a database provider to use. Possible values: ${O.join(", ")}`,v.flags.dbProvider).parse(process.argv);process.env.npm_config_user_agent?.startsWith("yarn/3")&&s.warn(` WARNING: It looks like you are using Yarn 3. This is currently not supported,
40
+ and likely to result in a crash. Please run create-tnt-stack with another
41
+ package manager such as pnpm, npm, or Yarn Classic.
42
+ See: https://github.com/t3-oss/create-t3-app/issues/57`);let n=t.args[0];if(n&&(e.appName=n),e.flags=t.opts(),e.flags.CI)return e.flags.nextAuth&&e.packages.push("nextAuth"),e.flags.prisma&&e.packages.push("prisma"),O.includes(e.flags.dbProvider)===!1&&(s.warn(`Incompatible database provided. Use: ${O.join(", ")}. Exiting.`),process.exit(0)),e.databaseProvider=e.packages.includes("prisma")?e.flags.dbProvider:"sqlite",e;if(e.flags.default)return e;try{if(process.env.TERM_PROGRAM?.toLowerCase().includes("mintty"))throw s.warn(` WARNING: It looks like you are using MinTTY, which is non-interactive. This is most likely because you are
43
+ using Git Bash. If that's that case, please use Git Bash from another terminal, such as Windows Terminal. Alternatively, you
44
+ can provide the arguments from the CLI directly: https://create.tntstack.org/installation#experimental-usage to skip the prompts.`),new I("Non-interactive environment");let a=f(),i=await c.group({...!n&&{name:()=>c.text({message:`What will your project be called? (e.g. ${h})`,defaultValue:n||h,validate:r=>te(r||h)})},authentication:()=>c.select({message:"What authentication provider would you like to use?",options:[{value:"none",label:"None"},{value:"nextAuth",label:"NextAuth.js"},{value:"lucia",label:"Lucia Auth (not a library)"}],initialValue:"none"}),database:()=>c.select({message:"What database ORM would you like to use?",options:[{value:"none",label:"None"},{value:"prisma",label:"Prisma"}],initialValue:"none"}),databaseProvider:({results:r})=>{if(r.database!=="none")return c.select({message:"",options:[{value:"sqlite",label:"SQLite (LibSQL)"},{value:"mysql",label:"MySQL"},{value:"postgresql",label:"PostgreSQL"}],initialValue:"sqlite"})},...!e.flags.noGit&&{git:()=>c.confirm({message:"Should be initialize a new Git repository and stage the changes?",initialValue:!v.flags.noGit})},...!e.flags.noInstall&&{install:()=>c.confirm({message:`Should we run '${a}`+(a==="yarn"?"'?":" install' for you?"),initialValue:!v.flags.noInstall})},importAlias:()=>c.text({message:"What import alias would you like to use?",defaultValue:v.flags.importAlias,placeholder:v.flags.importAlias,validate:ne})},{onCancel(){process.exit(1)}}),o=[];return i.authentication==="nextAuth"&&o.push("nextAuth"),i.database==="prisma"&&o.push("prisma"),{appName:i.name??e.appName,packages:o,databaseProvider:i.databaseProvider||"sqlite",flags:{...e.flags,noGit:!i.git||e.flags.noGit,noInstall:!i.install||e.flags.noInstall,importAlias:i.importAlias??e.flags.importAlias}}}catch(a){if(a instanceof I)s.warn(`${G} needs an interactive terminal to run.`),await c.confirm({message:"Continue scaffolding with default options?",initialValue:!0})||(s.info("Exiting..."),process.exit(0)),s.info(`Scaffolding default tnt app in ./${e.appName}`);else throw a}return e}import je from"path";import ie from"chalk";import Ge from"ora";function oe(e){let{packages:t}=e;s.info("Adding boilerplate...");for(let[n,a]of Object.entries(t))if(a.inUse){let i=Ge(`Boilerplating ${n}...`).start();a.installer(e),i.succeed(ie.green(`Successfully setup boilerplate for ${ie.green.bold(n)}`))}s.info("")}import V from"path";import*as k from"@clack/prompts";import w from"chalk";import S from"fs-extra";import $e from"ora";async function se({projectName:e,projectDir:t,pkgManager:n,noInstall:a}){let i=V.join(u,"template/base");a?s.info(""):s.info(`
45
+ Using: ${w.cyan.bold(n)}
46
+ `);let o=$e(`Scaffolding in: ${t}...
47
+ `).start();if(S.existsSync(t))if(S.readdirSync(t).length===0)e!=="."&&o.info(`${w.cyan.bold(e)} exists but is empty, continuing...
48
+ `);else{o.stopAndPersist();let l=await k.select({message:`${w.redBright.bold("Warning:")} ${w.cyan.bold(e)} already exists and isn't empty. How would you like to proceed?`,options:[{value:"abort",label:"Abort installation (recommended)"},{value:"clear",label:"Clear the directory and continue installation"},{value:"overwrite",label:"Continue installation and overwrite conflicting files"}],initialValue:"abort"});(k.isCancel(l)||l==="abort")&&(o.fail("Aborting installation..."),process.exit(1));let p=await k.confirm({message:`Are you sure you want to ${l==="clear"?"clear the directory":"overwrite conflicting files"}`,initialValue:!1});(k.isCancel(p)||!p)&&(o.fail("Aborting installation..."),process.exit(1)),l==="clear"&&(o.info(`Emptying ${w.cyan.bold(e)} and creating tnt app...
49
+ `),S.emptyDirSync(t))}o.start(),S.copySync(i,t),S.renameSync(V.join(t,"_gitignore"),V.join(t,".gitignore"));let r=e==="."?"App":w.cyan.bold(e);o.succeed(`${r} ${w.green.bold("scaffolded successfully!")}
50
+ `)}async function re({projectName:e,scopedAppName:t,packages:n,noInstall:a,databaseProvider:i}){let o=f(),r=je.resolve(process.cwd(),e);return await se({projectName:e,projectDir:r,pkgManager:o,scopedAppName:t,noInstall:a,databaseProvider:i}),oe({projectName:e,scopedAppName:t,projectDir:r,pkgManager:o,packages:n,noInstall:a,databaseProvider:i}),r}import{execSync as J}from"child_process";import U from"path";import*as W from"@clack/prompts";import T from"chalk";import{execa as C}from"execa";import le from"fs-extra";import Ve from"ora";function Ue(e){try{return J("git --version",{cwd:e}),!0}catch{return!1}}function L(e){return le.existsSync(U.join(e,".git"))}async function q(e){try{return await C("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}}function We(){let t=J("git --version").toString().trim().split(" ")[2],n=t?.split(".")[0],a=t?.split(".")[1];return{major:Number(n),minor:Number(a)}}function Je(){return J("git config --global init.defaultBranch || echo main").toString().trim()}async function pe(e){if(s.info("Initializing Git..."),!Ue(e)){s.warn("Git is not installed. Skipping Git initialization.");return}let t=Ve(`Creating a new git repo...
51
+ `).start(),n=L(e),a=await q(e),i=U.parse(e).name;if(a&&n){if(t.stop(),!await W.confirm({message:`${T.redBright.bold("Warning:")} Git is already initialized in "${i}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,initialValue:!1})){t.info("Skipping Git initialization.");return}le.removeSync(U.join(e,".git"))}else if(a&&!n&&(t.stop(),!await W.confirm({message:`${T.redBright.bold("Warning:")} "${i}" is already in a git worktree. Would you still like to initialize a new git repository in this directory?`,initialValue:!1}))){t.info("Skipping Git initialization.");return}try{let o=Je(),{major:r,minor:l}=We();r<2||r==2&&l<28?(await C("git",["init"],{cwd:e}),await C("git",["symbolic-ref","HEAD",`refs/heads/${o}`],{cwd:e})):await C("git",["init",`--initial-branch=${o}`],{cwd:e}),await C("git",["add","."],{cwd:e}),t.succeed(`${T.green("Successfully initialized and staged")} ${T.green.bold("git")}
52
+ `)}catch{t.fail(`${T.bold.red("Failed:")} could not initialize git. Update git to the latest version!
53
+ `)}}import Le from"chalk";import{execa as ce}from"execa";import me from"ora";var z=async(e,t,n)=>{let{onDataHandle:a,args:i=["install"],stdout:o="pipe"}=n,r=me(`Running ${t} install...`).start(),l=ce(t,i,{cwd:e,stdout:o});return await new Promise((p,d)=>{a&&l.stdout?.on("data",a(r)),l.on("error",m=>d(m)),l.on("close",()=>p())}),r},qe=async(e,t)=>{switch(e){case"npm":return await ce(e,["install"],{cwd:t,stderr:"inherit"}),null;case"pnpm":return z(t,e,{onDataHandle:n=>a=>{let i=a.toString();i.includes("Progress")&&(n.text=i.includes("|")?i.split(" | ")[1]??"":i)}});case"yarn":return z(t,e,{onDataHandle:n=>a=>{n.text=a.toString()}});case"bun":return z(t,e,{stdout:"ignore"})}},fe=async({projectDir:e})=>{s.info("Installing dependencies...");let t=f();(await qe(t,e)??me()).succeed(Le.green(`Successfully installed dependencies!
54
+ `))};var de=async({projectName:e=h,packages:t,noInstall:n,projectDir:a,databaseProvider:i})=>{let o=f();s.info("Next steps:"),e!=="."&&s.info(` cd ${e}`),n&&(o==="yarn"?s.info(` ${o}`):s.info(` ${o} install`)),["postgresql","mysql"].includes(i)&&s.info(" Add your database connection string to .env"),t?.nextAuth.inUse&&s.info(" Fill in your .env with necessary values. See https://create.tntstack.org/usage/first-steps for more info."),["npm","bun"].includes(o)?s.info(` ${o} run dev`):s.info(` ${o} dev`),!await q(a)&&!L(a)&&s.info(" git init"),s.info(' git commit -m "initial commit"')};import R from"fs";import ze from"path";function ge(e,t,n){R.readdirSync(e).forEach(i=>{let o=ze.join(e,i);if(R.statSync(o).isDirectory())ge(o,t,n);else{let l=R.readFileSync(o,"utf8").replace(new RegExp(t,"g"),n);R.writeFileSync(o,l,"utf8")}})}function ue(e,t){let n=t.replace(/\*/g,"").replace(/[^\/]$/,"$&/");ge(e,"@/",n)}import he from"path";function ye(e){let n=M(e).split("/"),a=n[n.length-1];if(a==="."){let r=he.resolve(process.cwd());a=he.basename(r)}let i=n.findIndex(r=>r.startsWith("@"));n.findIndex(r=>r.startsWith("@"))!==-1&&(a=n.slice(i).join("/"));let o=n.filter(r=>!r.startsWith("@")).join("/");return[a,o]}import Be from"gradient-string";var Fe={blue:"#add7ff",cyan:"#89ddff",green:"#5de4c7",magenta:"#fae4fc",red:"#d0679d",yellow:"#fffac2"};function be(){let e=Be(Object.values(Fe)),t=f();(t==="yarn"||t==="pnpm")&&console.log(""),console.log(e.multiline(F))}import{execSync as He}from"child_process";import Ke from"https";function ve(e){let t=P();t.includes("beta")?(s.warn(" You are using a beta version of create-tnt-stack."),s.warn(" Please report any bugs you encounter.")):t!==e&&(s.warn(" You are using an outdated version of create-tnt-stack."),s.warn(" Your version:",t+".","Latest version in the npm registry:",e),s.warn(" Please run the CLI with @latest to get the latest updates.")),console.log("")}function Ye(){return new Promise((e,t)=>{Ke.get("https://registry.npmjs.org/-/package/tnt-stack/dist-tags",n=>{if(n.statusCode===200){let a="";n.on("data",i=>a+=i),n.on("end",()=>{e(JSON.parse(a).latest)})}else t()}).on("error",()=>{t()})})}var we=()=>Ye().catch(()=>{try{return He("npm view create-tnt-stack version").toString().trim()}catch{return null}});async function Xe(){let e=await we(),t=f();be(),e&&ve(e);let{appName:n,databaseProvider:a,flags:{noGit:i,noInstall:o,importAlias:r},packages:l}=await ae(),p=ee(l),[d,m]=ye(n),g=await re({projectName:m,scopedAppName:d,packages:p,noInstall:o,databaseProvider:a}),_=_e.readJSONSync(ke.join(g,"package.json"));if(_.name=d,_.ctntaMetadata={initVersion:P()},t!=="bun"){let{stdout:D}=await Qe(t,["-v"],{cwd:g});_.packageManager=`${t}@${D.trim()}`}_e.writeJSONSync(ke.join(g,"package.json"),_,{spaces:2}),r!=="@/"&&ue(g,r),o||await fe({projectDir:g}),i||await pe(g),await de({projectDir:g,projectName:m,packages:p,noInstall:o,databaseProvider:a}),process.exit(0)}Xe().catch(e=>{s.error("Aborting installation..."),e instanceof Error?s.error(e):(s.error("An unknown error occurred. Please open an issue on GitHub with the below:"),console.log(e)),process.exit(1)});
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "create-tnt-stack",
3
+ "version": "0.1.0",
4
+ "description": "Create web application with the TNT-Powered stack",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/slickyeet/create-tnt-stack",
9
+ "directory": "cli"
10
+ },
11
+ "keywords": [
12
+ "create-tnt-stack",
13
+ "tnt-stack",
14
+ "typescript",
15
+ "next.js",
16
+ "tailwind"
17
+ ],
18
+ "type": "module",
19
+ "exports": "./dist/index.js",
20
+ "bin": {
21
+ "create-tnt-stack": "./dist/index.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "template",
26
+ "README.md",
27
+ "LICENSE",
28
+ "package.json"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "start": "node dist/index.js",
36
+ "dev": "tsup --watch",
37
+ "lint": "eslint . --ext .ts,.tsx",
38
+ "format": "prettier '**/*.{cjs,mjs,ts,tsx,md,json}' --ignore-path ../.gitignore --ignore-unknown --no-error-on-unmatched-pattern --write",
39
+ "typecheck": "tsc",
40
+ "clean": "rm -rf dist .turbo node_modules"
41
+ },
42
+ "dependencies": {
43
+ "@clack/core": "^0.4.1",
44
+ "@clack/prompts": "^0.10.0",
45
+ "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
46
+ "chalk": "^5.4.1",
47
+ "commander": "^13.1.0",
48
+ "execa": "^9.5.2",
49
+ "fs-extra": "^11.3.0",
50
+ "gradient-string": "^3.0.0",
51
+ "inquirer": "^12.4.2",
52
+ "ora": "^8.2.0",
53
+ "sort-package-json": "^3.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@auth/prisma-adapter": "^2.8.0",
57
+ "@prisma/client": "^6.4.1",
58
+ "@t3-oss/env-nextjs": "^0.12.0",
59
+ "@types/fs-extra": "^11.0.4",
60
+ "next": "^15.2.1",
61
+ "next-auth": "^4.24.11",
62
+ "prettier": "^3.5.3",
63
+ "prettier-plugin-tailwindcss": "^0.6.11",
64
+ "prisma": "^6.4.1",
65
+ "react": "^19.0.0",
66
+ "react-dom": "^19.0.0",
67
+ "tailwindcss": "^4.0.11",
68
+ "tsup": "^8.4.0",
69
+ "type-fest": "^4.37.0",
70
+ "typescript": "^5.8.2",
71
+ "zod": "^3.24.2"
72
+ }
73
+ }
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,41 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
@@ -0,0 +1,16 @@
1
+ import { dirname } from "path"
2
+ import { fileURLToPath } from "url"
3
+ import { FlatCompat } from "@eslint/eslintrc"
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = dirname(__filename)
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ })
11
+
12
+ const eslintConfig = [
13
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
14
+ ]
15
+
16
+ export default eslintConfig
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next"
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ }
6
+
7
+ export default nextConfig
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "template",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev --turbopack",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.0.0",
13
+ "react-dom": "^19.0.0",
14
+ "next": "15.2.1"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5",
18
+ "@types/node": "^22",
19
+ "@types/react": "^19",
20
+ "@types/react-dom": "^19",
21
+ "@tailwindcss/postcss": "^4",
22
+ "tailwindcss": "^4",
23
+ "eslint": "^9",
24
+ "eslint-config-next": "15.2.1",
25
+ "@eslint/eslintrc": "^3"
26
+ }
27
+ }
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ }
4
+
5
+ export default config
Binary file
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
@@ -0,0 +1,39 @@
1
+ @import "tailwindcss";
2
+
3
+ :root {
4
+ --background: #ffffff;
5
+ --foreground: #171717;
6
+
7
+ --primary: 255 60% 56%;
8
+ --accent: 189 94% 43%;
9
+ --highlight: 45 93% 47%;
10
+ }
11
+
12
+ @theme inline {
13
+ --color-background: var(--background);
14
+ --color-foreground: var(--foreground);
15
+
16
+ --color-primary: hsl(var(--primary));
17
+ --color-accent: hsl(var(--accent));
18
+ --color-highlight: hsl(var(--highlight));
19
+
20
+ --font-sans: var(--font-geist-sans);
21
+ --font-mono: var(--font-geist-mono);
22
+ }
23
+
24
+ @media (prefers-color-scheme: dark) {
25
+ :root {
26
+ --background: #0a0a0a;
27
+ --foreground: #ededed;
28
+
29
+ --primary: 255 60% 56%;
30
+ --accent: 189 94% 43%;
31
+ --highlight: 45 93% 47%;
32
+ }
33
+ }
34
+
35
+ body {
36
+ background: var(--background);
37
+ color: var(--foreground);
38
+ font-family: Arial, Helvetica, sans-serif;
39
+ }
@@ -0,0 +1,35 @@
1
+ import type { Metadata } from "next"
2
+ import { Geist, Geist_Mono } from "next/font/google"
3
+
4
+ import "./globals.css"
5
+
6
+ const geistSans = Geist({
7
+ variable: "--font-geist-sans",
8
+ subsets: ["latin"],
9
+ })
10
+
11
+ const geistMono = Geist_Mono({
12
+ variable: "--font-geist-mono",
13
+ subsets: ["latin"],
14
+ })
15
+
16
+ export const metadata: Metadata = {
17
+ title: "Create Next App",
18
+ description: "Generated by create next app",
19
+ }
20
+
21
+ export default function RootLayout({
22
+ children,
23
+ }: Readonly<{
24
+ children: React.ReactNode
25
+ }>) {
26
+ return (
27
+ <html lang="en">
28
+ <body
29
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
30
+ >
31
+ {children}
32
+ </body>
33
+ </html>
34
+ )
35
+ }
@@ -0,0 +1,114 @@
1
+ import Image from "next/image"
2
+
3
+ export default function HomePage() {
4
+ return (
5
+ <div className="grid min-h-screen grid-rows-[20px_1fr_20px] items-center justify-items-center gap-16 p-8 pb-20 font-[family-name:var(--font-geist-sans)] sm:p-20">
6
+ <main className="row-start-2 flex flex-col items-center gap-8 sm:items-start">
7
+ <div className="flex items-center gap-2">
8
+ <svg
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ width="50"
11
+ height="50"
12
+ viewBox="0 0 24 24"
13
+ fill="none"
14
+ stroke="currentColor"
15
+ strokeWidth="2"
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ className="from-primary/50 to-accent/50 rounded-lg bg-linear-to-r"
19
+ >
20
+ <polyline points="4 17 10 11 4 5" />
21
+ <line x1="12" x2="20" y1="19" y2="19" />
22
+ </svg>
23
+ <span className="inline-block text-4xl font-bold">
24
+ TNT-Powered Base Template
25
+ </span>
26
+ </div>
27
+
28
+ <ol className="list-inside list-decimal text-center font-[family-name:var(--font-geist-mono)] text-sm sm:text-left">
29
+ <li className="mb-2">
30
+ Get started by editing{" "}
31
+ <code className="rounded bg-black/[.05] px-1 py-0.5 font-semibold dark:bg-white/[.06]">
32
+ src/app/page.tsx
33
+ </code>
34
+ .
35
+ </li>
36
+ <li>Save and see your changes instantly.</li>
37
+ </ol>
38
+
39
+ <div className="flex flex-col items-center gap-4 sm:flex-row">
40
+ <a
41
+ className="bg-foreground text-background flex h-10 items-center justify-center gap-2 rounded-full border border-solid border-transparent px-4 text-sm transition-colors hover:bg-[#383838] sm:h-12 sm:px-5 sm:text-base dark:hover:bg-[#ccc]"
42
+ href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
43
+ target="_blank"
44
+ rel="noopener noreferrer"
45
+ >
46
+ <Image
47
+ className="dark:invert"
48
+ src="/vercel.svg"
49
+ alt="Vercel logomark"
50
+ width={20}
51
+ height={20}
52
+ />
53
+ Deploy now
54
+ </a>
55
+ <a
56
+ className="flex h-10 items-center justify-center rounded-full border border-solid border-black/[.08] px-4 text-sm transition-colors hover:border-transparent hover:bg-[#f2f2f2] sm:h-12 sm:min-w-44 sm:px-5 sm:text-base dark:border-white/[.145] dark:hover:bg-[#1a1a1a]"
57
+ href="https://create.tntstack.org/docs"
58
+ target="_blank"
59
+ rel="noopener noreferrer"
60
+ >
61
+ Read our docs
62
+ </a>
63
+ </div>
64
+ </main>
65
+ <footer className="row-start-3 flex flex-wrap items-center justify-center gap-6">
66
+ <a
67
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
68
+ href="https://create.tntstack.org/docs"
69
+ target="_blank"
70
+ rel="noopener noreferrer"
71
+ >
72
+ <Image
73
+ aria-hidden
74
+ src="/file.svg"
75
+ alt="File icon"
76
+ width={16}
77
+ height={16}
78
+ />
79
+ Learn
80
+ </a>
81
+ <a
82
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
83
+ href="https://create.tntstack.org/docs"
84
+ target="_blank"
85
+ rel="noopener noreferrer"
86
+ >
87
+ <Image
88
+ aria-hidden
89
+ src="/window.svg"
90
+ alt="Window icon"
91
+ width={16}
92
+ height={16}
93
+ />
94
+ Examples
95
+ </a>
96
+ <a
97
+ className="flex items-center gap-2 hover:underline hover:underline-offset-4"
98
+ href="https://create.tntstack.org"
99
+ target="_blank"
100
+ rel="noopener noreferrer"
101
+ >
102
+ <Image
103
+ aria-hidden
104
+ src="/globe.svg"
105
+ alt="Globe icon"
106
+ width={16}
107
+ height={16}
108
+ />
109
+ Go to create.tntstack.org →
110
+ </a>
111
+ </footer>
112
+ </div>
113
+ )
114
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,20 @@
1
+ // This is your Prisma schema file,
2
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
+
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ }
7
+
8
+ datasource db {
9
+ provider = "sqlite"
10
+ url = env("DATABASE_URL")
11
+ }
12
+
13
+ model Post {
14
+ id Int @id @default(autoincrement())
15
+ name String
16
+ createdAt DateTime @default(now())
17
+ updatedAt DateTime @updatedAt
18
+
19
+ @@index([name])
20
+ }
@@ -0,0 +1,85 @@
1
+ // This is your Prisma schema file,
2
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
+
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ }
7
+
8
+ datasource db {
9
+ provider = "sqlite"
10
+ // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below
11
+ // Further reading:
12
+ // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
13
+ // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
14
+ url = env("DATABASE_URL")
15
+ }
16
+
17
+ model Post {
18
+ id Int @id @default(autoincrement())
19
+ name String
20
+ createdAt DateTime @default(now())
21
+ updatedAt DateTime @updatedAt
22
+
23
+ createdBy User @relation(fields: [createdById], references: [id])
24
+ createdById String
25
+
26
+ @@index([name])
27
+ }
28
+
29
+ // Necessary for Next auth
30
+ model Account {
31
+ id String @id @default(cuid())
32
+
33
+ type String
34
+ scope String?
35
+ session_state String?
36
+ provider String
37
+ providerAccountId String
38
+
39
+ id_token String? // @db.Text
40
+ token_type String?
41
+ access_token String? // @db.Text
42
+ refresh_token String? // @db.Text
43
+ refresh_token_expires_in Int?
44
+
45
+ userId String
46
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
47
+
48
+ expires_at Int?
49
+
50
+ @@unique([provider, providerAccountId])
51
+ }
52
+
53
+ model Session {
54
+ id String @id @default(cuid())
55
+
56
+ sessionToken String @unique
57
+
58
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
59
+ userId String
60
+
61
+ expires DateTime
62
+ }
63
+
64
+ model User {
65
+ id String @id @default(cuid())
66
+
67
+ name String?
68
+ email String? @unique
69
+ emailVerified DateTime?
70
+
71
+ image String?
72
+
73
+ accounts Account[]
74
+ sessions Session[]
75
+ posts Post[]
76
+ }
77
+
78
+ model VerificationToken {
79
+ token String @unique
80
+ identifier String
81
+
82
+ expires DateTime
83
+
84
+ @@unique([identifier, token])
85
+ }
@@ -0,0 +1,3 @@
1
+ import { handlers } from "@/server/auth"
2
+
3
+ export const { GET, POST } = handlers
@@ -0,0 +1,44 @@
1
+ import { createEnv } from "@t3-oss/env-nextjs"
2
+ import { z } from "zod"
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * Specify your server-side environment variables schema here. This way you can ensure the app
7
+ * isn't built with invalid env vars.
8
+ */
9
+ server: {
10
+ DATABASE_URL: z.string().url(),
11
+ NODE_ENV: z
12
+ .enum(["development", "test", "production"])
13
+ .default("development"),
14
+ },
15
+
16
+ /**
17
+ * Specify your client-side environment variables schema here. This way you can ensure the app
18
+ * isn't built with invalid env vars. To expose them to the client, prefix them with
19
+ * `NEXT_PUBLIC_`.
20
+ */
21
+ client: {
22
+ // NEXT_PUBLIC_CLIENTVAR: z.string(),
23
+ },
24
+
25
+ /**
26
+ * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
27
+ * middlewares) or client-side so we need to destruct manually.
28
+ */
29
+ runtimeEnv: {
30
+ DATABASE_URL: process.env.DATABASE_URL,
31
+ NODE_ENV: process.env.NODE_ENV,
32
+ // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
33
+ },
34
+ /**
35
+ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
36
+ * useful for Docker builds.
37
+ */
38
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
39
+ /**
40
+ * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
41
+ * `SOME_VAR=''` will throw an error.
42
+ */
43
+ emptyStringAsUndefined: true,
44
+ })
@@ -0,0 +1,52 @@
1
+ import { createEnv } from "@t3-oss/env-nextjs"
2
+ import { z } from "zod"
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * Specify your server-side environment variables schema here. This way you can ensure the app
7
+ * isn't built with invalid env vars.
8
+ */
9
+ server: {
10
+ AUTH_SECRET:
11
+ process.env.NODE_ENV === "production"
12
+ ? z.string()
13
+ : z.string().optional(),
14
+ DISCORD_CLIENT_ID: z.string(),
15
+ DISCORD_CLIENT_SECRET: z.string(),
16
+ DATABASE_URL: z.string().url(),
17
+ NODE_ENV: z
18
+ .enum(["development", "test", "production"])
19
+ .default("development"),
20
+ },
21
+
22
+ /**
23
+ * Specify your client-side environment variables schema here. This way you can ensure the app
24
+ * isn't built with invalid env vars. To expose them to the client, prefix them with
25
+ * `NEXT_PUBLIC_`.
26
+ */
27
+ client: {
28
+ // NEXT_PUBLIC_CLIENTVAR: z.string(),
29
+ },
30
+
31
+ /**
32
+ * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
33
+ * middlewares) or client-side so we need to destruct manually.
34
+ */
35
+ runtimeEnv: {
36
+ AUTH_SECRET: process.env.AUTH_SECRET,
37
+ DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID,
38
+ DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET,
39
+ DATABASE_URL: process.env.DATABASE_URL,
40
+ NODE_ENV: process.env.NODE_ENV,
41
+ },
42
+ /**
43
+ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
44
+ * useful for Docker builds.
45
+ */
46
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
47
+ /**
48
+ * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
49
+ * `SOME_VAR=''` will throw an error.
50
+ */
51
+ emptyStringAsUndefined: true,
52
+ })
@@ -0,0 +1,51 @@
1
+ import { createEnv } from "@t3-oss/env-nextjs"
2
+ import { z } from "zod"
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * Specify your server-side environment variables schema here. This way you can ensure the app
7
+ * isn't built with invalid env vars.
8
+ */
9
+ server: {
10
+ AUTH_SECRET:
11
+ process.env.NODE_ENV === "production"
12
+ ? z.string()
13
+ : z.string().optional(),
14
+ DISCORD_CLIENT_ID: z.string(),
15
+ DISCORD_CLIENT_SECRET: z.string(),
16
+ NODE_ENV: z
17
+ .enum(["development", "test", "production"])
18
+ .default("development"),
19
+ },
20
+
21
+ /**
22
+ * Specify your client-side environment variables schema here. This way you can ensure the app
23
+ * isn't built with invalid env vars. To expose them to the client, prefix them with
24
+ * `NEXT_PUBLIC_`.
25
+ */
26
+ client: {
27
+ // NEXT_PUBLIC_CLIENTVAR: z.string(),
28
+ },
29
+
30
+ /**
31
+ * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
32
+ * middlewares) or client-side so we need to destruct manually.
33
+ */
34
+ runtimeEnv: {
35
+ AUTH_SECRET: process.env.AUTH_SECRET,
36
+ DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID,
37
+ DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET,
38
+ NODE_ENV: process.env.NODE_ENV,
39
+ // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
40
+ },
41
+ /**
42
+ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
43
+ * useful for Docker builds.
44
+ */
45
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
46
+ /**
47
+ * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
48
+ * `SOME_VAR=''` will throw an error.
49
+ */
50
+ emptyStringAsUndefined: true,
51
+ })
@@ -0,0 +1,60 @@
1
+ import { PrismaAdapter } from "@auth/prisma-adapter"
2
+ import { type DefaultSession, type NextAuthOptions } from "next-auth"
3
+ import DiscordProvider from "next-auth/providers/discord"
4
+
5
+ import { env } from "@/env"
6
+ import { db } from "@/server/db"
7
+
8
+ /**
9
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
10
+ * object and keep type safety.
11
+ *
12
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
13
+ */
14
+ declare module "next-auth" {
15
+ interface Session extends DefaultSession {
16
+ user: {
17
+ id: string
18
+ // ...other properties
19
+ // role: UserRole;
20
+ } & DefaultSession["user"]
21
+ }
22
+
23
+ // interface User {
24
+ // // ...other properties
25
+ // // role: UserRole;
26
+ // }
27
+ }
28
+
29
+ /**
30
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
31
+ *
32
+ * @see https://next-auth.js.org/configuration/options
33
+ */
34
+ export const authConfig = {
35
+ providers: [
36
+ DiscordProvider({
37
+ clientId: env.DISCORD_CLIENT_ID,
38
+ clientSecret: env.DISCORD_CLIENT_SECRET,
39
+ }),
40
+ /**
41
+ * ...add more providers here.
42
+ *
43
+ * Most other providers require a bit more work than the Discord provider. For example, the
44
+ * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
45
+ * model. Refer to the NextAuth.js docs for the provider you want to use. Example:
46
+ *
47
+ * @see https://next-auth.js.org/providers/github
48
+ */
49
+ ],
50
+ adapter: PrismaAdapter(db),
51
+ callbacks: {
52
+ session: ({ session, user }) => ({
53
+ ...session,
54
+ user: {
55
+ ...session.user,
56
+ id: user.id,
57
+ },
58
+ }),
59
+ },
60
+ } satisfies NextAuthOptions
@@ -0,0 +1,57 @@
1
+ import { type DefaultSession, type NextAuthOptions } from "next-auth"
2
+ import DiscordProvider from "next-auth/providers/discord"
3
+
4
+ import { env } from "@/env"
5
+
6
+ /**
7
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
8
+ * object and keep type safety.
9
+ *
10
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
11
+ */
12
+ declare module "next-auth" {
13
+ interface Session extends DefaultSession {
14
+ user: {
15
+ id: string
16
+ // ...other properties
17
+ // role: UserRole;
18
+ } & DefaultSession["user"]
19
+ }
20
+
21
+ // interface User {
22
+ // // ...other properties
23
+ // // role: UserRole;
24
+ // }
25
+ }
26
+
27
+ /**
28
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
29
+ *
30
+ * @see https://next-auth.js.org/configuration/options
31
+ */
32
+ export const authConfig = {
33
+ providers: [
34
+ DiscordProvider({
35
+ clientId: env.DISCORD_CLIENT_ID,
36
+ clientSecret: env.DISCORD_CLIENT_SECRET,
37
+ }),
38
+ /**
39
+ * ...add more providers here.
40
+ *
41
+ * Most other providers require a bit more work than the Discord provider. For example, the
42
+ * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
43
+ * model. Refer to the NextAuth.js docs for the provider you want to use. Example:
44
+ *
45
+ * @see https://next-auth.js.org/providers/github
46
+ */
47
+ ],
48
+ callbacks: {
49
+ session: ({ session, token }) => ({
50
+ ...session,
51
+ user: {
52
+ ...session.user,
53
+ id: token.sub,
54
+ },
55
+ }),
56
+ },
57
+ } satisfies NextAuthOptions
@@ -0,0 +1,10 @@
1
+ import NextAuth from "next-auth"
2
+ import { cache } from "react"
3
+
4
+ import { authConfig } from "./config"
5
+
6
+ const { auth: uncachedAuth, handlers, signIn, signOut } = NextAuth(authConfig)
7
+
8
+ const auth = cache(uncachedAuth)
9
+
10
+ export { auth, handlers, signIn, signOut }
@@ -0,0 +1,23 @@
1
+ import { PrismaClient } from "@prisma/client"
2
+
3
+ import { env } from "@/env"
4
+
5
+ const createPrismaClient = () =>
6
+ new PrismaClient({
7
+ log:
8
+ env.NODE_ENV === "development"
9
+ ? [
10
+ // "query",
11
+ "error",
12
+ "warn",
13
+ ]
14
+ : ["error"],
15
+ })
16
+
17
+ const globalForPrisma = globalThis as unknown as {
18
+ prisma: ReturnType<typeof createPrismaClient> | undefined
19
+ }
20
+
21
+ export const db = globalForPrisma.prisma ?? createPrismaClient()
22
+
23
+ if (env.NODE_ENV !== "production") globalForPrisma.prisma = db