add some scaffolding to name-generator

nix-hydra
pegasust 2022-12-02 22:42:02 +00:00
parent e40a7aea4d
commit aa35f43191
27 changed files with 596 additions and 9 deletions

3
.gitignore vendored
View File

@ -32,3 +32,6 @@ yarn-error.log*
# turbo # turbo
.turbo .turbo
.direnv .direnv
# I just clone create-t3-turbo and copy the apps and packages, so pls ignore it
create-t3-turbo

12
.npmrc Normal file
View File

@ -0,0 +1,12 @@
# Expo doesn't play nice with pnpm by default.
# The symbolic links of pnpm break the rules of Expo monorepos.
# @link https://docs.expo.dev/guides/monorepos/#common-issues
node-linker=hoisted
# In order to cache Prisma correctly
public-hoist-pattern[]=*prisma*
# FIXME: @prisma/client is required by the @acme/auth,
# but we don't want it installed there since it's already
# installed in the @acme/db package
strict-peer-dependencies=false

12
.prettierrc.cjs Normal file
View File

@ -0,0 +1,12 @@
/** @type {import("prettier").Config} */
module.exports = {
arrowParens: "always",
printWidth: 80,
singleQuote: false,
jsxSingleQuote: false,
semi: true,
trailingComma: "all",
tabWidth: 2,
plugins: [require.resolve("prettier-plugin-tailwindcss")],
tailwindConfig: "./packages/config/tailwind",
};

View File

@ -0,0 +1,4 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
extends: ["../../.eslintrc.cjs", "next"],
};

View File

