create-tnt-stack 0.3.5 → 0.4.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 (24) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +47 -42
  3. package/package.json +16 -5
  4. package/template/base/package.json +2 -6
  5. package/template/packages/config/payload/with-postgres.ts +39 -0
  6. package/template/packages/config/payload/with-sqlite.ts +42 -0
  7. package/template/packages/config/tsconfig/with-payload.json +43 -0
  8. package/template/packages/src/app/(payload)/admin/[[...segments]]/not-found.tsx +27 -0
  9. package/template/packages/src/app/(payload)/admin/[[...segments]]/page.tsx +27 -0
  10. package/template/packages/src/app/(payload)/admin/importMap.js +1 -0
  11. package/template/packages/src/app/(payload)/api/[...slug]/route.ts +20 -0
  12. package/template/packages/src/app/(payload)/api/graphql/route.ts +8 -0
  13. package/template/packages/src/app/(payload)/api/graphql-playground/route.ts +8 -0
  14. package/template/packages/src/app/(payload)/custom.scss +0 -0
  15. package/template/packages/src/app/(payload)/layout.tsx +35 -0
  16. package/template/packages/src/app/globals/with-payload.css +166 -0
  17. package/template/packages/src/app/layout/with-payload.tsx +20 -0
  18. package/template/{base/src/app/page.tsx → packages/src/app/page/base.tsx} +1 -1
  19. package/template/packages/src/app/page/with-payload.tsx +58 -0
  20. package/template/packages/src/payload/collections/Media.ts +16 -0
  21. package/template/packages/src/payload/collections/Users.ts +13 -0
  22. /package/template/{base/tsconfig.json → packages/config/tsconfig/base.json} +0 -0
  23. /package/template/{base/src/app/globals.css → packages/src/app/globals/base.css} +0 -0
  24. /package/template/{base/src/app/layout.tsx → packages/src/app/layout/base.tsx} +0 -0
