├── .eslintignore
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ └── PR-CI.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── commitlint.config.cjs
├── package.json
├── pnpm-lock.yaml
├── src
├── cli
│ └── index.ts
├── consts.ts
├── helpers
│ ├── createProject.ts
│ ├── initGit.ts
│ ├── installPackages.ts
│ ├── logNextSteps.ts
│ ├── scaffoldProject.ts
│ └── selectBoilerplate.ts
├── index.ts
├── installers
│ ├── index.ts
│ ├── next-auth.ts
│ ├── prisma.ts
│ ├── tailwind.ts
│ └── trpc.ts
└── utils
│ ├── execAsync.ts
│ ├── getT3Version.ts
│ ├── getUserPkgManager.ts
│ ├── logger.ts
│ ├── parseNameAndPath.ts
│ ├── renderTitle.ts
│ ├── runPkgManagerInstall.ts
│ └── validateAppName.ts
├── template
├── addons
│ ├── next-auth
│ │ ├── api-handler-prisma.ts
│ │ ├── api-handler.ts
│ │ └── restricted.ts
│ ├── prisma
│ │ ├── auth-schema.prisma
│ │ ├── client.ts
│ │ ├── sample-api.ts
│ │ └── schema.prisma
│ ├── tailwind
│ │ ├── globals.css
│ │ ├── postcss.config.js
│ │ └── tailwind.config.js
│ └── trpc
│ │ ├── api-handler.ts
│ │ ├── auth-context.ts
│ │ ├── auth-index-router.ts
│ │ ├── auth-prisma-context.ts
│ │ ├── auth-router.ts
│ │ ├── base-context.ts
│ │ ├── example-prisma-router.ts
│ │ ├── example-router.ts
│ │ ├── index-router.ts
│ │ ├── prisma-context.ts
│ │ └── utils.ts
├── base
│ ├── .env-example
│ ├── .eslintrc.json
│ ├── README.md
│ ├── _gitignore
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── pages
│ │ │ ├── _app.tsx
│ │ │ └── index.tsx
│ │ └── styles
│ │ │ └── globals.css
│ └── tsconfig.json
└── page-studs
│ ├── README.md
│ ├── _app
│ ├── with-auth-trpc.tsx
│ ├── with-auth.tsx
│ └── with-trpc.tsx
│ └── index
│ ├── with-auth-trpc-tw.tsx
│ ├── with-auth-trpc.tsx
│ ├── with-trpc-tw.tsx
│ ├── with-trpc.tsx
│ └── with-tw.tsx
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | dist/
4 | template/
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/eslintrc",
3 | "root": true,
4 | "parser": "@typescript-eslint/parser",
5 | "parserOptions": {
6 | "project": "./tsconfig.json"
7 | },
8 | "plugins": ["@typescript-eslint", "import"],
9 | "extends": [
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
12 | "plugin:@typescript-eslint/strict",
13 | "plugin:import/recommended",
14 | "plugin:import/typescript",
15 | "prettier"
16 | ],
17 | "env": {
18 | "node": true,
19 | "es6": true
20 | },
21 | "rules": {
22 | "no-shadow": 1,
23 | "import/no-named-as-default": 0,
24 | "import/no-named-as-default-member": 0,
25 | "import/order": [
26 | "error",
27 | {
28 | "groups": [
29 | "type",
30 | "builtin",
31 | "external",
32 | "internal",
33 | ["parent", "sibling", "index"],
34 | "unknown"
35 | ],
36 | "newlines-between": "never",
37 | "alphabetize": {
38 | "order": "asc",
39 | "caseInsensitive": true
40 | },
41 | "warnOnUnassignedImports": true
42 | }
43 | ]
44 | },
45 | "settings": {
46 | "import/resolver": {
47 | "typescript": {
48 | "alwaysTryTypes": true,
49 | "project": "./tsconfig.json"
50 | }
51 | }
52 | },
53 | "ignorePatterns": ["node_modules/", "build/", "dist/"]
54 | }
55 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Run this '...'
16 | 2. See error
17 |
18 | **Expected behavior**
19 | A clear and concise description of what you expected to happen.
20 |
21 | **Screenshots**
22 | If applicable, add screenshots to help explain your problem.
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # [Short title]
2 |
3 | - [ ] I reviewed linter warnings + errors, resolved formatting, types and other issues related to my work
4 | - [ ] The PR title follows the convention we established [conventional-commit](https://www.conventionalcommits.org/en/v1.0.0/)
5 | - [ ] I performed a functional test on my final commit
6 |
7 | ---
8 |
9 | _[Short summary/description of story/feature/bugfix]_
10 |
11 | ---
12 |
13 | ## Screenshots
14 |
15 | _[Screenshots]_
16 |
17 | 💯
18 |
--------------------------------------------------------------------------------
/.github/workflows/PR-CI.yml:
--------------------------------------------------------------------------------
1 | # this workflow will run on every pr to make sure the project is following the guidelines
2 |
3 | name: PR-CI
4 |
5 | on:
6 | pull_request:
7 | branches: "*"
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | jobs:
13 | lint:
14 | name: Run ESLint
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: pnpm/action-setup@v2
19 | with:
20 | version: 7.2.1
21 | - uses: actions/setup-node@v3
22 | with:
23 | node-version: 16
24 | cache: 'pnpm'
25 | - run: pnpm install
26 | - run: pnpm lint:check
27 |
28 | lint-pr:
29 | name: Lint the PR title
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: amannn/action-semantic-pull-request@v4
33 | env:
34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 |
36 | prettier:
37 | name: Run Prettier Check
38 | runs-on: ubuntu-latest
39 | steps:
40 | - uses: actions/checkout@v2
41 | - uses: pnpm/action-setup@v2
42 | with:
43 | version: 7.2.1
44 | - uses: actions/setup-node@v3
45 | with:
46 | node-version: 16
47 | cache: 'pnpm'
48 | - run: pnpm install
49 | - run: pnpm format:check
50 |
51 | tsc:
52 | name: Run Type Check
53 | runs-on: ubuntu-latest
54 | steps:
55 | - uses: actions/checkout@v2
56 | - uses: pnpm/action-setup@v2
57 | with:
58 | version: 7.2.1
59 | - uses: actions/setup-node@v3
60 | with:
61 | node-version: 16
62 | cache: 'pnpm'
63 | - run: pnpm install
64 | - run: pnpm typecheck
65 |
66 | build:
67 | name: Build the project
68 | runs-on: ubuntu-latest
69 | steps:
70 | - uses: actions/checkout@v2
71 | - uses: pnpm/action-setup@v2
72 | with:
73 | version: 7.2.1
74 | - uses: actions/setup-node@v3
75 | with:
76 | node-version: 16
77 | cache: 'pnpm'
78 | - run: pnpm install
79 | - run: pnpm build
80 |
81 | build-cardano-dapp:
82 | name: Build an outputted cardano dapp
83 | runs-on: ubuntu-latest
84 | steps:
85 | - uses: actions/checkout@v2
86 | - uses: pnpm/action-setup@v2
87 | with:
88 | version: 7.2.1
89 | - uses: actions/setup-node@v3
90 | with:
91 | node-version: 16
92 | cache: "pnpm"
93 | - run: pnpm install
94 | - run: pnpm build
95 | # has to be scaffolded outside the CLI project so that no lint/tsconfig are leaking
96 | # through. this way it ensures that it is the app's configs that are being used
97 | - run: pnpm start -y ../ci-test-app
98 | - run: cd ../ci-test-app && pnpm build
99 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### STANDARD GIT IGNORE FILE ###
2 |
3 | # DEPENDENCIES
4 | node_modules/
5 | /.pnp
6 | .pnp.js
7 |
8 | # TESTING
9 | /coverage
10 | *.lcov
11 | .nyc_output
12 |
13 | # BUILD
14 | build/
15 | public/build/
16 | dist/
17 | generated/
18 |
19 | # ENV FILES
20 | .env
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 |
26 | # LOGS
27 | logs
28 | *.log
29 | npm-debug.log*
30 | yarn-debug.log*
31 | yarn-error.log*
32 |
33 | # MISC
34 | .idea
35 | .turbo/
36 | .cache/
37 | .next/
38 | .nuxt/
39 | tmp/
40 | temp/
41 | .eslintcache
42 |
43 | # MAC
44 | ._*
45 | .DS_Store
46 | Thumbs.db
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --config commitlint.config.cjs --edit "$1"
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build/
3 | dist/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "arrowParens": "always",
4 | "printWidth": 80,
5 | "singleQuote": false,
6 | "jsxSingleQuote": false,
7 | "semi": true,
8 | "trailingComma": "all",
9 | "tabWidth": 2
10 | }
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [0.1.6](https://github.com/CardanoGoat-io/create-cardano-dapp/compare/v0.1.5...v0.1.6) (2022-09-24)
6 |
7 | ### [0.1.5](https://github.com/CardanoGoat-io/create-cardano-dapp/compare/v0.1.4...v0.1.5) (2022-07-12)
8 |
9 | ### Features
10 |
11 | - adding lucid and env examples ([e6af423](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/e6af4233d5d532eea934259a2ae2122e571db23b))
12 |
13 | ### Bug Fixes
14 |
15 | - adding cardano dapp terminology and removing javascript option ([ab81e94](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/ab81e949e56d47c1829e63a13b4e3c6bde966210))
16 |
17 | ### [0.1.4](https://github.com/CardanoGoat-io/create-cardano-dapp/compare/v0.1.3...v0.1.4) (2022-07-12)
18 |
19 | ### Bug Fixes
20 |
21 | - removing javascript option ([5523abd](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/5523abdcdf820817a84092f95440d4faa8806a3e))
22 |
23 | ### [0.1.3](https://github.com/CardanoGoat-io/create-cardano-dapp/compare/v0.1.2...v0.1.3) (2022-07-11)
24 |
25 | ### Features
26 |
27 | - update icon ([ac655c0](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/ac655c09bf53046484b774ee5f5c471e4dddf551))
28 |
29 | ### [0.1.2](https://github.com/CardanoGoat-io/create-cardano-dapp/compare/v0.1.1...v0.1.2) (2022-07-11)
30 |
31 | ### Features
32 |
33 | - updating stack data and adding readme ([7463d0f](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/7463d0fde68ab750fe737e6e4f1ade6186e2f669))
34 |
35 | ### Bug Fixes
36 |
37 | - github yaml and contributing page ([6f4b4a8](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/6f4b4a8e6956b575107764140fde96b1bbb6a14f))
38 |
39 | ### 0.1.1 (2022-07-11)
40 |
41 | ### Features
42 |
43 | - src typescript file that is missing cardano and wasm ([b08eebd](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/b08eebded217362f377ed20b476ad05469463ad2))
44 | - templating files for prisma nextjs auth tailwind trpc and base react project ([9024ceb](https://github.com/CardanoGoat-io/create-cardano-dapp/commit/9024ceb12920a13f972f5f10b7e384e1e069befa))
45 |
46 | # create-cardano-dapp
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to create-cardano-dapp
2 |
3 | ## Contribution guidelines
4 |
5 | - Be respectful, civil, and open-minded.
6 | - Before opening a new pull request, try searching through the [issue tracker](https://github.com/CardanoGoat-io/create-cardano-dapp/issues) for known issues or fixes.
7 |
8 | ## How to contribute
9 |
10 | 1. [Open an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue) and describe the problem.
11 | 2. [Fork this repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) to your own GitHub account, then [clone the repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) to your machine.
12 | 3. Follow the [README](https://github.com/cardanogoat-io/create-cardano-dapp#readme) to install the project.
13 | 4. Create a new branch and implement your changes.
14 |
15 | - Note: We use `commitlint` to autoupdate the changelog and versioning so make sure you follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) guidelines when making commits. Do not manually update the [changelog](./CHANGELOG.md) and version in the [package.json](./package.json).
16 |
17 | 4. Open a pull request! All pull requests must be made to the `main` branch.
18 |
19 | Thanks to Next.js. This contribution guide is inspired by the [Next.js contribution guide.](https://github.com/vercel/next.js/blob/canary/contributing.md)
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 CardanoGoat-io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Interactive CLI to quickly set up an opinionated, full stack, Cardano NextJS project.
6 |
7 |
8 |
9 |
10 |
11 | [![PRs-Welcome][contribute-image]][contribute-url] [![NPM version][npm-image]][npm-url]
12 | [![Downloads][downloads-image]][npm-url]
13 |
14 |
15 |
16 |
17 |
18 | # Usage
19 |
20 | **npm**
21 |
22 | ```bash
23 | npx create-cardano-dapp@latest
24 | ```
25 |
26 | **yarn**
27 |
28 | ```bash
29 | yarn create cardano-dapp
30 | ```
31 |
32 | **pnpm**
33 |
34 | ```bash
35 | pnpm dlx create-cardano-dapp@latest
36 | ```
37 |
38 |
39 |
40 |
41 |
42 | ## Table of contents
43 |
44 | - About
45 | - T3 Axioms
46 | - Dev/Contributor Setup
47 | - Contributors
48 |
49 |
50 |
51 | # What is this? Some kinda template?
52 |
53 | Kind of. We love all of the technologies that create-cardano-dapp includes, but we do NOT believe every project needs all of them.
54 |
55 | We made `create-cardno-dapp` to do **one thing** - simplify the complex boilerplate around the core Cardano Stack tech without compromising the modularity of the pieces.
56 |
57 | This is **NOT** an all-inclusive template. We **expect you to bring your own libraries as well**.
58 |
59 | We are selective about the packages we have included. We don't add libraries that are as simple as an `npm install zustand`. _If you cut an issue asking us to add your preferred libraries, we will make fun of you._
60 |
61 | ## What is the Cardano Stack?
62 |
63 | The _"Cardano Stack"_ is a web development stack made by [Cardano Goat](https://twitter.com/Cardano_G_O_A_T), focused on **simplicity**, **modularity**, and **full-stack typesafety**.
64 |
65 |
75 |
76 | It consists of
77 |
78 | - [Next.js](https://nextjs.org)
79 | - [lucid-cardano](https://github.com/Berry-Pool/lucid)
80 | - [tRPC](https://trpc.io)
81 | - [Tailwind CSS](https://tailwindcss.com)
82 | - [TypeScript](https://typescriptlang.org)
83 | - [Prisma](https://prisma.io)
84 | - [NextAuth.js](https://next-auth.js.org)
85 |
86 |
87 |
88 |
89 |
90 | # T3 Axioms
91 |
92 | I'll be frank - this is an _opinionated project_. We share a handful of core beliefs around building, and we treat them as the basis for our decisions.
93 |
94 | ## 1. Solve Problems
95 |
96 | It's easy to fall in the trap of "adding everything" - we explicitly _don't_ want to do that. Everything added to `create-cardano-dapp` should solve a _specific_ problem that exists within the core technologies included.
97 |
98 | This means we **won't** add things like state libraries (zustand, redux), but we **will** add things like NextAuth.js and integrate it with Prisma and tRPC for you
99 |
100 | ## 2. Bleed Responsibly
101 |
102 | We love our bleeding edge tech. The amount of speed and, honestly, _fun_ that comes out of new shit is really cool. We think it's important to **bleed responsibly**, using riskier tech in the less risky parts.
103 |
104 | This means we **wouldn't** bet on risky new database tech (SQL is great!) - but we **happily** bet on tRPC (it's just functions, moving off it is trivial).
105 |
106 | ## 3. Typesafety Isn't Optional
107 |
108 | Two of the three T's are typesafe (Typescript, tRPC). We take typesafety seriously in these parts. Any decision that compromises the full-stack typesafe nature of `create-cardano-dapp` is a decision that should be made in a different project.
109 |
110 |
111 |
112 |
113 |
114 | # Dev/Contributor Setup
115 |
116 | Read the [Contributing guidelines](CONTRIBUTING.md)
117 |
118 | To install dependencies
119 |
120 | ```bash
121 | # Install pnpm
122 | npm install -g pnpm
123 | # Install dependencies
124 | pnpm install
125 | # Initial build
126 | pnpm run build
127 | # Start the package locally
128 | pnpm start
129 | ```
130 |
131 |
132 |
133 | # Contributors
134 |
135 | We 💖 contributors! Feel free to contribute to this project
136 |
137 |
138 |
139 |
140 |
141 | Made with [contrib.rocks](https://contrib.rocks).
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | ### Other Recommendations
150 |
151 | We recognize that Next.js and TypeScript don't solve every problem. While we encourage you to use the primitives they provide as often as you can, there are other tools that will help simplify and improve your developer experience.
152 |
153 | ### UI Essentials
154 |
155 | Tailwind CSS - for your styles
156 | I hated the idea of Tailwind. Then I tried it.
157 |
158 | It seems like Bootstrap. I promise it is not Bootstrap. It enables a "flow state" in UI dev I didn't know was achievable. (and yes we used it here)
159 |
160 | ### Data Management
161 |
162 | React Query - for your client
163 | This little library handles all your async React needs from data fetching to IO management. Hard to imagine doing React dev without it now. Might be able to replace your state management library too.
164 |
165 | Prisma.io - for your DB
166 | Prisma is to SQL what TypeScript is to JS. Never thought I'd love an ORM, but man, trust me on this one.
167 |
168 | tRPC - for defining and consuming APIs
169 | tRPC delivers on GraphQL's promise of seamless client development against a typesafe server without all of the boilerplate. It's a clever abuse of TypeScript that provides an incredible dev experience.
170 |
171 | ### Analytics
172 |
173 | Plausible - for user data
174 | Need analytics? Plausible is one of the quickest ways to get them. Super minimal. It even has a simple plugin for Next.js.
175 |
176 | ### Authentication
177 |
178 | NextAuth.js - for authentication
179 | Man this library makes auth easy. No ownership compromise, hooks up to your DB directly. So good that I ported two projects to Next to use it.
180 |
181 | Note: Does not support React Native directly. Yet. 😉
182 |
183 | ### Deployments, Infrastructure, Databases and CI
184 |
185 | Vercel - for hosting your website
186 | Vercel took the hell of web deployments and made it a set-and-forget GitHub integration. We've scaled to hundreds of thousands of users without issue. AWS-powered, just a way better interface :)
187 |
188 | PlanetScale - for databases without the worry
189 | PlanetScale is the best "serverless database platform" I've used by far. Insane scale, great developer experience, fantastic pricing. If you're using sql (and hopefully Prisma), this is hard to beat.
190 |
191 | Railway - for hosting your infra
192 | "Modern Heroku". Easiest way to get a real server up and running. If Vercel and PlanetScale aren't enough, Railway probably is. Point it at a GitHub repo and go.
193 |
194 | ### State Management
195 |
196 | Editor's Note: State management libraries can be great, but often aren't necessary. Start with React Query and built-in React state, reach for these when you need more
197 |
198 | Zustand - for never using Redux again
199 | The "modern, simple Redux" you didn't know you needed. Poimandres can always be trusted. I have built everything from video call apps to games to servers with this little library
200 |
201 | Jotai - for never using Context again
202 | For a more atomic approach, Jotai is hard to beat. Also by Poimandres , Jotai lets you define singletons that feel like global useState. Great option for stateful behaviors that don't need a state machine just yet
203 |
204 | [downloads-image]: https://img.shields.io/npm/dm/create-cardano-dapp?color=364fc7&logoColor=364fc7
205 | [npm-url]: https://www.npmjs.com/package/create-cardano-dapp
206 | [npm-image]: https://img.shields.io/npm/v/create-cardano-dapp?color=0b7285&logoColor=0b7285
207 | [contribute-url]: https://github.com/CardanoGoat-io/create-cardano-dapp/blob/main/CONTRIBUTING.md
208 | [contribute-image]: https://img.shields.io/badge/PRs-welcome-blue.svg
209 |
--------------------------------------------------------------------------------
/commitlint.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional"],
3 | rules: {
4 | "type-enum": [
5 | 2,
6 | "always",
7 | [
8 | "feat",
9 | "fix",
10 | "docs",
11 | "chore",
12 | "style",
13 | "refactor",
14 | "ci",
15 | "test",
16 | "perf",
17 | "revert",
18 | ],
19 | ],
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-cardano-dapp",
3 | "version": "0.1.6",
4 | "description": "Create Cardano web application that works with the Cardano distributed network.",
5 | "author": "Cardano Goat ",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/CardanoGoat-io/create-cardano-dapp.git"
10 | },
11 | "keywords": [
12 | "create-cardano-dapp",
13 | "init.tips",
14 | "next.js",
15 | "tailwind",
16 | "tRPC",
17 | "typescript"
18 | ],
19 | "type": "module",
20 | "exports": "./dist/index.js",
21 | "bin": {
22 | "create-cardano-dapp": "./dist/index.js"
23 | },
24 | "engines": {
25 | "node": ">=14.16"
26 | },
27 | "scripts": {
28 | "typecheck": "tsc",
29 | "build": "tsup src/index.ts --format esm --clean --sourcemap --minify --metafile",
30 | "dev": "tsup src/index.ts --format esm --watch --clean --onSuccess \"node dist/index.js\"",
31 | "start": "node dist/index.js",
32 | "lint": "eslint src/ --fix",
33 | "lint:check": "eslint src/",
34 | "format": "prettier --write \"**/*.{ts,tsx,md,mdx,json}\"",
35 | "format:check": "prettier --check \"**/*.{ts,tsx,md,mdx,json}\"",
36 | "prepare": "husky install",
37 | "release": "standard-version",
38 | "pub:beta": "npm run build && npm publish --tag beta",
39 | "pub:release": "npm run build && npm publish"
40 | },
41 | "dependencies": {
42 | "chalk": "5.0.1",
43 | "commander": "^9.3.0",
44 | "figlet": "^1.5.2",
45 | "fs-extra": "^10.1.0",
46 | "gradient-string": "^2.0.1",
47 | "inquirer": "^9.0.0",
48 | "ora": "6.1.1"
49 | },
50 | "devDependencies": {
51 | "@commitlint/cli": "^17.0.3",
52 | "@commitlint/config-conventional": "^17.0.3",
53 | "@types/figlet": "^1.5.4",
54 | "@types/fs-extra": "^9.0.13",
55 | "@types/gradient-string": "^1.1.2",
56 | "@types/inquirer": "^8.2.1",
57 | "@types/node": "^18.0.0",
58 | "@typescript-eslint/eslint-plugin": "^5.30.0",
59 | "@typescript-eslint/parser": "^5.30.0",
60 | "eslint": "^8.18.0",
61 | "eslint-config-prettier": "^8.5.0",
62 | "eslint-import-resolver-typescript": "^3.1.1",
63 | "eslint-plugin-import": "^2.26.0",
64 | "husky": "^8.0.1",
65 | "lint-staged": "^13.0.3",
66 | "prettier": "^2.7.1",
67 | "standard-version": "^9.5.0",
68 | "tsup": "^6.1.2",
69 | "type-fest": "^2.14.0",
70 | "typescript": "^4.7.4"
71 | },
72 | "lint-staged": {
73 | "src/**/*.{ts,tsx}": [
74 | "prettier --write",
75 | "eslint --fix"
76 | ],
77 | "*.{json,md,mdx}": [
78 | "prettier --write"
79 | ]
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/cli/index.ts:
--------------------------------------------------------------------------------
1 | import type { AvailablePackages } from "../installers/index.js";
2 | import chalk from "chalk";
3 | import { Command } from "commander";
4 | import inquirer from "inquirer";
5 | import { CREATE_CARDANO_DAPP, DEFAULT_APP_NAME } from "../consts.js";
6 | import { availablePackages } from "../installers/index.js";
7 | import { getVersion } from "../utils/getT3Version.js";
8 | import { getUserPkgManager } from "../utils/getUserPkgManager.js";
9 | import { logger } from "../utils/logger.js";
10 | import { validateAppName } from "../utils/validateAppName.js";
11 |
12 | interface CliFlags {
13 | noGit: boolean;
14 | noInstall: boolean;
15 | default: boolean;
16 | }
17 |
18 | interface CliResults {
19 | appName: string;
20 | packages: AvailablePackages[];
21 | flags: CliFlags;
22 | }
23 |
24 | const defaultOptions: CliResults = {
25 | appName: DEFAULT_APP_NAME,
26 | packages: ["nextAuth", "prisma", "tailwind", "trpc"],
27 | flags: {
28 | noGit: false,
29 | noInstall: false,
30 | default: false,
31 | },
32 | };
33 |
34 | export const runCli = async () => {
35 | const cliResults = defaultOptions;
36 |
37 | const program = new Command().name(CREATE_CARDANO_DAPP);
38 |
39 | // TODO: This doesn't return anything typesafe. Research other options?
40 | // Emulate from: https://github.com/Schniz/soundtype-commander
41 | program
42 | .description("A CLI for creating web applications with the t3 stack")
43 | .argument(
44 | "[dir]",
45 | "The name of the application, as well as the name of the directory to create",
46 | )
47 | .option(
48 | "--noGit",
49 | "Explicitly tell the CLI to not initialize a new git repo in the project",
50 | false,
51 | )
52 | .option(
53 | "--noInstall",
54 | "Explicitly tell the CLI to not run the package manager's install command",
55 | false,
56 | )
57 | .option(
58 | "-y, --default",
59 | "Bypass the CLI and use all default options to bootstrap a new cardano dapp",
60 | false,
61 | )
62 | .version(getVersion(), "-v, --version", "Display the version number")
63 | .addHelpText(
64 | "afterAll",
65 | `\n The Cardano stack was inspired by ${chalk
66 | .hex("#E8DCFF")
67 | .bold(
68 | "@Cardano_G_O_A_T",
69 | )} and has been used to build awesome fullstack applications like\n`,
70 | )
71 | .parse(process.argv);
72 |
73 | // FIXME: TEMPORARY WARNING WHEN USING NODE 18. SEE ISSUE #59 at https://github.com/t3-oss/create-t3-app
74 | if (process.versions.node.startsWith("18")) {
75 | logger.warn(` WARNING: You are using Node.js version 18. This is currently not compatible with Next-Auth.
76 | If you want to use Next-Auth, switch to a previous version of Node, e.g. 16 (LTS).
77 | If you have nvm installed, use 'nvm install --lts' to switch to the latest LTS version of Node.
78 | `);
79 |
80 | cliResults.packages = cliResults.packages.filter(
81 | (val) => val !== "nextAuth",
82 | );
83 | }
84 |
85 | // Needs to be seperated outside the if statement to correctly infer the type as string | undefined
86 | const cliProvidedName = program.args[0];
87 | if (cliProvidedName) {
88 | cliResults.appName = cliProvidedName;
89 | }
90 |
91 | cliResults.flags = program.opts();
92 |
93 | const pkgManager = getUserPkgManager();
94 |
95 | // Explained below why this is in a try/catch block
96 | try {
97 | if (!cliResults.flags.default) {
98 | if (!cliProvidedName) {
99 | const { appName } = await inquirer.prompt>({
100 | name: "appName",
101 | type: "input",
102 | message: "What will your project be called?",
103 | default: defaultOptions.appName,
104 | validate: validateAppName,
105 | transformer: (input: string) => {
106 | return input.trim();
107 | },
108 | });
109 | cliResults.appName = appName;
110 | }
111 |
112 | const { language } = await inquirer.prompt<{ language: string }>({
113 | name: "language",
114 | type: "list",
115 | message: "Your only choice is TypeScript?",
116 | choices: [
117 | { name: "TypeScript", value: "typescript", short: "TypeScript" },
118 | // { name: "JavaScript", value: "javascript", short: "TypeScript" }, // Both options should have 'TypeScript' as the short value to improve UX and reduce confusion
119 | ],
120 | default: "typescript",
121 | });
122 |
123 | if (language === "javascript") {
124 | logger.error("Wrong answer, using TypeScript instead...");
125 | } else {
126 | logger.success("Good choice! Using TypeScript!");
127 | }
128 |
129 | const { packages } = await inquirer.prompt>({
130 | name: "packages",
131 | type: "checkbox",
132 | message: "Which packages would you like to enable?",
133 | choices: availablePackages.map((pkgName) => ({
134 | name: pkgName,
135 | checked: false,
136 | // FIXME: TEMPORARY WARNING WHEN USING NODE 18. SEE ISSUE #59
137 | disabled:
138 | pkgName === "nextAuth" && process.versions.node.startsWith("18")
139 | ? "Node.js version 18 is currently not compatible with Next-Auth."
140 | : false,
141 | })),
142 | });
143 |
144 | cliResults.packages = packages;
145 |
146 | // Skip if noGit flag provided
147 | if (!cliResults.flags.noGit) {
148 | const { git } = await inquirer.prompt<{ git: boolean }>({
149 | name: "git",
150 | type: "confirm",
151 | message: "Initialize a new git repository?",
152 | default: true,
153 | });
154 | if (git) {
155 | logger.success("Nice one! Initializing repository!");
156 | } else {
157 | cliResults.flags.noGit = true;
158 | logger.info("Sounds good! You can come back and run git init later.");
159 | }
160 | }
161 |
162 | if (!cliResults.flags.noInstall) {
163 | const { runInstall } = await inquirer.prompt<{ runInstall: boolean }>({
164 | name: "runInstall",
165 | type: "confirm",
166 | message: `Would you like us to run ${pkgManager} install?`,
167 | default: true,
168 | });
169 |
170 | if (runInstall) {
171 | logger.success("Alright. We'll install the dependencies for you!");
172 | } else {
173 | cliResults.flags.noInstall = true;
174 | logger.info(
175 | `No worries. You can run '${pkgManager} install' later to install the dependencies.`,
176 | );
177 | }
178 | }
179 | }
180 | } catch (err) {
181 | // If the user is not calling create-cardano-dapp from an interactive terminal, inquirer will throw an error with isTTYError = true
182 | // If this happens, we catch the error, tell the user what has happened, and then continue to run the program with a default t3 app
183 | // eslint-disable-next-line -- Otherwise we have to do some fancy namespace extension logic on the Error type which feels overkill for one line
184 | if (err instanceof Error && (err as any).isTTYError) {
185 | logger.warn(
186 | `${CREATE_CARDANO_DAPP} needs an interactive terminal to provide options`,
187 | );
188 | logger.info(
189 | `Bootstrapping a default cardano dapp in ./${cliResults.appName}`,
190 | );
191 | } else {
192 | throw err;
193 | }
194 | }
195 |
196 | return cliResults;
197 | };
198 |
--------------------------------------------------------------------------------
/src/consts.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { fileURLToPath } from "url";
3 |
4 | // With the move to TSUP as a build tool, this keeps path routes in other files (installers, loaders, etc) in check more easily.
5 | // Path is in relation to a single index.js file inside ./dist
6 | const __filename = fileURLToPath(import.meta.url);
7 | const distPath = path.dirname(__filename);
8 | export const PKG_ROOT = path.join(distPath, "../");
9 |
10 | //export const PKG_ROOT = path.dirname(require.main.filename);
11 |
12 | export const TITLE_TEXT = `CREATE ₳ DAPP`;
13 | export const DEFAULT_APP_NAME = "my-cardano-dapp";
14 | export const CREATE_CARDANO_DAPP = "create-cardano-dapp";
15 |
--------------------------------------------------------------------------------
/src/helpers/createProject.ts:
--------------------------------------------------------------------------------
1 | import type { PkgInstallerMap } from "../installers/index.js";
2 | import path from "path";
3 | import { getUserPkgManager } from "../utils/getUserPkgManager.js";
4 | import { installPackages } from "./installPackages.js";
5 | import { scaffoldProject } from "./scaffoldProject.js";
6 | import { selectAppFile, selectIndexFile } from "./selectBoilerplate.js";
7 |
8 | interface CreateProjectOptions {
9 | projectName: string;
10 | packages: PkgInstallerMap;
11 | noInstall: boolean;
12 | }
13 |
14 | export const createProject = async ({
15 | projectName,
16 | packages,
17 | noInstall,
18 | }: CreateProjectOptions) => {
19 | const pkgManager = getUserPkgManager();
20 | const projectDir = path.resolve(process.cwd(), projectName);
21 |
22 | // Bootstraps the base Next.js application
23 | await scaffoldProject({ projectName, projectDir, pkgManager, noInstall });
24 |
25 | // Install the selected packages
26 | await installPackages({ projectDir, pkgManager, packages, noInstall });
27 |
28 | // TODO: Look into using handlebars or other templating engine to scaffold without needing to maintain multiple copies of the same file
29 | await selectAppFile({ projectDir, packages });
30 | await selectIndexFile({ projectDir, packages });
31 |
32 | return projectDir;
33 | };
34 |
--------------------------------------------------------------------------------
/src/helpers/initGit.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import chalk from "chalk";
3 | import fs from "fs-extra";
4 | import ora from "ora";
5 | import { execa } from "../utils/execAsync.js";
6 | import { logger } from "../utils/logger.js";
7 |
8 | // This initializes the Git-repository for the project
9 | export const initializeGit = async (projectDir: string) => {
10 | logger.info("Initializing Git...");
11 | const spinner = ora("Creating a new git repo...\n").start();
12 | try {
13 | let initCmd = "git init --initial-branch=main";
14 |
15 | // --initial-branch flag was added in git v2.28.0
16 | const { stdout: gitVersionOutput } = await execa("git --version"); // git version 2.32.0 ...
17 | const gitVersionTag = gitVersionOutput.split(" ")[2];
18 | const major = gitVersionTag?.split(".")[0];
19 | const minor = gitVersionTag?.split(".")[1];
20 | if (Number(major) < 2 || Number(minor) < 28) {
21 | initCmd = "git init && git branch -m main";
22 | }
23 |
24 | await execa(initCmd, { cwd: projectDir });
25 | spinner.succeed(
26 | `${chalk.green("Successfully initialized")} ${chalk.green.bold("git")}\n`,
27 | );
28 | } catch (error) {
29 | spinner.fail(
30 | `${chalk.bold.red(
31 | "Failed:",
32 | )} could not initialize git. Update git to the latest version!\n`,
33 | );
34 | }
35 |
36 | await fs.rename(
37 | path.join(projectDir, "_gitignore"),
38 | path.join(projectDir, ".gitignore"),
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/helpers/installPackages.ts:
--------------------------------------------------------------------------------
1 | import type { InstallerOptions, PkgInstallerMap } from "../installers/index.js";
2 | import chalk from "chalk";
3 | import ora from "ora";
4 | import { logger } from "../utils/logger.js";
5 |
6 | type InstallPackagesOptions = {
7 | packages: PkgInstallerMap;
8 | } & InstallerOptions;
9 | // This runs the installer for all the packages that the user has selected
10 | export const installPackages = async ({
11 | projectDir,
12 | pkgManager,
13 | packages,
14 | noInstall,
15 | }: InstallPackagesOptions) => {
16 | logger.info(`${noInstall ? "Adding" : "Installing"} packages...`);
17 |
18 | for (const [name, pkgOpts] of Object.entries(packages)) {
19 | if (pkgOpts.inUse) {
20 | const spinner = ora(
21 | `${noInstall ? "Adding" : "Installing"} ${name}...`,
22 | ).start();
23 | await pkgOpts.installer({ projectDir, pkgManager, packages, noInstall });
24 | spinner.succeed(
25 | chalk.green(
26 | `Successfully ${noInstall ? "added" : "installed"} ${chalk.green.bold(
27 | name,
28 | )}`,
29 | ),
30 | );
31 | }
32 | }
33 | logger.info("");
34 | };
35 |
--------------------------------------------------------------------------------
/src/helpers/logNextSteps.ts:
--------------------------------------------------------------------------------
1 | import type { InstallerOptions } from "../installers/index.js";
2 | import { DEFAULT_APP_NAME } from "../consts.js";
3 | import { getUserPkgManager } from "../utils/getUserPkgManager.js";
4 | import { logger } from "../utils/logger.js";
5 |
6 | // This logs the next steps that the user should take in order to advance the project
7 | export const logNextSteps = ({
8 | projectName = DEFAULT_APP_NAME,
9 | packages,
10 | noInstall,
11 | }: Pick) => {
12 | const pkgManager = getUserPkgManager();
13 |
14 | logger.info("Next steps:");
15 | logger.info(` cd ${projectName}`);
16 | if (noInstall) {
17 | logger.info(` ${pkgManager} install`);
18 | }
19 |
20 | if (packages?.prisma.inUse) {
21 | logger.info(
22 | ` ${pkgManager === "npm" ? "npx" : pkgManager} prisma db push`,
23 | );
24 | }
25 |
26 | logger.info(` ${pkgManager === "npm" ? "npm run" : pkgManager} dev`);
27 | };
28 |
--------------------------------------------------------------------------------
/src/helpers/scaffoldProject.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import chalk from "chalk";
3 | import fs from "fs-extra";
4 | import inquirer from "inquirer";
5 | import ora from "ora";
6 | import { PKG_ROOT } from "../consts.js";
7 | import { InstallerOptions } from "../installers/index.js";
8 | import { execa } from "../utils/execAsync.js";
9 | import { logger } from "../utils/logger.js";
10 |
11 | // This bootstraps the base Next.js application
12 | export const scaffoldProject = async ({
13 | projectName,
14 | projectDir,
15 | pkgManager,
16 | noInstall,
17 | }: InstallerOptions) => {
18 | const srcDir = path.join(PKG_ROOT, "template/base");
19 |
20 | if (!noInstall) {
21 | logger.info(`\nUsing: ${chalk.cyan.bold(pkgManager)}\n`);
22 | } else {
23 | logger.info("");
24 | }
25 |
26 | const spinner = ora(`Scaffolding in: ${projectDir}...\n`).start();
27 |
28 | if (fs.existsSync(projectDir)) {
29 | if (fs.readdirSync(projectDir).length === 0) {
30 | spinner.info(
31 | `${chalk.cyan.bold(projectName)} exists but is empty, continuing...\n`,
32 | );
33 | } else {
34 | spinner.stopAndPersist();
35 | const { overwriteDir } = await inquirer.prompt<{ overwriteDir: boolean }>(
36 | {
37 | name: "overwriteDir",
38 | type: "confirm",
39 | message: `${chalk.redBright.bold("Warning:")} ${chalk.cyan.bold(
40 | projectName,
41 | )} already exists and isn't empty. Do you want to overwrite it?`,
42 | default: false,
43 | },
44 | );
45 | if (!overwriteDir) {
46 | spinner.fail("Aborting installation...");
47 | process.exit(0);
48 | } else {
49 | spinner.info(
50 | `Emptying ${chalk.cyan.bold(projectName)} and creating t3 app..\n`,
51 | );
52 | fs.emptyDirSync(projectDir);
53 | }
54 | }
55 | }
56 |
57 | spinner.start();
58 |
59 | await fs.copy(srcDir, projectDir);
60 |
61 | if (!noInstall) {
62 | await execa(`${pkgManager} install`, { cwd: projectDir });
63 | }
64 | spinner.succeed(`${chalk.cyan.bold(projectName)} scaffolded successfully!\n`);
65 | };
66 |
--------------------------------------------------------------------------------
/src/helpers/selectBoilerplate.ts:
--------------------------------------------------------------------------------
1 | import type { InstallerOptions } from "../installers/index.js";
2 | import path from "path";
3 | import fs from "fs-extra";
4 | import { PKG_ROOT } from "../consts.js";
5 |
6 | type SelectBoilerplateProps = Required<
7 | Pick
8 | >;
9 | // This generates the _app.tsx file that is used to render the app
10 | export const selectAppFile = async ({
11 | projectDir,
12 | packages,
13 | }: SelectBoilerplateProps) => {
14 | const appFileDir = path.join(PKG_ROOT, "template/page-studs/_app");
15 |
16 | const usingTrpc = packages.trpc.inUse;
17 | const usingNextAuth = packages.nextAuth.inUse;
18 |
19 | let appFile = "";
20 | if (usingNextAuth && usingTrpc) {
21 | appFile = "with-auth-trpc.tsx";
22 | } else if (usingNextAuth && !usingTrpc) {
23 | appFile = "with-auth.tsx";
24 | } else if (!usingNextAuth && usingTrpc) {
25 | appFile = "with-trpc.tsx";
26 | }
27 |
28 | if (appFile !== "") {
29 | const appSrc = path.join(appFileDir, appFile);
30 | const appDest = path.join(projectDir, "src/pages/_app.tsx");
31 | await fs.copy(appSrc, appDest);
32 | }
33 | };
34 |
35 | // This selects the proper index.tsx to be used that showcases the chosen tech
36 | export const selectIndexFile = async ({
37 | projectDir,
38 | packages,
39 | }: SelectBoilerplateProps) => {
40 | const indexFileDir = path.join(PKG_ROOT, "template/page-studs/index");
41 |
42 | const usingTrpc = packages.trpc.inUse;
43 | const usingTw = packages.tailwind.inUse;
44 | const usingAuth = packages.nextAuth.inUse;
45 | const usingPrisma = packages.prisma.inUse;
46 |
47 | let indexFile = "";
48 | // FIXME: auth showcase doesn't work with prisma since it requires more setup
49 | if (usingTrpc && usingTw && usingAuth && !usingPrisma) {
50 | indexFile = "with-auth-trpc-tw.tsx";
51 | } else if (usingTrpc && !usingTw && usingAuth && !usingPrisma) {
52 | indexFile = "with-auth-trpc.tsx";
53 | } else if (usingTrpc && usingTw) {
54 | indexFile = "with-trpc-tw.tsx";
55 | } else if (usingTrpc && !usingTw) {
56 | indexFile = "with-trpc.tsx";
57 | } else if (!usingTrpc && usingTw) {
58 | indexFile = "with-tw.tsx";
59 | }
60 |
61 | if (indexFile !== "") {
62 | const indexSrc = path.join(indexFileDir, indexFile);
63 | const indexDest = path.join(projectDir, "src/pages/index.tsx");
64 | await fs.copy(indexSrc, indexDest);
65 | }
66 | };
67 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import type { PackageJson } from "type-fest";
3 | import path from "path";
4 | import fs from "fs-extra";
5 | import { runCli } from "./cli/index.js";
6 | import { createProject } from "./helpers/createProject.js";
7 | import { initializeGit } from "./helpers/initGit.js";
8 | import { logNextSteps } from "./helpers/logNextSteps.js";
9 | import { buildPkgInstallerMap } from "./installers/index.js";
10 | import { logger } from "./utils/logger.js";
11 | import { parseNameAndPath } from "./utils/parseNameAndPath.js";
12 | import { renderTitle } from "./utils/renderTitle.js";
13 |
14 | const main = async () => {
15 | renderTitle();
16 |
17 | const {
18 | appName,
19 | packages,
20 | flags: { noGit, noInstall },
21 | } = await runCli();
22 |
23 | const usePackages = buildPkgInstallerMap(packages);
24 |
25 | // e.g. dir/@mono/app returns ["@mono/app", "dir/app"]
26 | const [scopedAppName, appDir] = parseNameAndPath(appName);
27 |
28 | const projectDir = await createProject({
29 | projectName: appDir,
30 | packages: usePackages,
31 | noInstall,
32 | });
33 |
34 | if (!noGit) {
35 | await initializeGit(projectDir);
36 | }
37 |
38 | logNextSteps({ projectName: appDir, packages: usePackages, noInstall });
39 | const pkgJson = (await fs.readJSON(
40 | path.join(projectDir, "package.json"),
41 | )) as PackageJson;
42 | pkgJson.name = scopedAppName;
43 | await fs.writeJSON(path.join(projectDir, "package.json"), pkgJson, {
44 | spaces: 2,
45 | });
46 |
47 | process.exit(0);
48 | };
49 |
50 | main().catch((err) => {
51 | logger.error("Aborting installation...");
52 | if (err instanceof Error) {
53 | logger.error(err);
54 | } else {
55 | logger.error(
56 | "An unknown error has occurred. Please open an issue on github with the below:",
57 | );
58 | console.log(err);
59 | }
60 | process.exit(1);
61 | });
62 |
--------------------------------------------------------------------------------
/src/installers/index.ts:
--------------------------------------------------------------------------------
1 | import type { PackageManager } from "../utils/getUserPkgManager.js";
2 | import { nextAuthInstaller } from "./next-auth.js";
3 | import { prismaInstaller } from "./prisma.js";
4 | import { tailwindInstaller } from "./tailwind.js";
5 | import { trpcInstaller } from "./trpc.js";
6 |
7 | // Turning this into a const allows the list to be iterated over for programatically creating prompt options
8 | // Should increase extensability in the future
9 | export const availablePackages = [
10 | "nextAuth",
11 | "prisma",
12 | "tailwind",
13 | "trpc",
14 | ] as const;
15 |
16 | export type AvailablePackages = typeof availablePackages[number];
17 |
18 | export interface InstallerOptions {
19 | projectDir: string;
20 | pkgManager: PackageManager;
21 | noInstall: boolean;
22 | packages?: PkgInstallerMap;
23 | projectName?: string;
24 | }
25 |
26 | export type Installer = (opts: InstallerOptions) => Promise;
27 |
28 | export type PkgInstallerMap = {
29 | [pkg in AvailablePackages]: {
30 | inUse: boolean;
31 | installer: Installer;
32 | };
33 | };
34 |
35 | export const buildPkgInstallerMap = (
36 | packages: AvailablePackages[],
37 | ): PkgInstallerMap => ({
38 | nextAuth: {
39 | inUse: packages.includes("nextAuth"),
40 | installer: nextAuthInstaller,
41 | },
42 | prisma: {
43 | inUse: packages.includes("prisma"),
44 | installer: prismaInstaller,
45 | },
46 | tailwind: {
47 | inUse: packages.includes("tailwind"),
48 | installer: tailwindInstaller,
49 | },
50 | trpc: {
51 | inUse: packages.includes("trpc"),
52 | installer: trpcInstaller,
53 | },
54 | });
55 |
--------------------------------------------------------------------------------
/src/installers/next-auth.ts:
--------------------------------------------------------------------------------
1 | import type { Installer } from "./index.js";
2 | import path from "path";
3 | import fs from "fs-extra";
4 | import { PKG_ROOT } from "../consts.js";
5 | import { runPkgManagerInstall } from "../utils/runPkgManagerInstall.js";
6 |
7 | export const nextAuthInstaller: Installer = async ({
8 | pkgManager,
9 | projectDir,
10 | packages,
11 | noInstall,
12 | }) => {
13 | await runPkgManagerInstall({
14 | pkgManager,
15 | projectDir,
16 | packages: [
17 | "next-auth",
18 | packages?.prisma.inUse ? "@next-auth/prisma-adapter" : "",
19 | ],
20 | devMode: false,
21 | noInstallMode: noInstall,
22 | });
23 |
24 | const nextAuthAssetDir = path.join(PKG_ROOT, "template/addons/next-auth");
25 |
26 | const apiHandlerSrc = path.join(
27 | nextAuthAssetDir,
28 | packages?.prisma.inUse ? "api-handler-prisma.ts" : "api-handler.ts",
29 | );
30 | const apiHandlerDest = path.join(
31 | projectDir,
32 | "src/pages/api/auth/[...nextauth].ts",
33 | );
34 |
35 | const restrictedApiSrc = path.join(nextAuthAssetDir, "restricted.ts");
36 | const restrictedApiDest = path.join(
37 | projectDir,
38 | "src/pages/api/restricted.ts",
39 | );
40 |
41 | await Promise.all([
42 | fs.copy(apiHandlerSrc, apiHandlerDest),
43 | fs.copy(restrictedApiSrc, restrictedApiDest),
44 | ]);
45 | };
46 |
--------------------------------------------------------------------------------
/src/installers/prisma.ts:
--------------------------------------------------------------------------------
1 | import type { Installer } from "./index.js";
2 | import type { PackageJson } from "type-fest";
3 | import path from "path";
4 | import fs from "fs-extra";
5 | import { PKG_ROOT } from "../consts.js";
6 | import { execa } from "../utils/execAsync.js";
7 | import { runPkgManagerInstall } from "../utils/runPkgManagerInstall.js";
8 |
9 | export const prismaInstaller: Installer = async ({
10 | projectDir,
11 | pkgManager,
12 | packages,
13 | noInstall,
14 | }) => {
15 | await runPkgManagerInstall({
16 | pkgManager,
17 | projectDir,
18 | packages: ["prisma"],
19 | devMode: true,
20 | noInstallMode: noInstall,
21 | });
22 | await runPkgManagerInstall({
23 | pkgManager,
24 | projectDir,
25 | packages: ["@prisma/client"],
26 | devMode: false,
27 | noInstallMode: noInstall,
28 | });
29 |
30 | const prismaAssetDir = path.join(PKG_ROOT, "template/addons/prisma");
31 |
32 | const schemaSrc = path.join(
33 | prismaAssetDir,
34 | packages?.nextAuth.inUse ? "auth-schema.prisma" : "schema.prisma",
35 | );
36 | const schemaDest = path.join(projectDir, "prisma/schema.prisma");
37 |
38 | const clientSrc = path.join(prismaAssetDir, "client.ts");
39 | const clientDest = path.join(projectDir, "src/server/db/client.ts");
40 |
41 | const sampleApiRouteSrc = path.join(prismaAssetDir, "sample-api.ts");
42 | const sampleApiRouteDest = path.join(projectDir, "src/pages/api/examples.ts");
43 |
44 | // add postinstall script to package.json
45 | const packageJsonPath = path.join(projectDir, "package.json");
46 |
47 | const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson;
48 | packageJsonContent.scripts!.postinstall = "prisma generate"; //eslint-disable-line @typescript-eslint/no-non-null-assertion
49 |
50 | await Promise.all([
51 | fs.copy(schemaSrc, schemaDest),
52 | fs.copy(clientSrc, clientDest),
53 | fs.copy(sampleApiRouteSrc, sampleApiRouteDest),
54 | fs.writeJSON(packageJsonPath, packageJsonContent, {
55 | spaces: 2,
56 | }),
57 | ]);
58 |
59 | // only generate client if we have installed the dependencies
60 | if (!noInstall) {
61 | const generateClientCmd =
62 | pkgManager === "npm"
63 | ? "npx prisma generate"
64 | : `${pkgManager} prisma generate`;
65 | await execa(generateClientCmd, { cwd: projectDir });
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/src/installers/tailwind.ts:
--------------------------------------------------------------------------------
1 | import type { Installer } from "./index.js";
2 | import path from "path";
3 | import fs from "fs-extra";
4 | import { PKG_ROOT } from "../consts.js";
5 | import { runPkgManagerInstall } from "../utils/runPkgManagerInstall.js";
6 |
7 | export const tailwindInstaller: Installer = async ({
8 | projectDir,
9 | pkgManager,
10 | noInstall,
11 | }) => {
12 | await runPkgManagerInstall({
13 | pkgManager,
14 | projectDir,
15 | packages: ["tailwindcss", "postcss", "autoprefixer"],
16 | devMode: true,
17 | noInstallMode: noInstall,
18 | });
19 |
20 | const twAssetDir = path.join(PKG_ROOT, "template/addons/tailwind");
21 |
22 | const twCfgSrc = path.join(twAssetDir, "tailwind.config.js");
23 | const twCfgDest = path.join(projectDir, "tailwind.config.js");
24 |
25 | const postcssCfgSrc = path.join(twAssetDir, "postcss.config.js");
26 | const postcssCfgDest = path.join(projectDir, "postcss.config.js");
27 |
28 | const cssSrc = path.join(twAssetDir, "globals.css");
29 | const cssDest = path.join(projectDir, "src/styles/globals.css");
30 |
31 | await Promise.all([
32 | fs.copy(twCfgSrc, twCfgDest),
33 | fs.copy(postcssCfgSrc, postcssCfgDest),
34 | fs.copy(cssSrc, cssDest),
35 | ]);
36 | };
37 |
--------------------------------------------------------------------------------
/src/installers/trpc.ts:
--------------------------------------------------------------------------------
1 | import type { Installer } from "./index.js";
2 | import path from "path";
3 | import fs from "fs-extra";
4 | import { PKG_ROOT } from "../consts.js";
5 | import { runPkgManagerInstall } from "../utils/runPkgManagerInstall.js";
6 |
7 | export const trpcInstaller: Installer = async ({
8 | projectDir,
9 | pkgManager,
10 | packages,
11 | noInstall,
12 | }) => {
13 | await runPkgManagerInstall({
14 | pkgManager,
15 | projectDir,
16 | packages: [
17 | "react-query",
18 | "superjson",
19 | "@trpc/server",
20 | "@trpc/client",
21 | "@trpc/next",
22 | "@trpc/react",
23 | "zod",
24 | ],
25 | devMode: false,
26 | noInstallMode: noInstall,
27 | });
28 | const usingAuth = packages?.nextAuth.inUse;
29 | const usingPrisma = packages?.prisma.inUse;
30 |
31 | const trpcAssetDir = path.join(PKG_ROOT, "template/addons/trpc");
32 |
33 | const apiHandlerSrc = path.join(trpcAssetDir, "api-handler.ts");
34 | const apiHandlerDest = path.join(projectDir, "src/pages/api/trpc/[trpc].ts");
35 |
36 | const utilsSrc = path.join(trpcAssetDir, "utils.ts");
37 | const utilsDest = path.join(projectDir, "src/utils/trpc.ts");
38 |
39 | const contextFile =
40 | usingAuth && usingPrisma
41 | ? "auth-prisma-context.ts"
42 | : usingAuth && !usingPrisma
43 | ? "auth-context.ts"
44 | : !usingAuth && usingPrisma
45 | ? "prisma-context.ts"
46 | : "base-context.ts";
47 | const contextSrc = path.join(trpcAssetDir, contextFile);
48 | const contextDest = path.join(projectDir, "src/server/router/context.ts");
49 |
50 | if (usingAuth) {
51 | const authRouterSrc = path.join(trpcAssetDir, "auth-router.ts");
52 | const authRouterDest = path.join(projectDir, "src/server/router/auth.ts");
53 | await fs.copy(authRouterSrc, authRouterDest);
54 | }
55 |
56 | const indexRouterFile = usingAuth
57 | ? "auth-index-router.ts"
58 | : "index-router.ts";
59 | const indexRouterSrc = path.join(trpcAssetDir, indexRouterFile);
60 | const indexRouterDest = path.join(projectDir, "src/server/router/index.ts");
61 |
62 | const exampleRouterFile = usingPrisma
63 | ? "example-prisma-router.ts"
64 | : "example-router.ts";
65 | const exampleRouterSrc = path.join(trpcAssetDir, exampleRouterFile);
66 | const exampleRouterDest = path.join(
67 | projectDir,
68 | "src/server/router/example.ts",
69 | );
70 |
71 | await Promise.all([
72 | fs.copy(apiHandlerSrc, apiHandlerDest),
73 | fs.copy(utilsSrc, utilsDest),
74 | fs.copy(contextSrc, contextDest),
75 | fs.copy(indexRouterSrc, indexRouterDest),
76 | fs.copy(exampleRouterSrc, exampleRouterDest),
77 | ]);
78 | };
79 |
--------------------------------------------------------------------------------
/src/utils/execAsync.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "child_process";
2 | import { promisify } from "util";
3 |
4 | export const execa = promisify(exec);
5 |
--------------------------------------------------------------------------------
/src/utils/getT3Version.ts:
--------------------------------------------------------------------------------
1 | import type { PackageJson } from "type-fest";
2 | import path from "path";
3 | import fs from "fs-extra";
4 | import { PKG_ROOT } from "../consts.js";
5 |
6 | export const getVersion = () => {
7 | const packageJsonPath = path.join(PKG_ROOT, "package.json");
8 |
9 | const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson;
10 |
11 | return packageJsonContent.version ?? "1.0.0";
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/getUserPkgManager.ts:
--------------------------------------------------------------------------------
1 | export type PackageManager = "npm" | "pnpm" | "yarn";
2 |
3 | export const getUserPkgManager: () => PackageManager = () => {
4 | // This environment variable is set by npm and yarn but pnpm seems less consistent
5 | const userAgent = process.env.npm_config_user_agent;
6 |
7 | if (userAgent) {
8 | if (userAgent.startsWith("yarn")) {
9 | return "yarn";
10 | } else if (userAgent.startsWith("pnpm")) {
11 | return "pnpm";
12 | } else {
13 | return "npm";
14 | }
15 | } else {
16 | // If no user agent is set, assume npm
17 | return "npm";
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 |
3 | export const logger = {
4 | error(...args: unknown[]) {
5 | console.log(chalk.red(...args));
6 | },
7 | warn(...args: unknown[]) {
8 | console.log(chalk.yellow(...args));
9 | },
10 | info(...args: unknown[]) {
11 | console.log(chalk.cyan(...args));
12 | },
13 | success(...args: unknown[]) {
14 | console.log(chalk.green(...args));
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/parseNameAndPath.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Parses the appName and its path from the user input.
3 | * Returns an array of [appName, path] where appName is the name put in the package.json and
4 | * path is the path to the directory where the app will be created.
5 | * Handles the case where the input includes a scoped package name
6 | * in which case that is being parsed as the name, but not included as the path
7 | * e.g. dir/@mono/app => ["@mono/app", "dir/app"]
8 | * e.g. dir/app => ["app", "dir/app"]
9 | **/
10 | export const parseNameAndPath = (input: string) => {
11 | const paths = input.split("/");
12 |
13 | let appName = paths[paths.length - 1];
14 |
15 | // If the first part is a @, it's a scoped package
16 | const indexOfDelimiter = paths.findIndex((p) => p.startsWith("@"));
17 | if (paths.findIndex((p) => p.startsWith("@")) !== -1) {
18 | appName = paths.slice(indexOfDelimiter).join("/");
19 | }
20 |
21 | const path = paths.filter((p) => !p.startsWith("@")).join("/");
22 |
23 | return [appName, path] as const;
24 | };
25 |
--------------------------------------------------------------------------------
/src/utils/renderTitle.ts:
--------------------------------------------------------------------------------
1 | import figlet from "figlet";
2 | import gradient from "gradient-string";
3 | import { TITLE_TEXT } from "../consts.js";
4 |
5 | // colors brought in from vscode poimandres theme
6 | const poimandresTheme = {
7 | blue: "#add7ff",
8 | cyan: "#89ddff",
9 | green: "#5de4c7",
10 | magenta: "#fae4fc",
11 | red: "#d0679d",
12 | yellow: "#fffac2",
13 | };
14 |
15 | export const renderTitle = () => {
16 | const text = figlet.textSync(TITLE_TEXT, { font: "Small" });
17 | const t3Gradient = gradient(Object.values(poimandresTheme));
18 | console.log("\n", t3Gradient.multiline(text));
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/runPkgManagerInstall.ts:
--------------------------------------------------------------------------------
1 | import type { PackageManager } from "./getUserPkgManager.js";
2 | import path from "path";
3 | import fs from "fs-extra";
4 | import { type PackageJson } from "type-fest";
5 | import { execa } from "./execAsync.js";
6 | import { logger } from "./logger.js";
7 |
8 | export const runPkgManagerInstall = async (opts: {
9 | pkgManager: PackageManager;
10 | devMode: boolean;
11 | projectDir: string;
12 | packages: string[];
13 | noInstallMode: boolean;
14 | }) => {
15 | const { pkgManager, devMode, projectDir, packages, noInstallMode } = opts;
16 |
17 | if (noInstallMode) {
18 | const pkgJson = (await fs.readJSON(
19 | path.join(projectDir, "package.json"),
20 | )) as PackageJson;
21 |
22 | for (const pkg of packages) {
23 | if (pkg === "") {
24 | // sometimes empty string is passed as a package when using ternaries so escaping that to prevent it pulling node's version
25 | continue;
26 | }
27 | const { stdout: latestVersion } = await execa(`npm show ${pkg} version`);
28 | if (!latestVersion) {
29 | logger.warn("WARN: Failed to resolve latest version of package:", pkg);
30 | continue;
31 | }
32 |
33 | // Note: We know that pkgJson.[dev]Dependencies exists in the base Next.js template so we don't need to validate it
34 | if (devMode) {
35 | pkgJson.devDependencies![pkg] = `^${latestVersion.trim()}`; //eslint-disable-line @typescript-eslint/no-non-null-assertion
36 | } else {
37 | pkgJson.dependencies![pkg] = `^${latestVersion.trim()}`; //eslint-disable-line @typescript-eslint/no-non-null-assertion
38 | }
39 | }
40 |
41 | await fs.writeJSON(path.join(projectDir, "package.json"), pkgJson, {
42 | spaces: 2,
43 | });
44 | return;
45 | }
46 |
47 | const installCmd =
48 | pkgManager === "yarn" ? `${pkgManager} add` : `${pkgManager} install`;
49 | const flag = devMode ? "-D" : "";
50 | const fullCmd = `${installCmd} ${flag} ${packages.join(" ")}`;
51 | await execa(fullCmd, { cwd: projectDir });
52 | };
53 |
--------------------------------------------------------------------------------
/src/utils/validateAppName.ts:
--------------------------------------------------------------------------------
1 | const validationRegExp =
2 | /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
3 |
4 | //Validate a string against allowed package.json names
5 | export const validateAppName = (input: string) => {
6 | const paths = input.split("/");
7 |
8 | // If the first part is a @, it's a scoped package
9 | const indexOfDelimiter = paths.findIndex((p) => p.startsWith("@"));
10 |
11 | let appName = paths[paths.length - 1];
12 | if (paths.findIndex((p) => p.startsWith("@")) !== -1) {
13 | appName = paths.slice(indexOfDelimiter).join("/");
14 | }
15 |
16 | if (validationRegExp.test(appName ?? "")) {
17 | return true;
18 | } else {
19 | return "App name must be lowercase, alphanumeric, and only use -, _, and @";
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/template/addons/next-auth/api-handler-prisma.ts:
--------------------------------------------------------------------------------
1 | import NextAuth, { type NextAuthOptions } from "next-auth";
2 | import GithubProvider from "next-auth/providers/github";
3 | import CredentialsProvider from "next-auth/providers/credentials";
4 |
5 | // Prisma adapter for NextAuth, optional and can be removed
6 | import { PrismaAdapter } from "@next-auth/prisma-adapter";
7 | import { prisma } from "../../../server/db/client";
8 |
9 | export const authOptions: NextAuthOptions = {
10 | // Configure one or more authentication providers
11 | adapter: PrismaAdapter(prisma),
12 | providers: [
13 | GithubProvider({
14 | clientId: process.env.GITHUB_ID,
15 | clientSecret: process.env.GITHUB_SECRET,
16 | }),
17 | // ...add more providers here
18 | CredentialsProvider({
19 | name: "Credentials",
20 | credentials: {
21 | name: {
22 | label: "Name",
23 | type: "text",
24 | placeholder: "Enter your name",
25 | },
26 | },
27 | async authorize(credentials, _req) {
28 | const user = { id: 1, name: credentials?.name ?? "J Smith" };
29 | return user;
30 | },
31 | }),
32 | ],
33 | };
34 |
35 | export default NextAuth(authOptions);
36 |
--------------------------------------------------------------------------------
/template/addons/next-auth/api-handler.ts:
--------------------------------------------------------------------------------
1 | import NextAuth, { type NextAuthOptions } from "next-auth";
2 | import GithubProvider from "next-auth/providers/github";
3 | import CredentialsProvider from "next-auth/providers/credentials";
4 |
5 | export const authOptions: NextAuthOptions = {
6 | // Configure one or more authentication providers
7 | providers: [
8 | GithubProvider({
9 | clientId: process.env.GITHUB_ID,
10 | clientSecret: process.env.GITHUB_SECRET,
11 | }),
12 | // ...add more providers here
13 | CredentialsProvider({
14 | name: "Credentials",
15 | credentials: {
16 | name: {
17 | label: "Name",
18 | type: "text",
19 | placeholder: "Enter your name",
20 | },
21 | },
22 | async authorize(credentials, _req) {
23 | const user = { id: 1, name: credentials?.name ?? "J Smith" };
24 | return user;
25 | },
26 | }),
27 | ],
28 | };
29 |
30 | export default NextAuth(authOptions);
31 |
--------------------------------------------------------------------------------
/template/addons/next-auth/restricted.ts:
--------------------------------------------------------------------------------
1 | // Example of a restricted endpoint that only authenticated users can access from https://next-auth.js.org/getting-started/example
2 |
3 | import { NextApiRequest, NextApiResponse } from "next";
4 | import { unstable_getServerSession as getServerSession } from "next-auth";
5 | import { authOptions as nextAuthOptions } from "./auth/[...nextauth]";
6 |
7 | const restricted = async (req: NextApiRequest, res: NextApiResponse) => {
8 | const session = await getServerSession(req, res, nextAuthOptions);
9 |
10 | if (session) {
11 | res.send({
12 | content:
13 | "This is protected content. You can access this content because you are signed in.",
14 | });
15 | } else {
16 | res.send({
17 | error:
18 | "You must be signed in to view the protected content on this page.",
19 | });
20 | }
21 | };
22 |
23 | export default restricted;
24 |
--------------------------------------------------------------------------------
/template/addons/prisma/auth-schema.prisma:
--------------------------------------------------------------------------------
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 = "file:./db.sqlite"
11 | // url = env("DATABASE_URL")
12 | }
13 |
14 | model Example {
15 | id String @id @default(cuid())
16 | }
17 |
18 | // Necessary for Next auth
19 | model Account {
20 | id String @id @default(cuid())
21 | userId String
22 | type String
23 | provider String
24 | providerAccountId String
25 | refresh_token String?
26 | access_token String?
27 | expires_at Int?
28 | token_type String?
29 | scope String?
30 | id_token String?
31 | session_state String?
32 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
33 |
34 | @@unique([provider, providerAccountId])
35 | }
36 |
37 | model Session {
38 | id String @id @default(cuid())
39 | sessionToken String @unique
40 | userId String
41 | expires DateTime
42 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
43 | }
44 |
45 | model User {
46 | id String @id @default(cuid())
47 | name String?
48 | email String? @unique
49 | emailVerified DateTime?
50 | image String?
51 | accounts Account[]
52 | sessions Session[]
53 | }
54 |
55 | model VerificationToken {
56 | identifier String
57 | token String @unique
58 | expires DateTime
59 |
60 | @@unique([identifier, token])
61 | }
62 |
--------------------------------------------------------------------------------
/template/addons/prisma/client.ts:
--------------------------------------------------------------------------------
1 | // src/server/db/client.ts
2 | import { PrismaClient } from "@prisma/client";
3 |
4 | declare global {
5 | var prisma: PrismaClient | undefined;
6 | }
7 |
8 | export const prisma =
9 | global.prisma ||
10 | new PrismaClient({
11 | log: ["query"],
12 | });
13 |
14 | if (process.env.NODE_ENV !== "production") {
15 | global.prisma = prisma;
16 | }
17 |
--------------------------------------------------------------------------------
/template/addons/prisma/sample-api.ts:
--------------------------------------------------------------------------------
1 | // src/pages/api/examples.ts
2 | import type { NextApiRequest, NextApiResponse } from "next";
3 | import { prisma } from "../../server/db/client";
4 |
5 | const examples = async (req: NextApiRequest, res: NextApiResponse) => {
6 | const examples = await prisma.example.findMany();
7 | res.status(200).json(examples);
8 | };
9 |
10 | export default examples;
11 |
--------------------------------------------------------------------------------
/template/addons/prisma/schema.prisma:
--------------------------------------------------------------------------------
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 = "file:./db.sqlite"
11 | // url = env("DATABASE_URL")
12 | }
13 |
14 | model Example {
15 | id String @id @default(cuid())
16 | }
17 |
--------------------------------------------------------------------------------
/template/addons/tailwind/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/template/addons/tailwind/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/template/addons/tailwind/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 |
3 | module.exports = {
4 | content: ["./src/**/*.{js,ts,jsx,tsx}"],
5 | theme: {
6 | extend: {},
7 | },
8 | plugins: [],
9 | };
10 |
--------------------------------------------------------------------------------
/template/addons/trpc/api-handler.ts:
--------------------------------------------------------------------------------
1 | // src/pages/api/trpc/[trpc].ts
2 | import { createNextApiHandler } from "@trpc/server/adapters/next";
3 | import { appRouter } from "../../../server/router";
4 | import { createContext } from "../../../server/router/context";
5 |
6 | // export API handler
7 | export default createNextApiHandler({
8 | router: appRouter,
9 | createContext: createContext,
10 | });
11 |
--------------------------------------------------------------------------------
/template/addons/trpc/auth-context.ts:
--------------------------------------------------------------------------------
1 | // src/server/router/context.ts
2 | import * as trpc from "@trpc/server";
3 | import * as trpcNext from "@trpc/server/adapters/next";
4 | import { unstable_getServerSession as getServerSession } from "next-auth";
5 |
6 | import { authOptions as nextAuthOptions } from "../../pages/api/auth/[...nextauth]";
7 |
8 | export const createContext = async (
9 | opts?: trpcNext.CreateNextContextOptions,
10 | ) => {
11 | const req = opts?.req;
12 | const res = opts?.res;
13 |
14 | const session =
15 | req && res && (await getServerSession(req, res, nextAuthOptions));
16 |
17 | return {
18 | req,
19 | res,
20 | session,
21 | };
22 | };
23 |
24 | type Context = trpc.inferAsyncReturnType;
25 |
26 | export const createRouter = () => trpc.router();
27 |
--------------------------------------------------------------------------------
/template/addons/trpc/auth-index-router.ts:
--------------------------------------------------------------------------------
1 | // src/server/router/index.ts
2 | import { createRouter } from "./context";
3 | import superjson from "superjson";
4 |
5 | import { exampleRouter } from "./example";
6 | import { authRouter } from "./auth";
7 |
8 | export const appRouter = createRouter()
9 | .transformer(superjson)
10 | .merge("example.", exampleRouter)
11 | .merge("auth.", authRouter);
12 |
13 | // export type definition of API
14 | export type AppRouter = typeof appRouter;
15 |
--------------------------------------------------------------------------------
/template/addons/trpc/auth-prisma-context.ts:
--------------------------------------------------------------------------------
1 | // src/server/router/context.ts
2 | import * as trpc from "@trpc/server";
3 | import * as trpcNext from "@trpc/server/adapters/next";
4 | import { unstable_getServerSession as getServerSession } from "next-auth";
5 |
6 | import { authOptions as nextAuthOptions } from "../../pages/api/auth/[...nextauth]";
7 | import { prisma } from "../db/client";
8 |
9 | export const createContext = async (
10 | opts?: trpcNext.CreateNextContextOptions,
11 | ) => {
12 | const req = opts?.req;
13 | const res = opts?.res;
14 |
15 | const session =
16 | req && res && (await getServerSession(req, res, nextAuthOptions));
17 |
18 | return {
19 | req,
20 | res,
21 | session,
22 | prisma,
23 | };
24 | };
25 |
26 | type Context = trpc.inferAsyncReturnType;
27 |
28 | export const createRouter = () => trpc.router();
29 |
--------------------------------------------------------------------------------
/template/addons/trpc/auth-router.ts:
--------------------------------------------------------------------------------
1 | import { TRPCError } from "@trpc/server";
2 | import { createRouter } from "./context";
3 |
4 | export const authRouter = createRouter()
5 | .query("getSession", {
6 | resolve({ ctx }) {
7 | return ctx.session;
8 | },
9 | })
10 | .middleware(async ({ ctx, next }) => {
11 | // Any queries or mutations after this middleware will
12 | // raise an error unless there is a current session
13 | if (!ctx.session) {
14 | throw new TRPCError({ code: "UNAUTHORIZED" });
15 | }
16 | return next();
17 | })
18 | .query("getSecretMessage", {
19 | async resolve({ ctx }) {
20 | return "You are logged in and can see this secret message!";
21 | },
22 | });
23 |
--------------------------------------------------------------------------------
/template/addons/trpc/base-context.ts:
--------------------------------------------------------------------------------
1 | // src/server/router/context.ts
2 | import * as trpc from "@trpc/server";
3 | import * as trpcNext from "@trpc/server/adapters/next";
4 |
5 | export const createContext = (opts?: trpcNext.CreateNextContextOptions) => {
6 | const req = opts?.req;
7 | const res = opts?.res;
8 |
9 | return {
10 | req,
11 | res,
12 | };
13 | };
14 |
15 | type Context = trpc.inferAsyncReturnType;
16 |
17 | export const createRouter = () => trpc.router();
18 |
--------------------------------------------------------------------------------
/template/addons/trpc/example-prisma-router.ts:
--------------------------------------------------------------------------------
1 | import { createRouter } from "./context";
2 | import { z } from "zod";
3 |
4 | export const exampleRouter = createRouter()
5 | .query("hello", {
6 | input: z
7 | .object({
8 | text: z.string().nullish(),
9 | })
10 | .nullish(),
11 | resolve({ input }) {
12 | return {
13 | greeting: `Hello ${input?.text ?? "world"}`,
14 | };
15 | },
16 | })
17 | .query("getAll", {
18 | async resolve({ ctx }) {
19 | return await ctx.prisma.example.findMany();
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/template/addons/trpc/example-router.ts:
--------------------------------------------------------------------------------
1 | import { createRouter } from "./context";
2 | import { z } from "zod";
3 |
4 | export const exampleRouter = createRouter().query("hello", {
5 | input: z
6 | .object({
7 | text: z.string().nullish(),
8 | })
9 | .nullish(),
10 | resolve({ input }) {
11 | return {
12 | greeting: `Hello ${input?.text ?? "world"}`,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/template/addons/trpc/index-router.ts:
--------------------------------------------------------------------------------
1 | // src/server/router/index.ts
2 | import { createRouter } from "./context";
3 | import superjson from "superjson";
4 |
5 | import { exampleRouter } from "./example";
6 |
7 | export const appRouter = createRouter()
8 | .transformer(superjson)
9 | .merge("example.", exampleRouter);
10 |
11 | // export type definition of API
12 | export type AppRouter = typeof appRouter;
13 |
--------------------------------------------------------------------------------
/template/addons/trpc/prisma-context.ts:
--------------------------------------------------------------------------------
1 | // src/server/router/context.ts
2 | import * as trpc from "@trpc/server";
3 | import * as trpcNext from "@trpc/server/adapters/next";
4 | import { prisma } from "../db/client";
5 |
6 | export const createContext = (opts?: trpcNext.CreateNextContextOptions) => {
7 | const req = opts?.req;
8 | const res = opts?.res;
9 |
10 | return {
11 | req,
12 | res,
13 | prisma,
14 | };
15 | };
16 |
17 | type Context = trpc.inferAsyncReturnType;
18 |
19 | export const createRouter = () => trpc.router();
20 |
--------------------------------------------------------------------------------
/template/addons/trpc/utils.ts:
--------------------------------------------------------------------------------
1 | // src/utils/trpc.ts
2 | import type { AppRouter } from "../server/router";
3 | import { createReactQueryHooks } from "@trpc/react";
4 |
5 | export const trpc = createReactQueryHooks();
6 |
7 | /**
8 | * Check out tRPC docs for Inference Helpers
9 | * https://trpc.io/docs/infer-types#inference-helpers
10 | */
11 |
--------------------------------------------------------------------------------
/template/base/.env-example:
--------------------------------------------------------------------------------
1 | # Note that not all variables here might be in use for your selected configuration
2 |
3 | # Blockfrost
4 | BLOCKFROST_TESTNET_URL=
5 | TESTNET_PROJECT_ID=
6 | BLOCKFROST_MAINNET_URL=
7 | MAINNET_URL=
8 |
9 | # Prisma
10 | DATABASE_URL=
11 |
12 | # Next Auth
13 | NEXTAUTH_SECRET=
14 | NEXTAUTH_URL=
15 |
16 | # Next Auth Github Provider
17 | GITHUB_ID=
18 | GITHUB_SECRET=
19 |
--------------------------------------------------------------------------------
/template/base/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals"]
3 | }
4 |
--------------------------------------------------------------------------------
/template/base/README.md:
--------------------------------------------------------------------------------
1 | # Create Cardano Dapp
2 |
3 | This is an app bootstrapped according to the Cardano Goat stack, also known as the Cardano-Dapp.
4 |
--------------------------------------------------------------------------------
/template/base/_gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env
30 | .env*.local
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 |
--------------------------------------------------------------------------------
/template/base/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/template/base/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | webpack: function (config, options) {
5 | config.experiments = {
6 | asyncWebAssembly: true,
7 | topLevelAwait: true,
8 | layers: true // optional, with some bundlers/frameworks it doesn't work without
9 | };
10 | return config;
11 | },
12 | }
13 |
14 | module.exports = nextConfig
15 |
--------------------------------------------------------------------------------
/template/base/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "next": "12.2.1",
13 | "react": "18.2.0",
14 | "react-dom": "18.2.0",
15 | "lucid-cardano": "^0.6.1"
16 | },
17 | "devDependencies": {
18 | "@types/node": "18.0.0",
19 | "@types/react": "18.0.14",
20 | "@types/react-dom": "18.0.5",
21 | "eslint": "8.18.0",
22 | "eslint-config-next": "12.2.1",
23 | "typescript": "4.7.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/template/base/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScriptAlchemist/create-cardano-dapp/727dcee5b9a9607b1ded45ec1552eccc43903f59/template/base/public/favicon.ico
--------------------------------------------------------------------------------
/template/base/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import type { AppType } from "next/dist/shared/lib/utils";
3 |
4 | const MyApp: AppType = ({ Component, pageProps }) => {
5 | return ;
6 | };
7 |
8 | export default MyApp;
9 |
--------------------------------------------------------------------------------
/template/base/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 |
4 | const Home: NextPage = () => {
5 | return (
6 | <>
7 |
8 | Create Cardano Dapp
9 |
10 |
11 |
12 |
13 |
14 | Create Cardano Dapp
15 |
16 |
17 |
18 |
This stack uses:
19 |
35 |
36 |
37 | >
38 | );
39 | };
40 |
41 | export default Home;
42 |
--------------------------------------------------------------------------------
/template/base/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/template/base/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "noUncheckedIndexedAccess": true
18 | },
19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/template/page-studs/README.md:
--------------------------------------------------------------------------------
1 | # UP FOR DEBATE
2 |
3 | The approach to this setup should probably be looked over so that there doesn't have to be this many template-files.
4 |
5 | This approach will be increasingly difficult with the new layouts RFC where the structure will be even more complex.
6 |
7 | The necessary pages should probably be generated with some codemod tooling so that there can be a minimal amount of templates. What I can think of right now is:
8 |
9 | - \_app.tsx: There will probably be 2 of this: with-trpc and without-trpc. Adding the session-provider for next-auth should be relatively trivial by using some codemod tool
10 | - index.tsx: There will probably be 2 of this: with-tw and without-tw. adding example usages for trpc, auth can probably be injected with codemod.
11 |
--------------------------------------------------------------------------------
/template/page-studs/_app/with-auth-trpc.tsx:
--------------------------------------------------------------------------------
1 | // src/pages/_app.tsx
2 | import { withTRPC } from "@trpc/next";
3 | import type { AppRouter } from "../server/router";
4 | import type { AppType } from "next/dist/shared/lib/utils";
5 | import superjson from "superjson";
6 | import { SessionProvider } from "next-auth/react";
7 | import "../styles/globals.css";
8 |
9 | const MyApp: AppType = ({
10 | Component,
11 | pageProps: { session, ...pageProps },
12 | }) => {
13 | return (
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | const getBaseUrl = () => {
21 | if (typeof window !== "undefined") {
22 | return "";
23 | }
24 | if (process.browser) return ""; // Browser should use current path
25 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
26 |
27 | return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
28 | };
29 |
30 | export default withTRPC({
31 | config({ ctx }) {
32 | /**
33 | * If you want to use SSR, you need to use the server's full URL
34 | * @link https://trpc.io/docs/ssr
35 | */
36 | const url = `${getBaseUrl()}/api/trpc`;
37 |
38 | return {
39 | url,
40 | transformer: superjson,
41 | /**
42 | * @link https://react-query.tanstack.com/reference/QueryClient
43 | */
44 | // queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
45 | };
46 | },
47 | /**
48 | * @link https://trpc.io/docs/ssr
49 | */
50 | ssr: false,
51 | })(MyApp);
52 |
--------------------------------------------------------------------------------
/template/page-studs/_app/with-auth.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import type { AppType } from "next/dist/shared/lib/utils";
3 | import { SessionProvider } from "next-auth/react";
4 |
5 | const MyApp: AppType = ({
6 | Component,
7 | pageProps: { session, ...pageProps },
8 | }) => {
9 | return (
10 |
11 | ;
12 |
13 | );
14 | };
15 |
16 | export default MyApp;
17 |
--------------------------------------------------------------------------------
/template/page-studs/_app/with-trpc.tsx:
--------------------------------------------------------------------------------
1 | // src/pages/_app.tsx
2 | import { withTRPC } from "@trpc/next";
3 | import type { AppRouter } from "../server/router";
4 | import type { AppType } from "next/dist/shared/lib/utils";
5 | import superjson from "superjson";
6 | import "../styles/globals.css";
7 |
8 | const MyApp: AppType = ({ Component, pageProps }) => {
9 | return ;
10 | };
11 |
12 | const getBaseUrl = () => {
13 | if (typeof window !== "undefined") {
14 | return "";
15 | }
16 | if (process.browser) return ""; // Browser should use current path
17 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
18 |
19 | return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
20 | };
21 |
22 | export default withTRPC({
23 | config({ ctx }) {
24 | /**
25 | * If you want to use SSR, you need to use the server's full URL
26 | * @link https://trpc.io/docs/ssr
27 | */
28 | const url = `${getBaseUrl()}/api/trpc`;
29 |
30 | return {
31 | url,
32 | transformer: superjson,
33 | /**
34 | * @link https://react-query.tanstack.com/reference/QueryClient
35 | */
36 | // queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
37 | };
38 | },
39 | /**
40 | * @link https://trpc.io/docs/ssr
41 | */
42 | ssr: false,
43 | })(MyApp);
44 |
--------------------------------------------------------------------------------
/template/page-studs/index/with-auth-trpc-tw.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 | import { trpc } from "../utils/trpc";
4 |
5 | import { signIn, signOut, useSession } from "next-auth/react";
6 |
7 | const AuthShowcase: React.FC = () => {
8 | const { data: secretMessage, isLoading } = trpc.useQuery([
9 | "auth.getSecretMessage",
10 | ]);
11 |
12 | const { data: sessionData } = useSession();
13 |
14 | return (
15 |
16 | {sessionData &&
Logged in as {sessionData?.user?.name}
}
17 | {secretMessage &&
{secretMessage}
}
18 |
signOut() : () => signIn()}
21 | >
22 | {sessionData ? "Sign out" : "Sign in"}
23 |
24 |
25 | );
26 | };
27 |
28 | const Home: NextPage = () => {
29 | return (
30 | <>
31 |
32 | Create Cardano Dapp
33 |
34 |
35 |
36 |
37 |
38 |
39 | Create Cardano Dapp
40 |
41 | This stack uses:
42 |
43 |
57 |
58 | TypeScript
59 |
60 | Strongly typed programming language that builds on JavaScript,
61 | giving you better tooling at any scale
62 |
63 |
69 | Documentation
70 |
71 |
72 |
73 | TailwindCSS
74 |
75 | Rapidly build modern websites without ever leaving your HTML
76 |
77 |
83 | Documentation
84 |
85 |
86 |
100 |
112 |
113 | Prisma
114 |
115 | Build data-driven JavaScript & TypeScript apps in less time
116 |
117 |
123 | Documentation
124 |
125 |
126 |
127 |
128 | >
129 | );
130 | };
131 |
132 | export default Home;
133 |
--------------------------------------------------------------------------------
/template/page-studs/index/with-auth-trpc.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 | import { trpc } from "../utils/trpc";
4 |
5 | import { signIn, signOut, useSession } from "next-auth/react";
6 |
7 | const AuthShowcase: React.FC = () => {
8 | const { data: secretMessage, isLoading } = trpc.useQuery([
9 | "auth.getSecretMessage",
10 | ]);
11 |
12 | const { data: sessionData } = useSession();
13 |
14 | return (
15 |
16 | {sessionData &&
Logged in as {sessionData?.user?.name}
}
17 | {secretMessage &&
{secretMessage}
}
18 |
signOut() : () => signIn()}>
19 | {sessionData ? "Sign out" : "Sign in"}
20 |
21 |
22 | );
23 | };
24 |
25 | const Home: NextPage = () => {
26 | return (
27 | <>
28 |
29 | Create Cardano Dapp
30 |
31 |
32 |
33 |
34 |
35 | Create Cardano Dapp
36 |
37 |
38 |
39 |
This stack uses:
40 |
65 |
66 |
67 |
68 | >
69 | );
70 | };
71 |
72 | export default Home;
73 |
--------------------------------------------------------------------------------
/template/page-studs/index/with-trpc-tw.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 | import { trpc } from "../utils/trpc";
4 |
5 | const Home: NextPage = () => {
6 | const hello = trpc.useQuery(["example.hello", { text: "from tRPC" }]);
7 |
8 | return (
9 | <>
10 |
11 | Create Cardano Dapp
12 |
13 |
14 |
15 |
16 |
17 |
18 | Create Cardano Dapp
19 |
20 | This stack uses:
21 |
22 |
36 |
37 | TypeScript
38 |
39 | Strongly typed programming language that builds on JavaScript,
40 | giving you better tooling at any scale
41 |
42 |
48 | Documentation
49 |
50 |
51 |
52 | TailwindCSS
53 |
54 | Rapidly build modern websites without ever leaving your HTML
55 |
56 |
62 | Documentation
63 |
64 |
65 |
79 |
80 |
81 | {hello.data ?
{hello.data.greeting}
:
Loading..
}
82 |
83 |
84 | >
85 | );
86 | };
87 |
88 | export default Home;
89 |
--------------------------------------------------------------------------------
/template/page-studs/index/with-trpc.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 | import { trpc } from "../utils/trpc";
4 |
5 | const Home: NextPage = () => {
6 | const { data, isLoading } = trpc.useQuery([
7 | "example.hello",
8 | { text: "from tRPC" },
9 | ]);
10 |
11 | return (
12 | <>
13 |
14 | Create Cardano Dapp
15 |
16 |
17 |
18 |
19 |
20 | Create Cardano Dapp
21 |
22 |
23 |
24 |
This stack uses:
25 |
46 |
47 |
{data ?
{data.greeting}
:
Loading..
}
48 |
49 |
50 | >
51 | );
52 | };
53 |
54 | export default Home;
55 |
--------------------------------------------------------------------------------
/template/page-studs/index/with-tw.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import Head from "next/head";
3 |
4 | const Home: NextPage = () => {
5 | return (
6 | <>
7 |
8 | Create Cardano Dapp
9 |
10 |
11 |
12 |
13 |
14 |
15 | Create Cardano Dapp
16 |
17 | This stack uses:
18 |
19 |
33 |
34 | TypeScript
35 |
36 | Strongly typed programming language that builds on JavaScript,
37 | giving you better tooling at any scale
38 |
39 |
45 | Documentation
46 |
47 |
48 |
49 | TailwindCSS
50 |
51 | Rapidly build modern websites without ever leaving your HTML
52 |
53 |
59 | Documentation
60 |
61 |
62 |
63 |
64 | >
65 | );
66 | };
67 |
68 | export default Home;
69 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | /* LANGUAGE COMPILATION OPTIONS */
5 | "target": "ES2020",
6 | "lib": ["DOM", "DOM.Iterable", "ES2020"],
7 | "module": "Node16",
8 | "moduleResolution": "Node16",
9 | "resolveJsonModule": true,
10 |
11 | /* EMIT RULES */
12 | "outDir": "./dist",
13 | "noEmit": true, // TSUP takes care of emitting js for us, in a MUCH faster way
14 | "declaration": true,
15 | "declarationMap": true,
16 | "sourceMap": true,
17 | "removeComments": true,
18 |
19 | /* TYPE CHECKING RULES */
20 | "strict": true,
21 | // "noImplicitAny": true, // Included in "Strict"
22 | // "noImplicitThis": true, // Included in "Strict"
23 | // "strictBindCallApply": true, // Included in "Strict"
24 | // "strictFunctionTypes": true, // Included in "Strict"
25 | // "strictNullChecks": true, // Included in "Strict"
26 | // "strictPropertyInitialization": true, // Included in "Strict"
27 | "noFallthroughCasesInSwitch": true,
28 | "noImplicitOverride": true,
29 | "noImplicitReturns": true,
30 | "noUnusedLocals": true,
31 | "noUnusedParameters": true,
32 | "useUnknownInCatchVariables": true,
33 | "noUncheckedIndexedAccess": true, // TLDR - Checking an indexed value (array[0]) now forces type as there is no confirmation that index exists
34 | // THE BELOW ARE EXTRA STRICT OPTIONS THAT SHOULD ONLY BY CONSIDERED IN VERY SAFE PROJECTS
35 | // "exactOptionalPropertyTypes": true, // TLDR - Setting to undefined is not the same as a property not being defined at all
36 | // "noPropertyAccessFromIndexSignature": true, // TLDR - Use dot notation for objects if youre sure it exists, use ['index'] notaion if unsure
37 |
38 | /* OTHER OPTIONS */
39 | "allowSyntheticDefaultImports": true,
40 | "esModuleInterop": true,
41 | // "emitDecoratorMetadata": true,
42 | // "experimentalDecorators": true,
43 | "forceConsistentCasingInFileNames": true,
44 | "skipLibCheck": true,
45 | "useDefineForClassFields": true
46 | },
47 | "include": ["src"],
48 | "exclude": ["node_modules", "template"]
49 | }
50 |
--------------------------------------------------------------------------------