@ -0,0 +1,131 @@
# Create T3 App
This is an app bootstrapped according to the [init.tips](https://init.tips) stack, also known as the T3-Stack.
## Why are there `.js` files in here?
As per [T3-Axiom #3](https://github.com/t3-oss/create-t3-app/tree/next#3-typesafety-isnt-optional), we believe take typesafety as a first class citizen. Unfortunately, not all frameworks and plugins support TypeScript which means some of the configuration files have to be `.js` files.
We try to emphasize that these files are javascript for a reason, by explicitly declaring its type (`cjs` or `mjs`) depending on what's supported by the library it is used by. Also, all the `js` files in this project are still typechecked using a `@ts-check` comment at the top.
## What's next? How do I make an app with this?
We try to keep this project as simple as possible, so you can start with the most basic configuration and then move on to more advanced configuration.
If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
- [Next-Auth.js](https://next-auth.js.org)
- [Prisma](https://prisma.io)
- [TailwindCSS](https://tailwindcss.com)
- [tRPC](https://trpc.io) (using @next version? [see v10 docs here](https://alpha.trpc.io))
## How do I deploy this?
### Vercel
We recommend deploying to [Vercel](https://vercel.com/?utm_source=t3-oss&utm_campaign=oss). It makes it super easy to deploy NextJs apps.
- Push your code to a GitHub repository.
- Go to [Vercel](https://vercel.com/?utm_source=t3-oss&utm_campaign=oss) and sign up with GitHub.
- Create a Project and import the repository you pushed your code to.
- Add your environment variables.
- Click **Deploy**
- Now whenever you push a change to your repository, Vercel will automatically redeploy your website!
### Docker
You can also dockerize this stack and deploy a container.
1. In your [next.config.mjs](./next.config.mjs), add the `output: "standalone"` option to your config.
2. Create a `.dockerignore` file with the following contents:
<details>
<summary>.dockerignore</summary>
```
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
```
</details>
3. Create a `Dockerfile` with the following contents:
<details>
<summary>Dockerfile</summary>
```Dockerfile
# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
```
</details>
4. You can now build an image to deploy yourself, or use a PaaS such as [Railway's](https://railway.app) automated [Dockerfile deployments](https://docs.railway.app/deploy/dockerfiles) to deploy your app.
## Useful resources
Here are some resources that we commonly refer to:
- [Protecting routes with Next-Auth.js](https://next-auth.js.org/configuration/nextjs#unstable_getserversession)

5
apps/name-generator/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -0,0 +1,22 @@
// @ts-check
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
* This is especially useful for Docker builds.
*/
!process.env.SKIP_ENV_VALIDATION && (await import("./src/env/server.mjs"));
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: true,
swcMinify: true,
experimental: {
// Enables hot-reload and easy integration for local packages
transpilePackages: ["@acme/api", "@acme/auth", "@acme/db"],
},
// We already do linting on GH actions
eslint: {
ignoreDuringBuilds: !!process.env.CI,
},
};
export default config;

View File

@ -0,0 +1,40 @@
{
"name": "@acme/name-generator",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"clean": "rm -rf .next .turbo node_modules",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@acme/api": "*",
"@acme/auth": "*",
"@acme/db": "*",
"@acme/tailwind-config": "*",
"@tanstack/react-query": "^4.16.1",
"@trpc/client": "^10.1.0",
"@trpc/next": "^10.1.0",
"@trpc/react-query": "^10.1.0",
"@trpc/server": "^10.1.0",
"next": "^13.0.5",
"next-auth": "^4.17.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"zod": "^3.18.0"
},
"devDependencies": {
"@types/node": "^18.0.0",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"autoprefixer": "^10.4.13",
"eslint": "^8.28.0",
"eslint-config-next": "13.0.4",
"postcss": "^8.4.19",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.3"
}
}

View File

@ -0,0 +1 @@
module.exports = require("@acme/tailwind-config/postcss");

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

33
apps/name-generator/src/env/client.mjs vendored Normal file
View File

@ -0,0 +1,33 @@
// @ts-check
import { clientEnv, clientSchema } from "./schema.mjs";
const _clientEnv = clientSchema.safeParse(clientEnv);
export const formatErrors = (
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
errors,
) =>
Object.entries(errors)
.map(([name, value]) => {
if (value && "_errors" in value)
return `${name}: ${value._errors.join(", ")}\n`;
})
.filter(Boolean);
if (!_clientEnv.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(_clientEnv.error.format()),
);
throw new Error("Invalid environment variables");
}
for (let key of Object.keys(_clientEnv.data)) {
if (!key.startsWith("NEXT_PUBLIC_")) {
console.warn("❌ Invalid public environment variable name:", key);
throw new Error("Invalid public environment variable name");
}
}
export const env = _clientEnv.data;

29
apps/name-generator/src/env/schema.mjs vendored Normal file
View File

@ -0,0 +1,29 @@
// @ts-check
import { z } from "zod";
/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
export const serverSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
});
/**
* Specify your client-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
* To expose them to the client, prefix them with `NEXT_PUBLIC_`.
*/
export const clientSchema = z.object({
// NEXT_PUBLIC_BAR: z.string(),
});
/**
* You can't destruct `process.env` as a regular object, so you have to do
* it manually here. This is because Next.js evaluates this at build time,
* and only used environment variables are included in the build.
* @type {{ [k in keyof z.infer<typeof clientSchema>]: z.infer<typeof clientSchema>[k] | undefined }}
*/
export const clientEnv = {
// NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR,
};

27
apps/name-generator/src/env/server.mjs vendored Normal file
View File

@ -0,0 +1,27 @@
// @ts-check
/**
* This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars.
* It has to be a `.mjs`-file to be imported there.
*/
import { serverSchema } from "./schema.mjs";
import { env as clientEnv, formatErrors } from "./client.mjs";
const _serverEnv = serverSchema.safeParse(process.env);
if (!_serverEnv.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(_serverEnv.error.format()),
);
throw new Error("Invalid environment variables");
}
for (let key of Object.keys(_serverEnv.data)) {
if (key.startsWith("NEXT_PUBLIC_")) {
console.warn("❌ You are exposing a server-side env-variable:", key);
throw new Error("You are exposing a server-side env-variable");
}
}
export const env = { ..._serverEnv.data, ...clientEnv };

View File

@ -0,0 +1,20 @@
// src/pages/_app.tsx
import "../styles/globals.css";
import { SessionProvider } from "next-auth/react";
import type { Session } from "next-auth";
import type { AppType } from "next/app";
import { trpc } from "../utils/trpc";
const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}) => {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
};
export default trpc.withTRPC(MyApp);

View File

@ -0,0 +1,5 @@
import NextAuth from "next-auth";
import { authOptions } from "@acme/auth";
export default NextAuth(authOptions);

View File

@ -0,0 +1,22 @@
import { appRouter, createContext } from "@acme/api";
import { createNextApiHandler } from "@trpc/server/adapters/next";
// export API handler
export default createNextApiHandler({
router: appRouter,
createContext,
});
// If you need to enable cors, you can do so like this:
// const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// // Enable cors
// await cors(req, res);
// // Let the tRPC handler do its magic
// return createNextApiHandler({
// router: appRouter,
// createContext,
// })(req, res);
// };
// export default handler;

View File

@ -0,0 +1,81 @@
import type { NextPage } from "next";
import Head from "next/head";
import { signIn, signOut } from "next-auth/react";
import { trpc } from "../utils/trpc";
import type { inferProcedureOutput } from "@trpc/server";
import type { AppRouter } from "@acme/api";
const PostCard: React.FC<{
post: inferProcedureOutput<AppRouter["post"]["all"]>[number];
}> = ({ post }) => {
return (
<div className="max-w-2xl rounded-lg border-2 border-gray-500 p-4 transition-all hover:scale-[101%]">
<h2 className="text-2xl font-bold text-[hsl(280,100%,70%)]">
{post.title}
</h2>
<p>{post.content}</p>
</div>
);
};
const Home: NextPage = () => {
const postQuery = trpc.post.all.useQuery();
return (
<>
<Head>
<title>Create T3 App</title>
<meta name="description" content="Generated by create-t3-app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="flex h-screen flex-col items-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-8">
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
Create <span className="text-[hsl(280,100%,70%)]">T3</span> Turbo
</h1>
<AuthShowcase />
<div className="flex h-[60vh] justify-center overflow-y-scroll px-4 text-2xl">
{postQuery.data ? (
<div className="flex flex-col gap-4">
{postQuery.data?.map((p) => {
return <PostCard key={p.id} post={p} />;
})}
</div>
) : (
<p>Loading..</p>
)}
</div>
</div>
</main>
</>
);
};
export default Home;
const AuthShowcase: React.FC = () => {
const { data: session } = trpc.auth.getSession.useQuery();
const { data: secretMessage } = trpc.auth.getSecretMessage.useQuery(
undefined, // no input
{ enabled: !!session?.user },
);
return (
<div className="flex flex-col items-center justify-center gap-4">
{session?.user && (
<p className="text-center text-2xl text-white">
{session && <span>Logged in as {session?.user?.name}</span>}
{secretMessage && <span> - {secretMessage}</span>}
</p>
)}
<button
className="rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20"
onClick={session ? () => signOut() : () => signIn()}
>
{session ? "Sign out" : "Sign in"}
</button>
</div>
);
};

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,44 @@
// src/utils/trpc.ts
import { createTRPCNext } from "@trpc/next";
import { httpBatchLink, loggerLink } from "@trpc/client";
import { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
import type { AppRouter } from "@acme/api";
import { transformer } from "@acme/api/transformer";
const getBaseUrl = () => {
if (typeof window !== "undefined") return ""; // browser should use relative url
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
export const trpc = createTRPCNext<AppRouter>({
config() {
return {
transformer,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
ssr: false,
});
/**
* Inference helpers for input types
* @example type HelloInput = RouterInputs['example']['hello']
**/
export type RouterInputs = inferRouterInputs<AppRouter>;
/**
* Inference helpers for output types
* @example type HelloOutput = RouterOutputs['example']['hello']
**/
export type RouterOutputs = inferRouterOutputs<AppRouter>;

View File

@ -0,0 +1,4 @@
/** @type {import("tailwindcss").Config} */
module.exports = {
presets: [require("@acme/tailwind-config")],
};

View File

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"exclude": [],
"include": [
"next-env.d.ts",
"node_modules/@acme/auth/next-auth.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.cjs",
"**/*.mjs"
]
}

View File

@ -26,6 +26,7 @@
nativeBuildInputs = [ pkgs.bashInteractive ]; nativeBuildInputs = [ pkgs.bashInteractive ];
buildInputs = [ buildInputs = [
pkgs.nodejs-18_x pkgs.nodejs-18_x
# pkgs.prettierd # not available?
pkgs.nodePackages.pnpm pkgs.nodePackages.pnpm
pkgs.nodePackages.prisma pkgs.nodePackages.prisma
pkgs.prisma-engines pkgs.prisma-engines

View File

@ -7,10 +7,15 @@
"packages/*" "packages/*"
], ],
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo build",
"dev": "turbo run dev --parallel", "clean": "rm -rf node_modules",
"lint": "turbo run lint", "clean:workspaces": "turbo clean",
"format": "prettier --write \"**/*.{ts,tsx,md}\"" "db-generate": "turbo db-generate",
"db-push": "turbo db-push",
"dev": "turbo dev --parallel",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"lint": "turbo lint && manypkg check",
"type-check": "turbo type-check"
}, },
"devDependencies": { "devDependencies": {
"eslint-config-custom": "workspace:*", "eslint-config-custom": "workspace:*",
@ -20,6 +25,14 @@
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
}, },
"dependencies": {}, "dependencies": {
"@manypkg/cli": "^0.19.2",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"eslint": "^8.28.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"typescript": "^4.9.3"
},
"packageManager": "pnpm@7.14.0" "packageManager": "pnpm@7.14.0"
} }

View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{ts,tsx}", "./src/_app.tsx"],
theme: {
extend: {},
},
plugins: [],
};