package/README.md CHANGED
@@ -76,7 +76,7 @@ bun create tnt-stack@latest
76
76
  ```
77
77
 
78
78
  For more information, visit the
79
- [docs](https://create.tntstack.org/docs/installation).
79
+ [docs](https://create.tntstack.org/docs/getting-started).
80
80
 
81
81
  <h2 id="features">🛠 Features</h2>
82
82
 
package/dist/index.js CHANGED
@@ -1,53 +1,58 @@
1
1
  #!/usr/bin/env node
2
- import Se from"path";import{execa as rt}from"execa";import Ce from"fs-extra";import{confirm as N,input as re,select as J}from"@inquirer/prompts";import{Command as qe}from"commander";import B from"path";import{fileURLToPath as je}from"url";var Me=je(import.meta.url),De=B.dirname(Me),c=B.join(De,"../"),H=` ___ ___ ___ _ _____ ___ _____ _ _ _____ ___ _____ _ ___ _ __
2
+ import Ue from"path";import{execa as vt}from"execa";import $e from"fs-extra";import{confirm as G,input as ue,select as T}from"@inquirer/prompts";import{Command as tt}from"commander";import X from"path";import{fileURLToPath as Be}from"url";var We=Be(import.meta.url),Ve=X.dirname(We),p=X.join(Ve,"../"),Z=` ___ ___ ___ _ _____ ___ _____ _ _ _____ ___ _____ _ ___ _ __
3
3
  / __| _ \\ __| /_\\_ _| __| |_ _| \\| |_ _| / __|_ _/_\\ / __| |/ /
4
4
  | (__| / _| / _ \\| | | _| | | | .\` | | | \\__ \\ | |/ _ \\ (__| ' <
5
5
  \\___|_|_\\___/_/ \\_\\_| |___| |_| |_|\\_| |_| |___/ |_/_/ \\_\\___|_|\\_\\
6
- `,P="my-tnt-stack",D="create-tnt-stack";import C from"path";import R from"fs-extra";import Y from"path";import Q from"fs-extra";import Re from"sort-package-json";var K={"next-auth":"^4.24.11","@auth/prisma-adapter":"^2.8.0",prisma:"^6.5.0","@prisma/client":"^6.5.0","@t3-oss/env-nextjs":"^0.12.0",zod:"^3.24.2",prettier:"^3.5.3","prettier-plugin-tailwindcss":"^0.6.11","@ianvs/prettier-plugin-sort-imports":"^4.4.1",eslint:"^9","eslint-config-next":"^15.2.3","@eslint/eslintrc":"^3"};var u=e=>{let{dependencies:t,devMode:a,projectDir:n}=e,i=Q.readJsonSync(Y.join(n,"package.json"));t.forEach(s=>{let l=K[s];a&&i.devDependencies?i.devDependencies[s]=l:i.dependencies&&(i.dependencies[s]=l)});let o=Re(i);Q.writeJsonSync(Y.join(n,"package.json"),o,{spaces:2})};var X=({projectDir:e,scopedAppName:t,packages:a,databaseProvider:n})=>{let i=a?.envVariables.inUse,o=a?.nextAuth.inUse,s=a?.prisma.inUse,l=[];i&&(l.push("@t3-oss/env-nextjs"),l.push("zod")),u({projectDir:e,dependencies:l,devMode:!1});let m=s,d=Ge(!!o,!!s,t,n),p="";if(m?o?p="with-next-auth-db.js":p="with-db.js":o&&(p="with-next-auth.js"),p!==""){let Oe=C.join(c,"template/packages/src/env",p),Ne=C.join(e,"src/env.js");R.copyFileSync(Oe,Ne)}let g=C.join(e,".env"),w=C.join(e,".env.example"),M=$e+d,Te=Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("base64"),Ee=d.replace('AUTH_SECRET=""',`AUTH_SECRET="${Te}" # Generated by create-tnt-stack`);R.writeFileSync(g,Ee,"utf-8"),R.writeFileSync(w,M,"utf-8")};function Ge(e,t,a,n){let i=`
7
- # When adding additional environment variables, the schema in "/src/env.js"
8
- # should be updated accordingly.
6
+ `,I="my-tnt-stack",$="create-tnt-stack";import u from"path";import k from"fs-extra";import te from"path";import ne from"fs-extra";import ze from"sort-package-json";var ee={"next-auth":"^4.24.11","@auth/prisma-adapter":"^2.8.0",prisma:"^6.5.0","@prisma/client":"^6.5.0","@t3-oss/env-nextjs":"^0.12.0",zod:"^3.24.2",prettier:"^3.5.3","prettier-plugin-tailwindcss":"^0.6.11","@ianvs/prettier-plugin-sort-imports":"^4.4.1",eslint:"^9","eslint-config-next":"^15.2.4","@eslint/eslintrc":"^3.3.1",typescript:"^5.8.2","@types/node":"^22","@types/react":"^19","@types/react-dom":"^19",payload:"^3.33.0","@payloadcms/next":"^3.33.0","@payloadcms/payload-cloud":"^3.33.0","@payloadcms/richtext-lexical":"^3.33.0","@payloadcms/db-vercel-postgres":"^3.33.0","@payloadcms/db-sqlite":"^3.33.0",graphql:"^16.10.0",sharp:"^0.34.1"};var d=e=>{let{dependencies:o,devMode:s,projectDir:n}=e,t=ne.readJsonSync(te.join(n,"package.json"));o.forEach(i=>{let l=ee[i];s&&t.devDependencies?t.devDependencies[i]=l:t.dependencies&&(t.dependencies[i]=l)});let a=ze(t);ne.writeJsonSync(te.join(n,"package.json"),a,{spaces:2})};var ae=({projectDir:e,packages:o,databaseProvider:s})=>{let n=[],t=[];if(o?.payload.inUse)switch(n.push("payload"),n.push("@payloadcms/next"),n.push("@payloadcms/payload-cloud"),n.push("@payloadcms/richtext-lexical"),n.push("graphql"),s){case"sqlite":t.push("@payloadcms/db-sqlite");break;case"postgresql":t.push("@payloadcms/db-vercel-postgres");break}d({projectDir:e,dependencies:n,devMode:!1}),d({projectDir:e,dependencies:t,devMode:!1});let a=u.join(p,"template/packages"),i=u.join(a,"config/payload",`${s==="postgresql"?"with-postgres":"with-sqlite"}.ts`),l=u.join(e,"payload.config.ts");k.copyFileSync(i,l);let m=u.join(a,"src/payload/collections"),f=u.join(e,"src/collections");k.mkdirSync(f,{recursive:!0}),k.copyFileSync(u.join(m,"Media.ts"),u.join(f,"Media.ts")),k.copyFileSync(u.join(m,"Users.ts"),u.join(f,"Users.ts"));let g=u.join(a,"src/app/(payload)"),c=u.join(e,"src/app/(payload)");k.mkdirSync(c,{recursive:!0}),k.copySync(g,c);let h=u.join(e,"package.json"),x=k.readJSONSync(h);x.scripts={...x.scripts,payload:"payload","generate:importmap":"payload generate:importmap","generate:types":"payload generate:types"},k.writeJSONSync(h,x,{spaces:2})};import oe from"path";import Ke from"fs-extra";var se=({projectDir:e,packages:o})=>{let s=o?.typescript.inUse,n=o?.payload.inUse,t=[];s&&(t.push("typescript"),t.push("@types/node"),t.push("@types/react"),t.push("@types/react-dom")),d({projectDir:e,dependencies:t,devMode:!0});let a=oe.join(p,"template/packages/config/tsconfig",`${n?"with-payload":"base"}.json`),i=oe.join(e,"tsconfig.json");Ke.copyFileSync(a,i)};import E from"path";import q from"fs-extra";var ie=({projectDir:e,scopedAppName:o,packages:s,databaseProvider:n})=>{let t=s?.envVariables.inUse,a=s?.nextAuth.inUse,i=s?.prisma.inUse,l=s?.payload.inUse,m=[];t&&(m.push("@t3-oss/env-nextjs"),m.push("zod")),d({projectDir:e,dependencies:m,devMode:!1});let f=i,g=He(!!a,!!i,!!l,o,n),c="";if(f?a?c="with-next-auth-db.js":c="with-db.js":a&&(c="with-next-auth.js"),c!==""){let Je=E.join(p,"template/packages/src/env",c),Le=E.join(e,"src/env.js");q.copyFileSync(Je,Le)}let h=E.join(e,".env"),x=E.join(e,".env.example"),qe=Ye+g,Q=Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("base64"),Fe=g.replace('AUTH_SECRET=""',`AUTH_SECRET="${Q}" # Generated by create-tnt-stack`).replace('PAYLOAD_SECRET=""',`PAYLOAD_SECRET="${Q}" # Generated by create-tnt-stack`);q.writeFileSync(h,Fe,"utf-8"),q.writeFileSync(x,qe,"utf-8")};function He(e,o,s,n,t){let a=`
7
+ # When adding additional environment variables, the schema in "/src/env.js"
8
+ # should be updated accordingly.
9
9
  `.trim().concat(`
10
- `);return t&&(i+=`
11
- # Prisma
12
- # https://www.prisma.io/docs/reference/database-reference/connection-urls#env
13
- `),t&&(n==="mysql"?i+=`DATABASE_URL="mysql://root:password@localhost:3306/${a}"`:n==="postgresql"?i+=`DATABASE_URL="postgresql://postgres:password@localhost:5432/${a}"`:n==="sqlite"&&(i+='DATABASE_URL="file:./db.sqlite"'),i+=`
14
- `),e&&(i+=`
15
- # Next Auth
16
- # You can generate a new secret on the command line with:
17
- # npx auth secret
18
- # https://next-auth.js.org/configuration/options#secret
19
- AUTH_SECRET=""
10
+ `);return o&&(a+=`
11
+ # Prisma
12
+ # https://www.prisma.io/docs/reference/database-reference/connection-urls#env
13
+ `),o&&(t==="mysql"?a+=`DATABASE_URL="mysql://root:password@localhost:3306/${n}"`:t==="postgresql"?a+=`DATABASE_URL="postgresql://postgres:password@localhost:5432/${n}"`:t==="sqlite"&&(a+='DATABASE_URL="file:./db.sqlite"'),a+=`
14
+ `),s&&(a+=`
15
+ # Payload CMS
16
+ # https://payloadcms.com/docs/database/overview
17
+ `,a+=`PAYLOAD_SECRET=""
18
+ `,t==="postgresql"?a+=`DATABASE_URL="postgresql://postgres:password@localhost:5432/${n}"`:t==="sqlite"&&(a+='DATABASE_URL="file:./db.sqlite"'),a+=`
19
+ `),e&&(a+=`
20
+ # Next Auth
21
+ # You can generate a new secret on the command line with:
22
+ # npx auth secret
23
+ # https://next-auth.js.org/configuration/options#secret
24
+ AUTH_SECRET=""
20
25
 
21
- # Next Auth Discord Provider
22
- DISCORD_CLIENT_ID=""
23
- DISCORD_CLIENT_SECRET=""
24
- `),!e&&!t&&(i+=`
25
- # Example:
26
- # SERVERVAR="foo"
27
- # NEXT_PUBLIC_CLIENTVAR="bar"
28
- `),i}var $e=`
29
- # Since the ".env" file is gitignored, you can use the ".env.example" file to
30
- # build a new ".env" file when you clone the repo. Keep this file up-to-date
31
- # when you add new variables to \`.env\`.
26
+ # Next Auth Discord Provider
27
+ DISCORD_CLIENT_ID=""
28
+ DISCORD_CLIENT_SECRET=""
29
+ `),!e&&!o&&!s&&(a+=`
30
+ # Example:
31
+ # SERVERVAR="foo"
32
+ # NEXT_PUBLIC_CLIENTVAR="bar"
33
+ `),a}var Ye=`
34
+ # Since the ".env" file is gitignored, you can use the ".env.example" file to
35
+ # build a new ".env" file when you clone the repo. Keep this file up-to-date
36
+ # when you add new variables to \`.env\`.
32
37
 
33
- # This file will be committed to version control, so make sure not to have any
34
- # secrets in it. If you are cloning this repo, create a copy of this file named
35
- # ".env" and populate it with your secrets.
36
- `.trim().concat(`
38
+ # This file will be committed to version control, so make sure not to have any
39
+ # secrets in it. If you are cloning this repo, create a copy of this file named
40
+ # ".env" and populate it with your secrets.
41
+ `.trim().concat(`
37
42
 
38
- `);import G from"path";import $ from"fs-extra";var Z=({projectDir:e,packages:t})=>{let a=t?.eslint.inUse,n=[];a&&(n.push("eslint"),n.push("eslint-config-next"),n.push("@eslint/eslintrc")),u({projectDir:e,dependencies:n,devMode:!0});let i=G.join(c,"template/packages/config","eslint.config.mjs"),o=G.join(e,"eslint.config.mjs"),s=G.join(e,"package.json"),l=$.readJSONSync(s);l.scripts={...l.scripts,lint:"next lint"},$.copyFileSync(i,o),$.writeJSONSync(s,l,{spaces:2})};import v from"path";import U from"fs-extra";var ee=({projectDir:e,packages:t})=>{let a=t?.prisma.inUse,n=["next-auth"];a&&n.push("@auth/prisma-adapter"),u({projectDir:e,dependencies:n,devMode:!1});let i=v.join(c,"template/packages"),o="src/app/api/auth/[...nextauth]/route.ts",s=v.join(i,o),l=v.join(e,o),m=v.join(i,"src/server/auth/config",a?"next-auth-with-prisma.ts":"next-auth.ts"),d=v.join(e,"src/server/auth/config.ts"),p=v.join(i,"src/server/auth/next-auth.ts"),g=v.join(e,"src/server/auth/index.ts");U.copySync(s,l),U.copySync(m,d),U.copySync(p,g)};import te from"path";import Ue from"fs-extra";var ne=({projectDir:e,packages:t})=>{let a=t?.prettier.inUse,n=[];a&&(n.push("prettier"),n.push("prettier-plugin-tailwindcss"),n.push("@ianvs/prettier-plugin-sort-imports")),u({projectDir:e,dependencies:n,devMode:!0});let i=te.join(c,"template/packages/config","prettier.config.mjs"),o=te.join(e,"prettier.config.mjs");Ue.copyFileSync(i,o)};import h from"path";import y from"fs-extra";var ie=({projectDir:e,packages:t,databaseProvider:a})=>{u({projectDir:e,dependencies:["prisma"],devMode:!0}),u({projectDir:e,dependencies:["@prisma/client"],devMode:!1});let n=h.join(c,"template/packages"),i=h.join(n,"prisma/schema",`${t?.nextAuth.inUse?"with-next-auth":"base"}.prisma`),o=y.readFileSync(i,"utf-8");a!=="sqlite"&&(o=o.replace('provider = "sqlite"',`provider = "${{mysql:"mysql",postgresql:"postgresql"}[a]}"`),["mysql"].includes(a)&&(o=o.replace("// @db.Text","@db.Text")));let s=h.join(e,"prisma/schema.prisma");y.mkdirSync(h.dirname(s),{recursive:!0}),y.writeFileSync(s,o);let l=h.join(n,"src/server/db/db-prisma.ts"),m=h.join(e,"src/server/db/index.ts");y.mkdirSync(h.dirname(m),{recursive:!0}),y.writeFileSync(m,y.readFileSync(l,"utf-8"));let d=h.join(e,"package.json"),p=y.readJSONSync(d);p.scripts={...p.scripts,postinstall:"prisma generate","db:push":"prisma db push","db:studio":"prisma studio","db:generate":"prisma migrate dev","db:migrate":"prisma migrate deploy"},y.writeJSONSync(d,p,{spaces:2})};var T=["sqlite","mysql","postgresql"],ae=e=>({nextAuth:{inUse:e.includes("nextAuth"),installer:ee},prisma:{inUse:e.includes("prisma"),installer:ie},envVariables:{inUse:!0,installer:X},prettier:{inUse:e.includes("prettier"),installer:ne},eslint:{inUse:!0,installer:Z}});import Je from"path";import We from"fs-extra";function k(){let e=Je.join(c,"package.json");return We.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 x=class extends Error{constructor(t){super(t)}};import E from"chalk";var r={error(...e){console.log(E.red(...e))},warn(...e){console.log(E.yellow(...e))},info(...e){console.log(E.cyan(...e))},success(...e){console.log(E.green(...e))}};var O=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e);var Le=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;function oe(e){let t=O(e),a=t.split("/"),n=a.findIndex(o=>o.startsWith("@")),i=a[a.length-1];return a.findIndex(o=>o.startsWith("@"))!==-1&&(i=a.slice(n).join("/")),t=="."||Le.test(i??"")?!0:"App name must consist of only lowercase alphanumeric characters, '-', and '_'"}var se=e=>e.startsWith(".")||e.startsWith("/")?"Import alias can't start with '.' or '/'":!0;var b={appName:P,packages:[],flags:{noGit:!1,noInstall:!1,default:!1,CI:!1,nextAuth:!1,prisma:!1,prettier:!1,importAlias:"@/",dbProvider:"sqlite"},databaseProvider:"sqlite"};async function le(){let e=b,t=new qe().name(D).description("CLI for scaffolding new web apps with the TNT-Powered stack").version(k(),"-v, --version","Output the current version of TNT").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`.",n=>!!n&&n!=="false").option("--prisma [boolean]","Experimental: Boolean value if we should install Prisma. Must be used in conjunction with `--CI`.",n=>!!n&&n!=="false").option("--prettier [boolean]","Experimental: Boolean value if we should install Prettier. Must be used in conjunction with `--CI`.",n=>!!n&&n!=="false").option("-i, --import-alias [alias]","Explicitly tell the CLI to use a custom import alias",b.flags.importAlias).option("--dbProvider [provider]",`Choose a database provider to use. Possible values: ${T.join(", ")}`,b.flags.dbProvider).parse(process.argv);process.env.npm_config_user_agent?.startsWith("yarn/3")&&r.warn(` WARNING: It looks like you are using Yarn 3. This is currently not supported,
43
+ `);import F from"path";import J from"fs-extra";var re=({projectDir:e,packages:o})=>{let s=o?.eslint.inUse,n=[];s&&(n.push("eslint"),n.push("eslint-config-next"),n.push("@eslint/eslintrc")),d({projectDir:e,dependencies:n,devMode:!0});let t=F.join(p,"template/packages/config","eslint.config.mjs"),a=F.join(e,"eslint.config.mjs"),i=F.join(e,"package.json"),l=J.readJSONSync(i);l.scripts={...l.scripts,lint:"next lint"},J.copyFileSync(t,a),J.writeJSONSync(i,l,{spaces:2})};import P from"path";import L from"fs-extra";var le=({projectDir:e,packages:o})=>{let s=o?.prisma.inUse,n=["next-auth"];s&&n.push("@auth/prisma-adapter"),d({projectDir:e,dependencies:n,devMode:!1});let t=P.join(p,"template/packages"),a="src/app/api/auth/[...nextauth]/route.ts",i=P.join(t,a),l=P.join(e,a),m=P.join(t,"src/server/auth/config",s?"next-auth-with-prisma.ts":"next-auth.ts"),f=P.join(e,"src/server/auth/config.ts"),g=P.join(t,"src/server/auth/next-auth.ts"),c=P.join(e,"src/server/auth/index.ts");L.copySync(i,l),L.copySync(m,f),L.copySync(g,c)};import pe from"path";import Qe from"fs-extra";var ce=({projectDir:e,packages:o})=>{let s=o?.prettier.inUse,n=[];s&&(n.push("prettier"),n.push("prettier-plugin-tailwindcss"),n.push("@ianvs/prettier-plugin-sort-imports")),d({projectDir:e,dependencies:n,devMode:!0});let t=pe.join(p,"template/packages/config","prettier.config.mjs"),a=pe.join(e,"prettier.config.mjs");Qe.copyFileSync(t,a)};import w from"path";import _ from"fs-extra";var me=({projectDir:e,packages:o,databaseProvider:s})=>{d({projectDir:e,dependencies:["prisma"],devMode:!0}),d({projectDir:e,dependencies:["@prisma/client"],devMode:!1});let n=w.join(p,"template/packages"),t=w.join(n,"prisma/schema",`${o?.nextAuth.inUse?"with-next-auth":"base"}.prisma`),a=_.readFileSync(t,"utf-8");s!=="sqlite"&&(a=a.replace('provider = "sqlite"',`provider = "${{mysql:"mysql",postgresql:"postgresql"}[s]}"`),["mysql"].includes(s)&&(a=a.replace("// @db.Text","@db.Text")));let i=w.join(e,"prisma/schema.prisma");_.mkdirSync(w.dirname(i),{recursive:!0}),_.writeFileSync(i,a);let l=w.join(n,"src/server/db/db-prisma.ts"),m=w.join(e,"src/server/db/index.ts");_.mkdirSync(w.dirname(m),{recursive:!0}),_.writeFileSync(m,_.readFileSync(l,"utf-8"));let f=w.join(e,"package.json"),g=_.readJSONSync(f);g.scripts={...g.scripts,postinstall:"prisma generate","db:push":"prisma db push","db:studio":"prisma studio","db:generate":"prisma migrate dev","db:migrate":"prisma migrate deploy"},_.writeJSONSync(f,g,{spaces:2})};var M=["sqlite","mysql","postgresql"],de=e=>({nextAuth:{inUse:e.includes("nextAuth"),installer:le},prisma:{inUse:e.includes("prisma"),installer:me},envVariables:{inUse:!0,installer:ie},prettier:{inUse:e.includes("prettier"),installer:ce},eslint:{inUse:!0,installer:re},typescript:{inUse:!0,installer:se},payload:{inUse:e.includes("payload"),installer:ae}});import Xe from"path";import Ze from"fs-extra";function A(){let e=Xe.join(p,"package.json");return Ze.readJSONSync(e).version??"1.0.0"}var y=()=>{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 C=class extends Error{constructor(o){super(o)}};import R from"chalk";var r={error(...e){console.log(R.red(...e))},warn(...e){console.log(R.yellow(...e))},info(...e){console.log(R.cyan(...e))},success(...e){console.log(R.green(...e))}};var N=e=>(e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e);var et=/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;function fe(e){let o=N(e),s=o.split("/"),n=s.findIndex(a=>a.startsWith("@")),t=s[s.length-1];return s.findIndex(a=>a.startsWith("@"))!==-1&&(t=s.slice(n).join("/")),o=="."||et.test(t??"")?!0:"App name must consist of only lowercase alphanumeric characters, '-', and '_'"}var ge=e=>e.startsWith(".")||e.startsWith("/")?"Import alias can't start with '.' or '/'":!0;var b={appName:I,packages:[],flags:{noGit:!1,noInstall:!1,default:!1,CI:!1,nextAuth:!1,prisma:!1,prettier:!1,importAlias:"@/",dbProvider:"sqlite",backend:"nextjs"},databaseProvider:"sqlite"};async function ye(){let e=b,o=new tt().name($).description("CLI for scaffolding new web apps with the TNT-Powered stack").version(A(),"-v, --version","Output the current version of TNT").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`.",n=>!!n&&n!=="false").option("--prisma [boolean]","Experimental: Boolean value if we should install Prisma. Must be used in conjunction with `--CI`.",n=>!!n&&n!=="false").option("--prettier [boolean]","Experimental: Boolean value if we should install Prettier. Must be used in conjunction with `--CI`.",n=>!!n&&n!=="false").option("-i, --import-alias [alias]","Explicitly tell the CLI to use a custom import alias",b.flags.importAlias).option("--dbProvider [provider]",`Choose a database provider to use. Possible values: ${M.join(", ")}`,b.flags.dbProvider).option("--backend [framework]",`Choose a backend framework to use. Possible values: ${b.flags.backend}`,b.flags.backend).parse(process.argv);process.env.npm_config_user_agent?.startsWith("yarn/3")&&r.warn(` WARNING: It looks like you are using Yarn 3. This is currently not supported,
39
44
  and likely to result in a crash. Please run create-tnt-stack with another
40
45
  package manager such as pnpm, npm, or Yarn Classic.
41
- See: https://github.com/t3-oss/create-t3-app/issues/57`);let a=t.args[0];if(a&&(e.appName=a),e.flags=t.opts(),e.flags.CI)return e.packages=[],e.flags.nextAuth&&e.packages.push("nextAuth"),e.flags.prisma&&e.packages.push("prisma"),e.flags.prettier&&e.packages.push("prettier"),T.includes(e.flags.dbProvider)===!1&&(r.warn(`Incompatible database provided. Use: ${T.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 r.warn(` WARNING: It looks like you are using MinTTY, which is non-interactive. This is most likely because you are
46
+ See: https://github.com/t3-oss/create-t3-app/issues/57`);let s=o.args[0];if(s&&(e.appName=s),e.flags=o.opts(),e.flags.CI)return e.packages=[],e.flags.nextAuth&&e.packages.push("nextAuth"),e.flags.prisma&&e.packages.push("prisma"),e.flags.prettier&&e.packages.push("prettier"),M.includes(e.flags.dbProvider)===!1&&(r.warn(`Incompatible database provided. Use: ${M.join(", ")}. Exiting.`),process.exit(0)),e.flags.backend==="payload"&&e.flags.dbProvider==="mysql"&&(r.warn("Payload CMS does not support MySQL. Exiting."),process.exit(0)),e;if(e.flags.default)return e;try{if(process.env.TERM_PROGRAM?.toLowerCase().includes("mintty"))throw r.warn(` WARNING: It looks like you are using MinTTY, which is non-interactive. This is most likely because you are
42
47
  using Git Bash. If that's that case, please use Git Bash from another terminal, such as Windows Terminal. Alternatively, you
43
- can provide the arguments from the CLI directly: https://create.tntstack.org/installation#experimental-usage to skip the prompts.`),new x("Non-interactive environment");let n=f(),i={};a||(i.name=await re({message:"What will your project be called?",default:P,validate:s=>oe(s)})),i.authentication=await J({message:"What authentication provider would you like to use?",choices:[{value:"none",name:"None"},{value:"nextAuth",name:"NextAuth.js"}],default:"none"}),i.database=await J({message:"What database ORM would you like to use?",choices:[{value:"none",name:"None"},{value:"prisma",name:"Prisma"}],default:"none"}),i.database!=="none"&&(i.databaseProvider=await J({message:"What database provider would you like to use?",choices:[{value:"sqlite",name:"SQLite"},{value:"mysql",name:"MySQL"},{value:"postgresql",name:"PostgreSQL"}],default:"sqlite"})),i.prettier=await N({message:"Should we install Prettier?",default:!b.flags.prettier}),e.flags.noGit||(i.noGit=await N({message:"Should we initialize a Git repository and stage the changes?",default:!b.flags.noGit})),e.flags.noInstall||(i.noInstall=await N({message:`Should we run '${n}`+(n==="yarn"?"'?":" install' for you?"),default:!b.flags.noInstall})),i.importAlias=await re({message:"What import alias would you like to use?",default:b.flags.importAlias,validate:se});let o=[];return i.authentication==="nextAuth"&&o.push("nextAuth"),i.database==="prisma"&&o.push("prisma"),i.prettier&&o.push("prettier"),{appName:i.name??e.appName,packages:o,flags:{...e.flags,noGit:!i.noGit||e.flags.noGit,noInstall:!i.noInstall||e.flags.noInstall,importAlias:i.importAlias??e.flags.importAlias},databaseProvider:i.databaseProvider||"sqlite"}}catch(n){if(n instanceof x)r.warn(`${D} needs an interactive terminal to run.`),await N({message:"Continue scaffolding with default options?",default:!0})||(r.info("Exiting..."),process.exit(0)),r.info(`Scaffolding default tnt app in ./${e.appName}`);else throw n}return e}import He from"path";import pe from"chalk";import Ve from"ora";function ce(e){let{packages:t}=e;r.info("Adding boilerplate...");for(let[a,n]of Object.entries(t))if(n.inUse){let i=Ve(`Boilerplating ${a}...`).start();n.installer(e),i.succeed(pe.green(`${pe.green.bold(a)}`))}r.info("")}import W from"path";import{confirm as ze,select as Fe}from"@inquirer/prompts";import _ from"chalk";import I from"fs-extra";import Be from"ora";async function me({projectName:e,projectDir:t,pkgManager:a,noInstall:n}){let i=W.join(c,"template/base");n?r.info(""):r.info(`
44
- Using: ${_.cyan.bold(a)}
45
- `);let o=Be(`Scaffolding in: ${t}...
46
- `).start();if(I.existsSync(t))if(I.readdirSync(t).length===0)e!=="."&&o.info(`${_.cyan.bold(e)} exists but is empty, continuing...
47
- `);else{o.stopAndPersist();let l=await Fe({message:`${_.redBright.bold("Warning:")} ${_.cyan.bold(e)} already exists and isn't empty. How would you like to proceed?`,choices:[{value:"abort",name:"Abort installation (recommended)"},{value:"clear",name:"Clear the directory and continue installation"},{value:"overwrite",name:"Continue installation and overwrite conflicting files"}],default:"abort"});l==="abort"&&(o.fail("Aborting installation..."),process.exit(1)),await ze({message:`Are you sure you want to ${l==="clear"?"clear the directory":"overwrite conflicting files"}`,default:!1})||(o.fail("Aborting installation..."),process.exit(1)),l==="clear"&&(o.info(`Emptying ${_.cyan.bold(e)} and creating tnt app...
48
- `),I.emptyDirSync(t))}o.start(),I.copySync(i,t),I.renameSync(W.join(t,"_gitignore"),W.join(t,".gitignore"));let s=e==="."?"App":_.cyan.bold(e);o.succeed(`${s} ${_.green.bold("scaffolded successfully!")}
49
- `)}async function de({projectName:e,scopedAppName:t,packages:a,noInstall:n,databaseProvider:i}){let o=f(),s=He.resolve(process.cwd(),e);return await me({projectName:e,projectDir:s,pkgManager:o,scopedAppName:t,noInstall:n,databaseProvider:i}),ce({projectName:e,scopedAppName:t,projectDir:s,pkgManager:o,packages:a,noInstall:n,databaseProvider:i}),s}import{execSync as q}from"child_process";import L from"path";import{confirm as fe}from"@inquirer/prompts";import A from"chalk";import{execa as S}from"execa";import ge from"fs-extra";import Ke from"ora";function Ye(e){try{return q("git --version",{cwd:e}),!0}catch{return!1}}function V(e){return ge.existsSync(L.join(e,".git"))}async function z(e){try{return await S("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}}function Qe(){let t=q("git --version").toString().trim().split(" ")[2],a=t?.split(".")[0],n=t?.split(".")[1];return{major:Number(a),minor:Number(n)}}function Xe(){return q("git config --global init.defaultBranch || echo main").toString().trim()}async function ue(e){if(r.info("Initializing Git..."),!Ye(e)){r.warn("Git is not installed. Skipping Git initialization.");return}let t=Ke(`Creating a new git repo...
50
- `).start(),a=V(e),n=await z(e),i=L.parse(e).name;if(n&&a){if(t.stop(),!await fe({message:`${A.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?`,default:!1})){t.info("Skipping Git initialization.");return}ge.removeSync(L.join(e,".git"))}else if(n&&!a&&(t.stop(),!await fe({message:`${A.redBright.bold("Warning:")} "${i}" is already in a git worktree. Would you still like to initialize a new git repository in this directory?`,default:!1}))){t.info("Skipping Git initialization.");return}try{let o=Xe(),{major:s,minor:l}=Qe();s<2||s==2&&l<28?(await S("git",["init"],{cwd:e}),await S("git",["symbolic-ref","HEAD",`refs/heads/${o}`],{cwd:e})):await S("git",["init",`--initial-branch=${o}`],{cwd:e}),await S("git",["add","."],{cwd:e}),t.succeed(`${A.green("Successfully initialized and staged")} ${A.green.bold("git")}
51
- `)}catch{t.fail(`${A.bold.red("Failed:")} could not initialize git. Update git to the latest version!
52
- `)}}import Ze from"chalk";import{execa as he}from"execa";import ye from"ora";var F=async(e,t,a)=>{let{onDataHandle:n,args:i=["install"],stdout:o="pipe"}=a,s=ye(`Running ${t} install...`).start(),l=he(t,i,{cwd:e,stdout:o});return await new Promise((m,d)=>{n&&l.stdout?.on("data",n(s)),l.on("error",p=>d(p)),l.on("close",()=>m())}),s},et=async(e,t)=>{switch(e){case"npm":return await he(e,["install"],{cwd:t,stderr:"inherit"}),null;case"pnpm":return F(t,e,{onDataHandle:a=>n=>{let i=n.toString();i.includes("Progress")&&(a.text=i.includes("|")?i.split(" | ")[1]??"":i)}});case"yarn":return F(t,e,{onDataHandle:a=>n=>{a.text=n.toString()}});case"bun":return F(t,e,{stdout:"ignore"})}},ve=async({projectDir:e})=>{r.info("Installing dependencies...");let t=f();(await et(t,e)??ye()).succeed(Ze.green(`Successfully installed dependencies!
53
- `))};var be=async({projectName:e=P,packages:t,noInstall:a,projectDir:n,databaseProvider:i})=>{let o=f();r.info("Next steps:"),e!=="."&&r.info(` cd ${e}`),a&&(o==="yarn"?r.info(` ${o}`):r.info(` ${o} install`)),["postgresql","mysql"].includes(i)&&r.info(" Add your database connection string to .env"),t?.nextAuth.inUse&&r.info(" Fill in your .env with necessary values. See https://create.tntstack.org/usage/first-steps for more info."),["npm"].includes(o)?r.info(` ${o} run dev`):r.info(` ${o} dev`),!await z(n)&&!V(n)&&r.info(" git init"),r.info(' git commit -m "initial commit"')};import j from"fs";import tt from"path";function _e(e,t,a){j.readdirSync(e).forEach(i=>{let o=tt.join(e,i);if(j.statSync(o).isDirectory())_e(o,t,a);else{let l=j.readFileSync(o,"utf8").replace(new RegExp(t,"g"),a);j.writeFileSync(o,l,"utf8")}})}function we(e,t){let a=t.replace(/\*/g,"").replace(/[^\/]$/,"$&/");_e(e,"@/",a)}import ke from"path";function Pe(e){let a=O(e).split("/"),n=a[a.length-1];if(n==="."){let s=ke.resolve(process.cwd());n=ke.basename(s)}let i=a.findIndex(s=>s.startsWith("@"));a.findIndex(s=>s.startsWith("@"))!==-1&&(n=a.slice(i).join("/"));let o=a.filter(s=>!s.startsWith("@")).join("/");return[n,o]}import nt from"gradient-string";var it={magenta:"#765bc8",pink:"#a48897",yellow:"#c7b561",green:"#8bb8a0",blue:"#4b97d5",cyan:"#22b6d2"};function xe(){let e=nt(Object.values(it)),t=f();(t==="yarn"||t==="pnpm")&&console.log(""),console.log(e.multiline(H))}import{execSync as at}from"child_process";import ot from"https";function Ie(e){let t=k();t.includes("beta")?(r.warn(" You are using a beta version of create-tnt-stack."),r.warn(" Please report any bugs you encounter.")):t!==e&&(r.warn(" You are using an outdated version of create-tnt-stack."),r.warn(" Your version:",t+".","Latest version in the npm registry:",e),r.warn(" Please run the CLI with @latest to get the latest updates.")),console.log("")}function st(){return new Promise((e,t)=>{ot.get("https://registry.npmjs.org/-/package/tnt-stack/dist-tags",a=>{if(a.statusCode===200){let n="";a.on("data",i=>n+=i),a.on("end",()=>{e(JSON.parse(n).latest)})}else t()}).on("error",()=>{t()})})}var Ae=()=>st().catch(()=>{try{return at("npm view create-tnt-stack version").toString().trim()}catch{return null}});async function lt(){let e=await Ae(),t=f();xe(),e&&Ie(e);let{appName:a,packages:n,flags:{noGit:i,noInstall:o,importAlias:s},databaseProvider:l}=await le(),m=ae(n),[d,p]=Pe(a),g=await de({projectName:p,scopedAppName:d,packages:m,noInstall:o,databaseProvider:l}),w=Ce.readJsonSync(Se.join(g,"package.json"));w.name=d,w.ctntaMetadata={initVersion:k()};let{stdout:M}=await rt(t,["-v"],{cwd:g});w.packageManager=`${t}@${M.trim()}`,Ce.writeJSONSync(Se.join(g,"package.json"),w,{spaces:2}),s!=="@/"&&we(g,s),o||await ve({projectDir:g}),i||await ue(g),await be({projectDir:g,projectName:p,packages:m,noInstall:o,databaseProvider:l}),process.exit(0)}lt().catch(e=>{r.error("Aborting installation..."),e instanceof Error?r.error(e.message):(r.error("An unknown error occurred. Please open an issue on GitHub with the below:"),console.error(e)),process.exit(1)});
48
+ can provide the arguments from the CLI directly: https://create.tntstack.org/installation#experimental-usage to skip the prompts.`),new C("Non-interactive environment");let n=y(),t={};s||(t.name=await ue({message:"What will your project be called?",default:I,validate:i=>fe(i)})),t.backend=await T({message:"What backend framework would you like to use?",choices:[{value:"nextjs",name:"Next.js"},{value:"payload",name:"Payload CMS"}],default:"nextjs"}),t.backend==="payload"&&(t.databaseProvider=await T({message:"What database provider would you like to use?",choices:[{value:"sqlite",name:"SQLite"},{value:"postgresql",name:"PostgreSQL"}],default:"sqlite"})),t.backend==="nextjs"&&(t.authentication=await T({message:"What authentication provider would you like to use?",choices:[{value:"none",name:"None"},{value:"nextAuth",name:"NextAuth.js"}],default:"none"}),t.database=await T({message:"What database ORM would you like to use?",choices:[{value:"none",name:"None"},{value:"prisma",name:"Prisma"}],default:"none"}),t.database!=="none"&&(t.databaseProvider=await T({message:"What database provider would you like to use?",choices:[{value:"sqlite",name:"SQLite"},{value:"mysql",name:"MySQL"},{value:"postgresql",name:"PostgreSQL"}],default:"sqlite"}))),t.prettier=await G({message:"Should we install Prettier?",default:!b.flags.prettier}),e.flags.noGit||(t.noGit=await G({message:"Should we initialize a Git repository and stage the changes?",default:!b.flags.noGit})),e.flags.noInstall||(t.noInstall=await G({message:`Should we run '${n}`+(n==="yarn"?"'?":" install' for you?"),default:!b.flags.noInstall})),t.importAlias=await ue({message:"What import alias would you like to use?",default:b.flags.importAlias,validate:ge});let a=[];return t.authentication==="nextAuth"&&a.push("nextAuth"),t.database==="prisma"&&a.push("prisma"),t.prettier&&a.push("prettier"),t.backend==="payload"&&a.push("payload"),{appName:t.name??e.appName,packages:a,flags:{...e.flags,noGit:!t.noGit||e.flags.noGit,noInstall:!t.noInstall||e.flags.noInstall,importAlias:t.importAlias??e.flags.importAlias},databaseProvider:t.databaseProvider||"sqlite"}}catch(n){if(n instanceof C)r.warn(`${$} needs an interactive terminal to run.`),await G({message:"Continue scaffolding with default options?",default:!0})||(r.info("Exiting..."),process.exit(0)),r.info(`Scaffolding default tnt app in ./${e.appName}`);else throw n}return e}import it from"path";import he from"chalk";import nt from"ora";function be(e){let{packages:o}=e;r.info("Adding boilerplate...");for(let[s,n]of Object.entries(o))if(n.inUse){let t=nt(`Boilerplating ${s}...`).start();n.installer(e),t.succeed(he.green(`${he.green.bold(s)}`))}r.info("")}import B from"path";import{confirm as at,select as ot}from"@inquirer/prompts";import S from"chalk";import j from"fs-extra";import st from"ora";async function ve({projectName:e,projectDir:o,pkgManager:s,noInstall:n}){let t=B.join(p,"template/base");n?r.info(""):r.info(`
49
+ Using: ${S.cyan.bold(s)}
50
+ `);let a=st(`Scaffolding in: ${o}...
51
+ `).start();if(j.existsSync(o))if(j.readdirSync(o).length===0)e!=="."&&a.info(`${S.cyan.bold(e)} exists but is empty, continuing...
52
+ `);else{a.stopAndPersist();let l=await ot({message:`${S.redBright.bold("Warning:")} ${S.cyan.bold(e)} already exists and isn't empty. How would you like to proceed?`,choices:[{value:"abort",name:"Abort installation (recommended)"},{value:"clear",name:"Clear the directory and continue installation"},{value:"overwrite",name:"Continue installation and overwrite conflicting files"}],default:"abort"});l==="abort"&&(a.fail("Aborting installation..."),process.exit(1)),await at({message:`Are you sure you want to ${l==="clear"?"clear the directory":"overwrite conflicting files"}`,default:!1})||(a.fail("Aborting installation..."),process.exit(1)),l==="clear"&&(a.info(`Emptying ${S.cyan.bold(e)} and creating tnt app...
53
+ `),j.emptyDirSync(o))}a.start(),j.copySync(t,o),j.renameSync(B.join(o,"_gitignore"),B.join(o,".gitignore"));let i=e==="."?"App":S.cyan.bold(e);a.succeed(`${i} ${S.green.bold("scaffolded successfully!")}
54
+ `)}import v from"path";import W from"fs-extra";function ke({packages:e,projectDir:o}){let s=v.join(p,"template/packages/src/app/layout"),n=e.payload.inUse,t="base.tsx";n&&(t="with-payload.tsx");let a=v.join(s,t),i=v.join(o,`src/app/${n?"(frontend)":""}/layout.tsx`);W.copySync(a,i)}function we({packages:e,projectDir:o}){let s=v.join(p,"template/packages/src/app/page"),n=e.payload.inUse,t="base.tsx";n&&(t="with-payload.tsx");let a=v.join(s,t),i=v.join(o,`src/app/${n?"(frontend)":""}/page.tsx`);W.copySync(a,i)}function _e({packages:e,projectDir:o}){let s=v.join(p,"template/packages/src/app/globals"),n=e.payload.inUse,t="base.css";n&&(t="with-payload.css");let a=v.join(s,t),i=v.join(o,`src/app/${n?"(frontend)":""}/globals.css`);W.copySync(a,i)}async function xe({projectName:e,scopedAppName:o,packages:s,noInstall:n,databaseProvider:t}){let a=y(),i=it.resolve(process.cwd(),e);return await ve({projectName:e,projectDir:i,pkgManager:a,scopedAppName:o,noInstall:n,databaseProvider:t}),be({projectName:e,scopedAppName:o,projectDir:i,pkgManager:a,packages:s,noInstall:n,databaseProvider:t}),ke({packages:s,projectDir:i}),we({packages:s,projectDir:i}),_e({packages:s,projectDir:i}),i}import{execSync as z}from"child_process";import V from"path";import{confirm as Pe}from"@inquirer/prompts";import O from"chalk";import{execa as D}from"execa";import Se from"fs-extra";import rt from"ora";function lt(e){try{return z("git --version",{cwd:e}),!0}catch{return!1}}function K(e){return Se.existsSync(V.join(e,".git"))}async function H(e){try{return await D("git",["rev-parse","--is-inside-work-tree"],{cwd:e,stdout:"ignore"}),!0}catch{return!1}}function pt(){let o=z("git --version").toString().trim().split(" ")[2],s=o?.split(".")[0],n=o?.split(".")[1];return{major:Number(s),minor:Number(n)}}function ct(){return z("git config --global init.defaultBranch || echo main").toString().trim()}async function Ae(e){if(r.info("Initializing Git..."),!lt(e)){r.warn("Git is not installed. Skipping Git initialization.");return}let o=rt(`Creating a new git repo...
55
+ `).start(),s=K(e),n=await H(e),t=V.parse(e).name;if(n&&s){if(o.stop(),!await Pe({message:`${O.redBright.bold("Warning:")} Git is already initialized in "${t}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,default:!1})){o.info("Skipping Git initialization.");return}Se.removeSync(V.join(e,".git"))}else if(n&&!s&&(o.stop(),!await Pe({message:`${O.redBright.bold("Warning:")} "${t}" is already in a git worktree. Would you still like to initialize a new git repository in this directory?`,default:!1}))){o.info("Skipping Git initialization.");return}try{let a=ct(),{major:i,minor:l}=pt();i<2||i==2&&l<28?(await D("git",["init"],{cwd:e}),await D("git",["symbolic-ref","HEAD",`refs/heads/${a}`],{cwd:e})):await D("git",["init",`--initial-branch=${a}`],{cwd:e}),await D("git",["add","."],{cwd:e}),o.succeed(`${O.green("Successfully initialized and staged")} ${O.green.bold("git")}
56
+ `)}catch{o.fail(`${O.bold.red("Failed:")} could not initialize git. Update git to the latest version!
57
+ `)}}import mt from"chalk";import{execa as Ie}from"execa";import Ce from"ora";var Y=async(e,o,s)=>{let{onDataHandle:n,args:t=["install"],stdout:a="pipe"}=s,i=Ce(`Running ${o} install...`).start(),l=Ie(o,t,{cwd:e,stdout:a});return await new Promise((m,f)=>{n&&l.stdout?.on("data",n(i)),l.on("error",g=>f(g)),l.on("close",()=>m())}),i},dt=async(e,o)=>{switch(e){case"npm":return await Ie(e,["install"],{cwd:o,stderr:"inherit"}),null;case"pnpm":return Y(o,e,{onDataHandle:s=>n=>{let t=n.toString();t.includes("Progress")&&(s.text=t.includes("|")?t.split(" | ")[1]??"":t)}});case"yarn":return Y(o,e,{onDataHandle:s=>n=>{s.text=n.toString()}});case"bun":return Y(o,e,{stdout:"ignore"})}},Te=async({projectDir:e})=>{r.info("Installing dependencies...");let o=y();(await dt(o,e)??Ce()).succeed(mt.green(`Successfully installed dependencies!
58
+ `))};var je=async({projectName:e=I,packages:o,noInstall:s,projectDir:n,databaseProvider:t})=>{let a=y();r.info("Next steps:"),e!=="."&&r.info(` cd ${e}`),s&&(a==="yarn"?r.info(` ${a}`):r.info(` ${a} install`)),["postgresql","mysql"].includes(t)&&r.info(" Add your database connection string to .env"),o?.nextAuth.inUse&&r.info(" Fill in your .env with necessary values. See https://create.tntstack.org/usage/first-steps for more info."),["npm"].includes(a)?r.info(` ${a} run dev`):r.info(` ${a} dev`),!await H(n)&&!K(n)&&r.info(" git init"),r.info(' git commit -m "initial commit"')};import U from"fs";import ft from"path";function Oe(e,o,s){U.readdirSync(e).forEach(t=>{let a=ft.join(e,t);if(U.statSync(a).isDirectory())Oe(a,o,s);else{let l=U.readFileSync(a,"utf8").replace(new RegExp(o,"g"),s);U.writeFileSync(a,l,"utf8")}})}function De(e,o){let s=o.replace(/\*/g,"").replace(/[^\/]$/,"$&/");Oe(e,"@/",s)}import Ee from"path";function Me(e){let s=N(e).split("/"),n=s[s.length-1];if(n==="."){let i=Ee.resolve(process.cwd());n=Ee.basename(i)}let t=s.findIndex(i=>i.startsWith("@"));s.findIndex(i=>i.startsWith("@"))!==-1&&(n=s.slice(t).join("/"));let a=s.filter(i=>!i.startsWith("@")).join("/");return[n,a]}import gt from"gradient-string";var ut={magenta:"#765bc8",pink:"#a48897",yellow:"#c7b561",green:"#8bb8a0",blue:"#4b97d5",cyan:"#22b6d2"};function Re(){let e=gt(Object.values(ut)),o=y();(o==="yarn"||o==="pnpm")&&console.log(""),console.log(e.multiline(Z))}import{execSync as yt}from"child_process";import ht from"https";function Ne(e){let o=A();o.includes("beta")?(r.warn(" You are using a beta version of create-tnt-stack."),r.warn(" Please report any bugs you encounter.")):o!==e&&(r.warn(" You are using an outdated version of create-tnt-stack."),r.warn(" Your version:",o+".","Latest version in the npm registry:",e),r.warn(" Please run the CLI with @latest to get the latest updates.")),console.log("")}function bt(){return new Promise((e,o)=>{ht.get("https://registry.npmjs.org/-/package/tnt-stack/dist-tags",s=>{if(s.statusCode===200){let n="";s.on("data",t=>n+=t),s.on("end",()=>{e(JSON.parse(n).latest)})}else o()}).on("error",()=>{o()})})}var Ge=()=>bt().catch(()=>{try{return yt("npm view create-tnt-stack version").toString().trim()}catch{return null}});async function kt(){let e=await Ge(),o=y();Re(),e&&Ne(e);let{appName:s,packages:n,flags:{noGit:t,noInstall:a,importAlias:i},databaseProvider:l}=await ye(),m=de(n),[f,g]=Me(s),c=await xe({projectName:g,scopedAppName:f,packages:m,noInstall:a,databaseProvider:l}),h=$e.readJsonSync(Ue.join(c,"package.json"));h.name=f,h.ctntaMetadata={initVersion:A()};let{stdout:x}=await vt(o,["-v"],{cwd:c});h.packageManager=`${o}@${x.trim()}`,$e.writeJSONSync(Ue.join(c,"package.json"),h,{spaces:2}),i!=="@/"&&De(c,i),a||await Te({projectDir:c}),t||await Ae(c),await je({projectDir:c,projectName:g,packages:m,noInstall:a,databaseProvider:l}),process.exit(0)}kt().catch(e=>{r.error("Aborting installation..."),e instanceof Error?r.error(e.message):(r.error("An unknown error occurred. Please open an issue on GitHub with the below:"),console.error(e)),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tnt-stack",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Create web application with the TNT-Powered stack",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,21 +54,32 @@
54
54
  },
55
55
  "devDependencies": {
56
56
  "@auth/prisma-adapter": "^2.8.0",
57
- "@eslint/eslintrc": "^3.3.0",
57
+ "@eslint/eslintrc": "^3.3.1",
58
58
  "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
59
+ "@payloadcms/db-sqlite": "^3.33.0",
60
+ "@payloadcms/db-vercel-postgres": "^3.33.0",
61
+ "@payloadcms/next": "^3.33.0",
62
+ "@payloadcms/payload-cloud": "^3.33.0",
63
+ "@payloadcms/richtext-lexical": "^3.33.0",
59
64
  "@prisma/client": "^6.5.0",
60
65
  "@t3-oss/env-nextjs": "^0.12.0",
61
66
  "@types/fs-extra": "^11.0.4",
67
+ "@types/node": "^22",
68
+ "@types/react": "^19",
69
+ "@types/react-dom": "^19",
62
70
  "eslint": "^9.22.0",
63
- "eslint-config-next": "^15.2.3",
64
- "next": "^15.2.3",
71
+ "eslint-config-next": "^15.2.4",
72
+ "graphql": "^16.10.0",
73
+ "next": "^15.2.4",
65
74
  "next-auth": "^4.24.11",
75
+ "payload": "^3.33.0",
66
76
  "prettier": "^3.5.3",
67
77
  "prettier-plugin-tailwindcss": "^0.6.11",
68
78
  "prisma": "^6.5.0",
69
79
  "react": "^19.0.0",
70
80
  "react-dom": "^19.0.0",
71
- "tailwindcss": "^4.0.14",
81
+ "sharp": "^0.34.1",
82
+ "tailwindcss": "^4.0.17",
72
83
  "tsup": "^8.4.0",
73
84
  "type-fest": "^4.37.0",
74
85
  "typescript": "^5.8.2",
@@ -11,14 +11,10 @@
11
11
  "dependencies": {
12
12
  "react": "^19.0.0",
13
13
  "react-dom": "^19.0.0",
14
- "next": "^15.2.3"
14
+ "next": "^15.2.4"
15
15
  },
16
16
  "devDependencies": {
17
- "typescript": "^5.8.2",
18
- "@types/node": "^22",
19
- "@types/react": "^19",
20
- "@types/react-dom": "^19",
21
17
  "@tailwindcss/postcss": "^4",
22
- "tailwindcss": "^4.0.14"
18
+ "tailwindcss": "^4.0.17"
23
19
  }
24
20
  }
@@ -0,0 +1,39 @@
1
+ // storage-adapter-import-placeholder
2
+ import path from "path"
3
+ import { fileURLToPath } from "url"
4
+ import { vercelPostgresAdapter } from "@payloadcms/db-vercel-postgres"
5
+ import { payloadCloudPlugin } from "@payloadcms/payload-cloud"
6
+ import { lexicalEditor } from "@payloadcms/richtext-lexical"
7
+ import { buildConfig } from "payload"
8
+ import sharp from "sharp"
9
+
10
+ import { Media } from "@/collections/Media"
11
+ import { Users } from "@/collections/Users"
12
+
13
+ const filename = fileURLToPath(import.meta.url)
14
+ const dirname = path.dirname(filename)
15
+
16
+ export default buildConfig({
17
+ admin: {
18
+ user: Users.slug,
19
+ importMap: {
20
+ baseDir: path.resolve(dirname),
21
+ },
22
+ },
23
+ collections: [Users, Media],
24
+ editor: lexicalEditor(),
25
+ secret: process.env.PAYLOAD_SECRET || "",
26
+ typescript: {
27
+ outputFile: path.resolve(dirname, "payload-types.ts"),
28
+ },
29
+ db: vercelPostgresAdapter({
30
+ pool: {
31
+ connectionString: process.env.DATABASE_URL || "",
32
+ },
33
+ }),
34
+ sharp,
35
+ plugins: [
36
+ payloadCloudPlugin(),
37
+ // storage-adapter-placeholder
38
+ ],
39
+ })
@@ -0,0 +1,42 @@
1
+ // storage-adapter-import-placeholder
2
+ import path from "path"
3
+ import { fileURLToPath } from "url"
4
+ import { sqliteAdapter } from "@payloadcms/db-sqlite"
5
+ import { payloadCloudPlugin } from "@payloadcms/payload-cloud"
6
+ import { lexicalEditor } from "@payloadcms/richtext-lexical"
7
+ import { buildConfig } from "payload"
8
+ import sharp from "sharp"
9
+
10
+ import { Media } from "@/collections/Media"
11
+ import { Users } from "@/collections/Users"
12
+
13
+ const filename = fileURLToPath(import.meta.url)
14
+ const dirname = path.dirname(filename)
15
+
16
+ export default buildConfig({
17
+ admin: {
18
+ user: Users.slug,
19
+ importMap: {
20
+ baseDir: path.resolve(dirname),
21
+ },
22
+ },
23
+ collections: [Users, Media],
24
+ editor: lexicalEditor(),
25
+ secret: process.env.PAYLOAD_SECRET || "",
26
+ typescript: {
27
+ outputFile: path.resolve(dirname, "payload-types.ts"),
28
+ },
29
+ db: sqliteAdapter({
30
+ // SQLite-specific arguments go here.
31
+ // `client.url` is required.
32
+ client: {
33
+ url: process.env.DATABASE_URL || "",
34
+ authToken: process.env.DATABASE_AUTH_TOKEN,
35
+ },
36
+ }),
37
+ sharp,
38
+ plugins: [
39
+ payloadCloudPlugin(),
40
+ // storage-adapter-placeholder
41
+ ],
42
+ })
@@ -0,0 +1,43 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Base Options: */
4
+ "esModuleInterop": true,
5
+ "skipLibCheck": true,
6
+ "target": "ES2017",
7
+ "allowJs": true,
8
+ "resolveJsonModule": true,
9
+ "isolatedModules": true,
10
+
11
+ /* Strictness */
12
+ "strict": true,
13
+ "checkJs": true,
14
+
15
+ /* Bundled projects */
16
+ "lib": ["dom", "dom.iterable", "esnext"],
17
+ "noEmit": true,
18
+ "module": "esnext",
19
+ "moduleResolution": "bundler",
20
+ "jsx": "preserve",
21
+ "incremental": true,
22
+ "plugins": [
23
+ {
24
+ "name": "next"
25
+ }
26
+ ],
27
+
28
+ /* Path Aliases */
29
+ "baseUrl": ".",
30
+ "paths": {
31
+ "@/*": ["./src/*"],
32
+ "@payload-config": ["./payload.config.ts"]
33
+ }
34
+ },
35
+ "include": [
36
+ "next-env.d.ts",
37
+ "**/*.ts",
38
+ "**/*.tsx",
39
+ "**/*.js",
40
+ ".next/types/**/*.ts"
41
+ ],
42
+ "exclude": ["node_modules"]
43
+ }
@@ -0,0 +1,27 @@
1
+ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2
+ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
3
+ import config from "@payload-config"
4
+ import { generatePageMetadata, NotFoundPage } from "@payloadcms/next/views"
5
+ import type { Metadata } from "next"
6
+
7
+ import { importMap } from "../importMap"
8
+
9
+ type Args = {
10
+ params: Promise<{
11
+ segments: string[]
12
+ }>
13
+ searchParams: Promise<{
14
+ [key: string]: string | string[]
15
+ }>
16
+ }
17
+
18
+ export const generateMetadata = ({
19
+ params,
20
+ searchParams,
21
+ }: Args): Promise<Metadata> =>
22
+ generatePageMetadata({ config, params, searchParams })
23
+
24
+ const NotFound = ({ params, searchParams }: Args) =>
25
+ NotFoundPage({ config, params, searchParams, importMap })
26
+
27
+ export default NotFound
@@ -0,0 +1,27 @@
1
+ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2
+ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
3
+ import config from "@payload-config"
4
+ import { generatePageMetadata, RootPage } from "@payloadcms/next/views"
5
+ import type { Metadata } from "next"
6
+
7
+ import { importMap } from "../importMap"
8
+
9
+ type Args = {
10
+ params: Promise<{
11
+ segments: string[]
12
+ }>
13
+ searchParams: Promise<{
14
+ [key: string]: string | string[]
15
+ }>
16
+ }
17
+
18
+ export const generateMetadata = ({
19
+ params,
20
+ searchParams,
21
+ }: Args): Promise<Metadata> =>
22
+ generatePageMetadata({ config, params, searchParams })
23
+
24
+ const Page = ({ params, searchParams }: Args) =>
25
+ RootPage({ config, params, searchParams, importMap })
26
+
27
+ export default Page
@@ -0,0 +1 @@
1
+ export const importMap = {}
@@ -0,0 +1,20 @@
1
+ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2
+ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
3
+ import config from "@payload-config"
4
+ import {
5
+ REST_DELETE,
6
+ REST_GET,
7
+ REST_OPTIONS,
8
+ REST_PATCH,
9
+ REST_POST,
10
+ REST_PUT,
11
+ } from "@payloadcms/next/routes"
12
+
13
+ import "@payloadcms/next/css"
14
+
15
+ export const GET = REST_GET(config)
16
+ export const POST = REST_POST(config)
17
+ export const DELETE = REST_DELETE(config)
18
+ export const PATCH = REST_PATCH(config)
19
+ export const PUT = REST_PUT(config)
20
+ export const OPTIONS = REST_OPTIONS(config)
@@ -0,0 +1,8 @@
1
+ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2
+ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
3
+ import config from "@payload-config"
4
+ import { GRAPHQL_POST, REST_OPTIONS } from "@payloadcms/next/routes"
5
+
6
+ export const POST = GRAPHQL_POST(config)
7
+
8
+ export const OPTIONS = REST_OPTIONS(config)
@@ -0,0 +1,8 @@
1
+ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2
+ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
3
+ import config from "@payload-config"
4
+ import { GRAPHQL_PLAYGROUND_GET } from "@payloadcms/next/routes"
5
+
6
+ import "@payloadcms/next/css"
7
+
8
+ export const GET = GRAPHQL_PLAYGROUND_GET(config)
@@ -0,0 +1,35 @@
1
+ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
2
+ /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
3
+ import config from "@payload-config"
4
+ import { handleServerFunctions, RootLayout } from "@payloadcms/next/layouts"
5
+ import type { ServerFunctionClient } from "payload"
6
+
7
+ import { importMap } from "./admin/importMap.js"
8
+
9
+ import "@payloadcms/next/css"
10
+ import "./custom.scss"
11
+
12
+ type Args = {
13
+ children: React.ReactNode
14
+ }
15
+
16
+ const serverFunction: ServerFunctionClient = async function (args) {
17
+ "use server"
18
+ return handleServerFunctions({
19
+ ...args,
20
+ config,
21
+ importMap,
22
+ })
23
+ }
24
+
25
+ const Layout = ({ children }: Args) => (
26
+ <RootLayout
27
+ config={config}
28
+ importMap={importMap}
29
+ serverFunction={serverFunction}
30
+ >
31
+ {children}
32
+ </RootLayout>
33
+ )
34
+
35
+ export default Layout
@@ -0,0 +1,166 @@
1
+ @import "tailwindcss";
2
+
3
+ :root {
4
+ --font-mono: "Roboto Mono", monospace;
5
+ }
6
+
7
+ * {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ html {
12
+ font-size: 18px;
13
+ line-height: 32px;
14
+
15
+ background: rgb(0, 0, 0);
16
+ -webkit-font-smoothing: antialiased;
17
+ }
18
+
19
+ html,
20
+ body,
21
+ #app {
22
+ height: 100%;
23
+ }
24
+
25
+ body {
26
+ font-family: system-ui;
27
+ font-size: 18px;
28
+ line-height: 32px;
29
+
30
+ margin: 0;
31
+ color: rgb(1000, 1000, 1000);
32
+
33
+ @media (max-width: 1024px) {
34
+ font-size: 15px;
35
+ line-height: 24px;
36
+ }
37
+ }
38
+
39
+ img {
40
+ max-width: 100%;
41
+ height: auto;
42
+ display: block;
43
+ }
44
+
45
+ h1 {
46
+ margin: 40px 0;
47
+ font-size: 64px;
48
+ line-height: 70px;
49
+ font-weight: bold;
50
+
51
+ @media (max-width: 1024px) {
52
+ margin: 24px 0;
53
+ font-size: 42px;
54
+ line-height: 42px;
55
+ }
56
+
57
+ @media (max-width: 768px) {
58
+ font-size: 38px;
59
+ line-height: 38px;
60
+ }
61
+
62
+ @media (max-width: 400px) {
63
+ font-size: 32px;
64
+ line-height: 32px;
65
+ }
66
+ }
67
+
68
+ p {
69
+ margin: 24px 0;
70
+
71
+ @media (max-width: 1024px) {
72
+ margin: calc(var(--base) * 0.75) 0;
73
+ }
74
+ }
75
+
76
+ a {
77
+ color: currentColor;
78
+
79
+ &:focus {
80
+ opacity: 0.8;
81
+ outline: none;
82
+ }
83
+
84
+ &:active {
85
+ opacity: 0.7;
86
+ outline: none;
87
+ }
88
+ }
89
+
90
+ svg {
91
+ vertical-align: middle;
92
+ }
93
+
94
+ .home {
95
+ display: flex;
96
+ flex-direction: column;
97
+ justify-content: space-between;
98
+ align-items: center;
99
+ height: 100vh;
100
+ padding: 45px;
101
+ max-width: 1024px;
102
+ margin: 0 auto;
103
+ overflow: hidden;
104
+
105
+ @media (max-width: 400px) {
106
+ padding: 24px;
107
+ }
108
+
109
+ .content {
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: center;
113
+ justify-content: center;
114
+ flex-grow: 1;
115
+
116
+ h1 {
117
+ text-align: center;
118
+ }
119
+ }
120
+
121
+ .links {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 12px;
125
+
126
+ a {
127
+ text-decoration: none;
128
+ padding: 0.25rem 0.5rem;
129
+ border-radius: 4px;
130
+ }
131
+
132
+ .admin {
133
+ color: rgb(0, 0, 0);
134
+ background: rgb(1000, 1000, 1000);
135
+ border: 1px solid rgb(0, 0, 0);
136
+ }
137
+
138
+ .docs {
139
+ color: rgb(1000, 1000, 1000);
140
+ background: rgb(0, 0, 0);
141
+ border: 1px solid rgb(1000, 1000, 1000);
142
+ }
143
+ }
144
+
145
+ .footer {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 8px;
149
+
150
+ @media (max-width: 1024px) {
151
+ flex-direction: column;
152
+ gap: 6px;
153
+ }
154
+
155
+ p {
156
+ margin: 0;
157
+ }
158
+
159
+ .codeLink {
160
+ text-decoration: none;
161
+ padding: 0 0.5rem;
162
+ background: rgb(60, 60, 60);
163
+ border-radius: 4px;
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,20 @@
1
+ import type { Metadata } from "next"
2
+
3
+ import "./globals.css"
4
+
5
+ export const metadata: Metadata = {
6
+ description: "A blank template using Payload in a Next.js app.",
7
+ title: "Payload Blank Template",
8
+ }
9
+
10
+ export default function RootLayout({
11
+ children,
12
+ }: Readonly<{
13
+ children: React.ReactNode
14
+ }>) {
15
+ return (
16
+ <html lang="en">
17
+ <body className="antialiased">{children}</body>
18
+ </html>
19
+ )
20
+ }
@@ -55,7 +55,7 @@ export default function HomePage() {
55
55
  </Link>
56
56
 
57
57
  <Link
58
- href="https://create.tntstack.org/docs"
58
+ href="https://create.tntstack.org/introduction"
59
59
  target="_blank"
60
60
  referrerPolicy="no-referrer"
61
61
  className="hover:text-primary relative flex items-center justify-center gap-2 text-lg font-medium"
@@ -0,0 +1,58 @@
1
+ import { fileURLToPath } from "url"
2
+ import config from "@payload-config"
3
+ import { headers as getHeaders } from "next/headers.js"
4
+ import Image from "next/image"
5
+ import { getPayload } from "payload"
6
+
7
+ import "./globals.css"
8
+
9
+ export default async function HomePage() {
10
+ const headers = await getHeaders()
11
+ const payloadConfig = await config
12
+ const payload = await getPayload({ config: payloadConfig })
13
+ const { user } = await payload.auth({ headers })
14
+
15
+ const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}`
16
+
17
+ return (
18
+ <div className="home">
19
+ <div className="content">
20
+ <picture>
21
+ <source srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-favicon.svg" />
22
+ <Image
23
+ alt="Payload Logo"
24
+ height={65}
25
+ src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-favicon.svg"
26
+ width={65}
27
+ />
28
+ </picture>
29
+ {!user && <h1>Welcome to your new project.</h1>}
30
+ {user && <h1>Welcome back, {user.email}</h1>}
31
+ <div className="links">
32
+ <a
33
+ className="admin"
34
+ href={payloadConfig.routes.admin}
35
+ rel="noopener noreferrer"
36
+ target="_blank"
37
+ >
38
+ Go to admin panel
39
+ </a>
40
+ <a
41
+ className="docs"
42
+ href="https://payloadcms.com/docs"
43
+ rel="noopener noreferrer"
44
+ target="_blank"
45
+ >
46
+ Documentation
47
+ </a>
48
+ </div>
49
+ </div>
50
+ <div className="footer">
51
+ <p>Update this page by editing</p>
52
+ <a className="codeLink" href={fileURL}>
53
+ <code>app/(frontend)/page.tsx</code>
54
+ </a>
55
+ </div>
56
+ </div>
57
+ )
58
+ }
@@ -0,0 +1,16 @@
1
+ import type { CollectionConfig } from "payload"
2
+
3
+ export const Media: CollectionConfig = {
4
+ slug: "media",
5
+ access: {
6
+ read: () => true,
7
+ },
8
+ fields: [
9
+ {
10
+ name: "alt",
11
+ type: "text",
12
+ required: true,
13
+ },
14
+ ],
15
+ upload: true,
16
+ }
@@ -0,0 +1,13 @@
1
+ import type { CollectionConfig } from "payload"
2
+
3
+ export const Users: CollectionConfig = {
4
+ slug: "users",
5
+ admin: {
6
+ useAsTitle: "email",
7
+ },
8
+ auth: true,
9
+ fields: [
10
+ // Email added by default
11
+ // Add more fields as needed
12
+ ],
13
+ }