View File

@ -0,0 +1,15 @@
{
"name": "@acme/tailwind-config",
"version": "0.1.0",
"main": "index.js",
"license": "MIT",
"files": [
"index.js",
"postcss.js"
],
"devDependencies": {
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"tailwindcss": "^3.2.4"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -2,25 +2,56 @@
"$schema": "https://turbo.build/schema.json", "$schema": "https://turbo.build/schema.json",
"pipeline": { "pipeline": {
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": [
"outputs": ["dist/**", ".next/**"] "^build",
"^db-generate"
],
"outputs": [
"dist/**",
".next/**",
".expo/**"
]
}, },
"lint": { "lint": {
"outputs": [] "outputs": []
}, },
"dev": { "dev": {
"dependsOn": [
"^db-generate"
],
"cache": false "cache": false
}, },
"dev:local_infra": { "dev:local_infra": {
"cache": false "cache": false
}, },
"db:migrate": { "db-generate": {
"inputs": [
"prisma/schema.prisma"
],
"cache": false
},
"db-push": {
"inputs": [
"prisma/schema.prisma"
],
"cache": false "cache": false
}, },
"test": { "test": {
"outputs": [], "outputs": [],
"cache": false "cache": false
}, },
"type-check": {
"dependsOn": [
"^db-generate"
],
"cache": false
},
"clean": {
"cache": false
},
"//#clean": {
"cache": false
},
"about-me#build": { "about-me#build": {
"dependsOn": [ "dependsOn": [
"^build" "^build"
@ -32,7 +63,10 @@
"NODE_ENV", "NODE_ENV",
"DATABASE_URL" "DATABASE_URL"
], ],
"outputs": ["dist/**", ".next/**"] "outputs": [
"dist/**",
".next/**"
]
} }
} }
} }