├── sandbox.config.json
├── .npmrc
├── apps
├── astro
│ ├── src
│ │ ├── env.d.ts
│ │ ├── layouts
│ │ │ └── Layout.astro
│ │ ├── components
│ │ │ └── Card.astro
│ │ └── pages
│ │ │ └── index.astro
│ ├── tsconfig.json
│ ├── astro.config.mjs
│ ├── .gitignore
│ ├── package.json
│ ├── public
│ │ └── favicon.svg
│ ├── Dockerfile
│ └── README.md
├── vite
│ ├── src
│ │ ├── vite-env.d.ts
│ │ ├── pages
│ │ │ ├── index.tsx
│ │ │ ├── Cats
│ │ │ │ └── index.tsx
│ │ │ ├── AddUser
│ │ │ │ └── index.tsx
│ │ │ └── About
│ │ │ │ └── index.tsx
│ │ ├── index.css
│ │ ├── store
│ │ │ └── index.ts
│ │ ├── router
│ │ │ ├── routes.ts
│ │ │ └── index.tsx
│ │ ├── services
│ │ │ ├── queryClient.ts
│ │ │ └── trpc.ts
│ │ ├── mocks
│ │ │ ├── browser.ts
│ │ │ ├── server.ts
│ │ │ └── handlers.ts
│ │ ├── main.tsx
│ │ ├── App.spec.tsx
│ │ ├── hooks
│ │ │ └── usePrevious.ts
│ │ ├── App.css
│ │ ├── App.tsx
│ │ └── components
│ │ │ ├── AddUserForm
│ │ │ └── AddUserForm.tsx
│ │ │ └── Navbar.tsx
│ ├── postcss.config.cjs
│ ├── tsconfig.json
│ ├── index.html
│ ├── .gitignore
│ ├── tailwind.config.cjs
│ ├── setupTests.ts
│ ├── .eslintcache
│ ├── vite.config.ts
│ ├── public
│ │ ├── vite.svg
│ │ └── mockServiceWorker.js
│ ├── package.json
│ └── Dockerfile
├── remix
│ ├── styles
│ │ └── app.css
│ ├── remix.env.d.ts
│ ├── public
│ │ └── favicon.ico
│ ├── app
│ │ ├── routes
│ │ │ ├── index.spec.tsx
│ │ │ ├── jokes
│ │ │ │ ├── index.tsx
│ │ │ │ ├── $jokeId.tsx
│ │ │ │ └── new.tsx
│ │ │ ├── jokes.tsx
│ │ │ └── index.tsx
│ │ ├── root.tsx
│ │ ├── entry.client.tsx
│ │ └── entry.server.tsx
│ ├── tsconfig.json
│ ├── tailwind.config.js
│ ├── .eslintrc.js
│ ├── .eslintcache
│ ├── remix.config.js
│ ├── package.json
│ ├── README.md
│ └── Dockerfile
├── nextjs
│ ├── public
│ │ ├── favicon.ico
│ │ ├── vercel.svg
│ │ ├── thirteen.svg
│ │ └── next.svg
│ ├── postcss.config.js
│ ├── services
│ │ ├── queryClient.ts
│ │ └── trpc.ts
│ ├── setupTests.ts
│ ├── next-env.d.ts
│ ├── .eslintrc.js
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── api
│ │ │ └── hello.ts
│ │ └── index.tsx
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ ├── __tests__
│ │ ├── e2e
│ │ │ ├── click-boop-button.spec.ts
│ │ │ └── demo-todo-app.spec.ts
│ │ └── index.spec.tsx
│ ├── .gitignore
│ ├── next.config.js
│ ├── package.json
│ ├── README.md
│ ├── Dockerfile
│ ├── styles
│ │ ├── globals.css
│ │ └── Home.module.css
│ └── playwright.config.ts
└── backend
│ └── express
│ ├── vitest.config.js
│ ├── src
│ ├── controllers
│ │ └── cat.ts
│ ├── server.ts
│ ├── app.spec.ts
│ ├── trpc
│ │ ├── isAuthedMiddleware.ts
│ │ ├── loggerMiddleware.ts
│ │ ├── trpc.ts
│ │ ├── appRouter.ts
│ │ └── context.ts
│ └── app.ts
│ ├── tsconfig.json
│ ├── package.json
│ ├── Dockerfile
│ └── .eslintcache
├── pnpm-workspace.yaml
├── .dockerignore
├── .vscode
├── settings.json
└── extensions.json
├── docs
├── logo.png
├── apps
│ ├── remix.png
│ ├── storybook.png
│ ├── vite.svg
│ ├── nextjs.svg
│ └── astro.svg
└── CONTRIBUTING.md
├── packages
├── ui
│ ├── .storybook
│ │ ├── preview-head.html
│ │ ├── styles.css
│ │ ├── preview.js
│ │ └── main.js
│ ├── src
│ │ ├── index.tsx
│ │ ├── Button
│ │ │ ├── Button.tsx
│ │ │ ├── Button.test.tsx
│ │ │ └── Button.stories.tsx
│ │ └── UsersList
│ │ │ ├── UsersList.stories.tsx
│ │ │ └── UsersList.tsx
│ ├── postcss.config.js
│ ├── setupTests.ts
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ ├── tailwind.config.cjs
│ ├── .eslintcache
│ └── package.json
├── schema
│ ├── src
│ │ ├── index.ts
│ │ └── zod
│ │ │ ├── userSchema.ts
│ │ │ └── catSchema.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── .eslintcache
├── database
│ ├── prisma
│ │ ├── dev.db
│ │ ├── migrations
│ │ │ ├── migration_lock.toml
│ │ │ └── 20221026152715_init
│ │ │ │ └── migration.sql
│ │ └── schema.prisma
│ ├── src
│ │ ├── client.ts
│ │ ├── index.spec.ts
│ │ └── index.ts
│ ├── setupTests.ts
│ ├── tsconfig.json
│ ├── .env
│ ├── package.json
│ └── .eslintcache
└── eslint-config-custom
│ ├── index.js
│ └── package.json
├── .husky
├── pre-commit
└── commit-msg
├── .prettierignore
├── commitlint.config.js
├── prettier.config.js
├── .lintstagedrc.js
├── .eslintrc.js
├── e2e
├── artillery-vite.yml
├── index-page-next.spec.ts
└── index-page-vite.spec.ts
├── .github
└── workflows
│ ├── dev-stack.yml
│ └── ci.yml
├── .gitignore
├── turbo.json
├── docker-compose.yml
├── package.json
├── playwright.config.ts
└── README.md
/sandbox.config.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | public-hoist-pattern[]=*prisma*
2 |
--------------------------------------------------------------------------------
/apps/astro/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apps/vite/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "apps/**/*"
3 | - "packages/**/*"
4 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .env
4 | .next
5 | build
6 | dist
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.configPath": "prettier.config.js"
3 | }
4 |
--------------------------------------------------------------------------------
/apps/vite/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./About";
2 | export * from "./AddUser";
3 |
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaminskypavel/fullpower-stack/HEAD/docs/logo.png
--------------------------------------------------------------------------------
/packages/ui/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/remix/styles/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/apps/vite/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm lint-staged
5 |
--------------------------------------------------------------------------------
/docs/apps/remix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaminskypavel/fullpower-stack/HEAD/docs/apps/remix.png
--------------------------------------------------------------------------------
/packages/schema/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./zod/userSchema";
2 | export * from "./zod/catSchema";
3 |
--------------------------------------------------------------------------------
/apps/vite/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "jotai";
2 |
3 | export const countAtom = atom(0);
4 |
--------------------------------------------------------------------------------
/packages/ui/.storybook/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/packages/ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | export * from "./Button/Button";
2 | export * from "./UsersList/UsersList";
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out
3 | dist
4 | .next
5 | public
6 | .husky
7 | next-env.d.ts
8 | yarn.lock
9 |
--------------------------------------------------------------------------------
/docs/apps/storybook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaminskypavel/fullpower-stack/HEAD/docs/apps/storybook.png
--------------------------------------------------------------------------------
/apps/remix/remix.env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | module.exports = { extends: ["@commitlint/config-conventional"] };
3 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit ${1}
5 |
--------------------------------------------------------------------------------
/apps/remix/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaminskypavel/fullpower-stack/HEAD/apps/remix/public/favicon.ico
--------------------------------------------------------------------------------
/apps/nextjs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaminskypavel/fullpower-stack/HEAD/apps/nextjs/public/favicon.ico
--------------------------------------------------------------------------------
/apps/vite/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | export const ROUTES = {
2 | HOME: "/",
3 | CATS: "cats",
4 | ABOUT: "about",
5 | };
6 |
--------------------------------------------------------------------------------
/packages/database/prisma/dev.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaminskypavel/fullpower-stack/HEAD/packages/database/prisma/dev.db
--------------------------------------------------------------------------------
/packages/database/src/client.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 | export const prisma = new PrismaClient();
3 |
--------------------------------------------------------------------------------
/apps/nextjs/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/apps/vite/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
--------------------------------------------------------------------------------
/apps/nextjs/services/queryClient.ts:
--------------------------------------------------------------------------------
1 | import { QueryClient } from "@tanstack/react-query";
2 |
3 | export const queryClient = new QueryClient();
4 |
--------------------------------------------------------------------------------
/apps/vite/src/services/queryClient.ts:
--------------------------------------------------------------------------------
1 | import { QueryClient } from "@tanstack/react-query";
2 |
3 | export const queryClient = new QueryClient();
4 |
--------------------------------------------------------------------------------
/packages/database/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { expect } from "vitest";
2 | import matchers from "@testing-library/jest-dom/matchers";
3 |
4 | expect.extend(matchers);
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "ms-playwright.playwright"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/apps/astro/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/base",
3 | "compilerOptions": {
4 | "jsx": "react-jsx",
5 | "jsxImportSource": "react"
6 | }
7 | }
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | // https://github.com/tailwindlabs/prettier-plugin-tailwindcss
2 | module.exports = {
3 | plugins: [require("prettier-plugin-tailwindcss")],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/database/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "sqlite"
--------------------------------------------------------------------------------
/packages/schema/src/zod/userSchema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const addUserSchemaInput = z.object({
4 | name: z.string().min(5),
5 | email: z.string().email(),
6 | });
7 |
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "*.{js,jsx,ts,tsx}": [
3 | "prettier --write --ignore-unknown --cache",
4 | "eslint --quiet --fix --cache",
5 | // "vitest related"
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/apps/nextjs/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { expect } from "vitest";
2 | import matchers from "@testing-library/jest-dom/matchers";
3 | import "@testing-library/jest-dom/extend-expect";
4 |
5 | expect.extend(matchers);
6 |
--------------------------------------------------------------------------------
/apps/remix/app/routes/index.spec.tsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 |
3 | describe("dummy remix test", () => {
4 | it("dummy test", () => {
5 | expect(1).toBe(1);
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/packages/ui/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { expect } from "vitest";
2 | import matchers from "@testing-library/jest-dom/matchers";
3 | import "@testing-library/jest-dom/extend-expect";
4 |
5 | expect.extend(matchers);
6 |
--------------------------------------------------------------------------------
/apps/backend/express/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | // https://vitejs.dev/config/
4 | export default defineConfig({
5 | test: {
6 | globals: true,
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/apps/remix/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/remix/tsconfig.json",
3 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
4 | "compilerOptions": {
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/remix/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./**/*.{js,ts,jsx,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
--------------------------------------------------------------------------------
/apps/nextjs/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 |
--------------------------------------------------------------------------------
/packages/database/src/index.spec.ts:
--------------------------------------------------------------------------------
1 | // test index.ts
2 |
3 | import { expect, it, describe } from "vitest";
4 |
5 | describe("index.ts", () => {
6 | it("should be true", () => {
7 | expect(true).toBe(true);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/apps/vite/src/services/trpc.ts:
--------------------------------------------------------------------------------
1 | // https://trpc.io/docs/v10/react
2 | import { createTRPCReact } from "@trpc/react-query";
3 | import { AppRouter } from "@fullpower-stack/express-backend";
4 |
5 | export const trpc = createTRPCReact();
6 |
--------------------------------------------------------------------------------
/apps/nextjs/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | "plugin:@next/next/recommended",
4 | "@fullpower-stack/eslint-config-custom",
5 | ],
6 | env: {
7 | browser: true,
8 | amd: true,
9 | node: true,
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/apps/remix/app/routes/jokes/index.tsx:
--------------------------------------------------------------------------------
1 | export default function JokesIndexRoute() {
2 | return (
3 |
4 |
Here's a random joke:
5 |
I was wondering why the frisbee was getting bigger, then it hit me.
6 |
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/apps/vite/src/mocks/browser.ts:
--------------------------------------------------------------------------------
1 | // src/mocks/browser.js
2 | import { setupWorker } from "msw";
3 | import { handlers } from "./handlers";
4 |
5 | // This configures a Service Worker with the given request handlers.
6 | export const worker = setupWorker(...handlers);
7 |
--------------------------------------------------------------------------------
/apps/astro/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config';
2 | // https://docs.astro.build/en/getting-started/
3 | import react from "@astrojs/react";
4 |
5 | // https://astro.build/config
6 | export default defineConfig({
7 | integrations: [react()]
8 | });
--------------------------------------------------------------------------------
/apps/remix/app/routes/jokes.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "@remix-run/react";
2 |
3 | export default function JokesRoute() {
4 | return (
5 |
6 |
J🤪KES
7 |
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/apps/vite/src/mocks/server.ts:
--------------------------------------------------------------------------------
1 | // src/mocks/server.js
2 | import { setupServer } from "msw/node";
3 | import { handlers } from "./handlers";
4 |
5 | // This configures a request mocking server with the given request handlers.
6 | export const server = setupServer(...handlers);
7 |
--------------------------------------------------------------------------------
/packages/ui/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import './styles.css';
2 |
3 | export const parameters = {
4 | actions: { argTypesRegex: "^on[A-Z].*" },
5 | controls: {
6 | matchers: {
7 | color: /(background|color)$/i,
8 | date: /Date$/,
9 | },
10 | },
11 | }
--------------------------------------------------------------------------------
/packages/schema/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/schema",
3 | "main": "./src/index.ts",
4 | "types": "./src/index.ts",
5 | "license": "MIT",
6 | "scripts": {
7 | "lint": "eslint .",
8 | "build": "tsup src/index.ts --format cjs --dts"
9 | }
10 | }
--------------------------------------------------------------------------------
/apps/remix/.eslintrc.js:
--------------------------------------------------------------------------------
1 |
2 | /** @type {import('eslint').Linter.Config} */
3 | module.exports = {
4 | // https://github.com/eslint/eslint/issues/13385
5 | root: true,
6 | extends: [
7 | "@remix-run/eslint-config",
8 | "@remix-run/eslint-config/node"
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/apps/remix/app/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@fullpower-stack/ui";
2 |
3 | export default function Index() {
4 | return (
5 |
6 | Welcome to Fullpower Stack
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/apps/vite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/vite-react/tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["vitest/globals"]
5 | },
6 | "include": ["vite.config.ts", "./src", "./setupTests.ts"],
7 | "exclude": ["node_modules", "**/*.spec.tsx", "**/*.test.tsx", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/backend/express/src/controllers/cat.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const getCatImage = async (text = "hello world") => {
4 | const { data } = await axios.get(
5 | `https://cataas.com/cat/says/${text}?json=true`
6 | );
7 | return data;
8 | };
9 |
10 | export { getCatImage };
11 |
--------------------------------------------------------------------------------
/apps/vite/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/apps/backend/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node18/tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src",
6 | "types": ["node", "vitest/globals"]
7 | },
8 | "include": ["src"],
9 | "exclude": ["node_modules", "**/*.spec.ts", "dist"]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/nextjs/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 |
3 | import type { AppType } from "next/app";
4 | import { trpc } from "../services/trpc";
5 |
6 | const MyApp: AppType = ({ Component, pageProps }) => {
7 | return ;
8 | };
9 |
10 | export default trpc.withTRPC(MyApp);
11 |
--------------------------------------------------------------------------------
/apps/remix/app/routes/jokes/$jokeId.tsx:
--------------------------------------------------------------------------------
1 | export default function JokeRoute() {
2 | return (
3 |
4 |
Here's your hilarious joke:
5 |
6 | Why don't you find hippopotamuses hiding in trees? They're really good
7 | at it.
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/apps/nextjs/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document";
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/vite-react/tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["vitest/globals", "@testing-library/jest-dom"]
5 | },
6 | "include": ["vite.config.ts", "vitest.config.ts", "./src", "./setupTests.ts"],
7 | "exclude": ["node_modules", "dist", "build"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/astro/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | .output/
4 |
5 | # dependencies
6 | node_modules/
7 |
8 | # logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 |
15 | # environment variables
16 | .env
17 | .env.production
18 |
19 | # macOS-specific files
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/apps/nextjs/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx}",
5 | "./components/**/*.{js,ts,jsx,tsx}",
6 | "@fullpower-stack/**/*.{js,ts,jsx,tsx}",
7 | ],
8 | theme: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | };
13 |
--------------------------------------------------------------------------------
/apps/backend/express/src/server.ts:
--------------------------------------------------------------------------------
1 | // express server starting the app
2 |
3 | import app from "./app";
4 |
5 | // eslint-disable-next-line turbo/no-undeclared-env-vars
6 | const PORT = process.env.PORT || 4000;
7 |
8 | app.listen(PORT);
9 | console.log(`Express started on port ${PORT} 🚀`);
10 |
11 | export * from "./trpc/appRouter";
12 |
--------------------------------------------------------------------------------
/packages/schema/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node18/tsconfig.json",
3 | "include": ["."],
4 | "exclude": ["dist", "build", "node_modules"],
5 | "compilerOptions": {
6 | "sourceMap": true,
7 | "outDir": "dist",
8 | "strict": true,
9 | "lib": ["esnext"],
10 | "esModuleInterop": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/database/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node18/tsconfig.json",
3 | "include": ["."],
4 | "exclude": ["dist", "build", "node_modules"],
5 | "compilerOptions": {
6 | "sourceMap": true,
7 | "outDir": "dist",
8 | "strict": true,
9 | "lib": ["esnext"],
10 | "esModuleInterop": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/ui/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import react from "@vitejs/plugin-react";
3 |
4 | export default defineConfig({
5 | plugins: [react() as any],
6 | test: {
7 | globals: true,
8 | environment: "happy-dom", // or 'jsdom', 'node'
9 | setupFiles: "./setupTests.ts",
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["@fullpower-stack/eslint-config-custom"],
5 | ignorePatterns: ["dist", "node_modules", ".next", ".cache"],
6 | settings: {
7 | next: {
8 | rootDir: ["apps/*/"],
9 | },
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## install localy
4 |
5 | ### to install the packages for the whole project
6 |
7 | `pnpm i`
8 |
9 | ### to generate Prisma Client
10 |
11 | `npm --prefix packages\database run prisma:generate`
12 |
13 | ### run tests
14 |
15 | `pnpm run test`
16 |
17 | ### run dev environment
18 |
19 | `pnpm run dev`
20 |
--------------------------------------------------------------------------------
/apps/remix/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\remix\\remix.env.d.ts":"1"},{"size":83,"mtime":1669634556322,"results":"2","hashOfConfig":"3"},{"filePath":"4","messages":"5","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1tzbx0g","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\remix\\remix.env.d.ts",[]]
--------------------------------------------------------------------------------
/apps/nextjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/next/tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["vitest/globals"]
5 | },
6 |
7 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
8 | "exclude": [
9 | "node_modules",
10 | "/**/*.spec.tsx",
11 | "/**/*.test.tsx",
12 | "dist",
13 | ".next",
14 | "next.config.js"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/nextjs/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from "next";
3 |
4 | type Data = {
5 | name: string;
6 | };
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: "John Doe" });
13 | }
14 |
--------------------------------------------------------------------------------
/apps/nextjs/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import react from "@vitejs/plugin-react";
3 |
4 | export default defineConfig({
5 | plugins: [react() as any],
6 | test: {
7 | globals: true,
8 | environment: "happy-dom", // or 'jsdom', 'node'
9 | setupFiles: "./setupTests.ts",
10 | exclude: ["**/node_modules/**", "**/e2e/**/*.spec.*"],
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | "turbo",
4 | "prettier",
5 | "eslint:recommended",
6 | "plugin:@typescript-eslint/recommended",
7 | "prettier",
8 | ],
9 | parser: "@typescript-eslint/parser",
10 | plugins: ["@typescript-eslint"],
11 | rules: {
12 | "@next/next/no-html-link-for-pages": "off",
13 | "react/jsx-key": "off",
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/apps/backend/express/src/app.spec.ts:
--------------------------------------------------------------------------------
1 | import request from "supertest";
2 | import app from "./app";
3 | import { describe, expect, it } from "vitest";
4 |
5 | describe("app.ts", () => {
6 | it('should return return "Hello World!"', async () => {
7 | const res = await request(app).get(
8 | `/trpc/cat?batch=1&input={"0":{"text":"hello"}}`
9 | );
10 | expect(res.statusCode).toEqual(200);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/apps/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/ui/src/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | export const Button: React.FC = () => {
2 | return (
3 | alert("👉 + 🐈 = Boop")}
6 | >
7 | Boop
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/apps/vite/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | **/*/test-results/
27 | **/*/playwright-report/
28 | **/*/playwright/.cache/
29 |
--------------------------------------------------------------------------------
/packages/ui/src/Button/Button.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@testing-library/react";
2 | import { PrimaryButton } from "./Button.stories";
3 |
4 | describe("#PrimaryButton", () => {
5 | it("renders a count button", () => {
6 | render( );
7 | const button = screen.getByRole("button", {
8 | name: /boop/i,
9 | });
10 |
11 | expect(button).toBeVisible();
12 | // console.log(screen.logTestingPlaygroundURL());
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/apps/remix/remix.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@remix-run/dev').AppConfig} */
2 |
3 | module.exports = {
4 | // appDirectory: "app",
5 | // assetsBuildDirectory: "public/build",
6 | // serverBuildPath: "build/index.js",
7 | // publicPath: "/build/",
8 | ignoredRouteFiles: ["**/.*", "**/*.css", "**/*.{test,spec}.{js,jsx,ts,tsx}"],
9 | serverDependenciesToBundle: ['@fullpower-stack/ui'],
10 | watchPaths: [
11 | './../../packages/ui/**/*.tsx',
12 | ],
13 |
14 | };
15 |
--------------------------------------------------------------------------------
/packages/database/.env:
--------------------------------------------------------------------------------
1 | # Environment variables declared in this file are automatically made available to Prisma.
2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
3 |
4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
6 |
7 | DATABASE_URL="file:./dev.db"
--------------------------------------------------------------------------------
/packages/schema/src/zod/catSchema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const getCatImageInputSchema = z.object({
4 | text: z.string().optional(),
5 | });
6 |
7 | export const getCatImageOutputSchema = z.object({
8 | tags: z.array(z.string()),
9 | createdAt: z.string(),
10 | updatedAt: z.string(),
11 | validated: z.boolean(),
12 | owner: z.null(),
13 | file: z.string(),
14 | mimetype: z.string(),
15 | size: z.number(),
16 | _id: z.string(),
17 | url: z.string(),
18 | });
19 |
--------------------------------------------------------------------------------
/e2e/artillery-vite.yml:
--------------------------------------------------------------------------------
1 | # https://www.artillery.io/docs/guides/guides/test-script-reference
2 | config:
3 | target: "http://localhost:5173"
4 | phases:
5 | - duration: 60
6 | arrivalRate: 5
7 | name: Warm up
8 | - duration: 120
9 | arrivalRate: 5
10 | rampTo: 50
11 | name: Ramp up load
12 | - duration: 600
13 | arrivalRate: 50
14 | name: Sustained load
15 | scenarios:
16 | - name: vite main page
17 | flow:
18 | - get:
19 | url: /
20 |
--------------------------------------------------------------------------------
/apps/vite/src/App.spec.tsx:
--------------------------------------------------------------------------------
1 | // check that App is rendered with vitest and msw
2 |
3 | import { Button } from "@fullpower-stack/ui";
4 | import { render, screen } from "@testing-library/react";
5 | import { expect, it, describe } from "vitest";
6 |
7 | describe("dummy", () => {
8 | it("should render a dummy button from a different package", () => {
9 | const { getByText } = render( );
10 | const boopButtonEl = getByText(/Boop/i);
11 |
12 | expect(boopButtonEl).toBeInTheDocument();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/apps/nextjs/__tests__/e2e/click-boop-button.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from "@playwright/test";
2 |
3 | test("should navigate to main page and click boop", async ({ page }) => {
4 | await page.goto("/");
5 | page.once("dialog", (dialog) => {
6 | console.log(`Dialog message: ${dialog.message()}`);
7 |
8 | const res = dialog.message();
9 | test.expect(res).toBe("👉 + 🐈 = Boop");
10 |
11 | dialog.dismiss().catch((e) => {
12 | console.error(e);
13 | });
14 | });
15 | await page.getByRole("button", { name: "Boop" }).click();
16 | });
17 |
--------------------------------------------------------------------------------
/apps/vite/src/pages/Cats/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@fullpower-stack/ui";
2 | import React from "react";
3 | import { trpc } from "../../services/trpc";
4 |
5 | function CatImage() {
6 | const { data } = trpc.cat.useQuery({ text: "hello" });
7 | const { url } = data ?? {};
8 | return (
9 |
10 |
11 | {JSON.stringify(data)} {url}
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default CatImage;
19 |
--------------------------------------------------------------------------------
/apps/vite/src/hooks/usePrevious.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 |
3 | export function usePrevious(value: T): T {
4 | // The ref object is a generic container whose current property is mutable ...
5 | // ... and can hold any value, similar to an instance property on a class
6 | const ref: any = useRef();
7 | // Store current value in ref
8 | useEffect(() => {
9 | ref.current = value;
10 | }, [value]); // Only re-run if value changes
11 | // Return previous value (happens before update in useEffect above)
12 | return ref.current;
13 | }
14 |
--------------------------------------------------------------------------------
/apps/nextjs/.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*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | .next
--------------------------------------------------------------------------------
/apps/remix/app/root.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./styles/app.css";
2 |
3 | import { Links, LiveReload, Outlet } from "@remix-run/react";
4 |
5 | export function links() {
6 | return [{ rel: "stylesheet", href: styles }];
7 | }
8 |
9 | export default function App() {
10 | return (
11 |
12 |
13 |
14 | Remix: So great, it's funny!
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/apps/vite/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 |
3 | module.exports = {
4 | content: [
5 | "./index.html",
6 | "**/*.{js,ts,jsx,tsx}",
7 | ],
8 | theme: {
9 | extend: {
10 | animation: {
11 | fade: 'fadeOut 2s ease-in-out',
12 | },
13 | keyframes: theme => ({
14 | fadeOut: {
15 | '0%': { backgroundColor: theme('colors.yellow.100') },
16 | '100%': { backgroundColor: theme('colors.transparent') },
17 | },
18 | }),
19 | },
20 | },
21 | variants: {},
22 | plugins: [],
23 | }
24 |
--------------------------------------------------------------------------------
/apps/vite/setupTests.ts:
--------------------------------------------------------------------------------
1 | import "@testing-library/jest-dom/extend-expect";
2 | import { cleanup } from "@testing-library/react";
3 | import { afterAll, afterEach, beforeAll } from "vitest";
4 |
5 | import { server } from "./src/mocks/server";
6 |
7 | // Establish API mocking before all tests.
8 | beforeAll(() => server.listen());
9 | // Reset any request handlers that we may add during the tests,
10 | // so they don't affect other tests.
11 | afterEach(() => server.resetHandlers());
12 | // Clean up after the tests are finished.
13 | afterAll(() => {
14 | server.close();
15 | cleanup();
16 | });
17 |
--------------------------------------------------------------------------------
/apps/backend/express/src/trpc/isAuthedMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { initTRPC, TRPCError } from "@trpc/server";
2 | import { Context } from "./context";
3 |
4 | export const t = initTRPC.context().create();
5 |
6 | export const isAuthedMiddleware = t.middleware(({ next, ctx }) => {
7 | if (!ctx.session?.user?.email) {
8 | throw new TRPCError({
9 | code: "UNAUTHORIZED",
10 | });
11 | }
12 |
13 | return next({
14 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15 | // @ts-ignore
16 | ctx: {
17 | session: ctx.session,
18 | },
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/eslint-config-custom",
3 | "main": "index.js",
4 | "license": "MIT",
5 | "dependencies": {
6 | "@typescript-eslint/eslint-plugin": "^5.41.0",
7 | "@typescript-eslint/parser": "^5.41.0",
8 | "eslint": "^7.32.0",
9 | "@next/eslint-plugin-next": "^13.1.1",
10 | "eslint-config-prettier": "^8.5.0",
11 | "eslint-config-turbo": "latest",
12 | "eslint-plugin-react": "7.31.8"
13 | },
14 | "devDependencies": {
15 | "typescript": "^4.8.4"
16 | },
17 | "publishConfig": {
18 | "access": "public"
19 | }
20 | }
--------------------------------------------------------------------------------
/apps/backend/express/src/trpc/loggerMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { initTRPC } from "@trpc/server";
2 | import { Context } from "./context";
3 | import pino from "pino";
4 |
5 | const logger = pino({
6 | name: "express-trpc-server",
7 | level: "debug",
8 | });
9 |
10 | export const t = initTRPC.context().create();
11 |
12 | export const loggerMiddleware = t.middleware(
13 | async ({ path, type, input, next }) => {
14 | const result = await next();
15 | result.ok
16 | ? logger.debug({ path, type, input }, "✅")
17 | : logger.debug({ path, type, input }, "❌");
18 | return result;
19 | }
20 | );
21 |
--------------------------------------------------------------------------------
/apps/astro/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/astro",
3 | "type": "module",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "dev": "astro dev --port 3002",
8 | "start": "astro dev --port 3002 --host",
9 | "build": "astro build",
10 | "preview": "astro preview",
11 | "astro": "astro"
12 | },
13 | "dependencies": {
14 | "@astrojs/react": "^1.2.2",
15 | "@fullpower-stack/ui": "workspace:*",
16 | "@types/react": "^18.0.21",
17 | "@types/react-dom": "^18.0.6",
18 | "astro": "^1.6.5",
19 | "react": "^18.0.0",
20 | "react-dom": "^18.0.0"
21 | }
22 | }
--------------------------------------------------------------------------------
/apps/nextjs/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/remix/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | import { RemixBrowser } from "@remix-run/react";
2 | import { startTransition, StrictMode } from "react";
3 | import { hydrateRoot } from "react-dom/client";
4 |
5 | function hydrate() {
6 | startTransition(() => {
7 | hydrateRoot(
8 | document,
9 |
10 |
11 |
12 | );
13 | });
14 | }
15 |
16 | if (window.requestIdleCallback) {
17 | window.requestIdleCallback(hydrate);
18 | } else {
19 | // Safari doesn't support requestIdleCallback
20 | // https://caniuse.com/requestidlecallback
21 | window.setTimeout(hydrate, 1);
22 | }
23 |
--------------------------------------------------------------------------------
/packages/ui/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 |
3 | module.exports = {
4 | content: ["./src/**/*.{js,ts,jsx,tsx}"],
5 | theme: {
6 | extend: {
7 | animation: {
8 | fade: 'fadeOut 2s ease-in-out',
9 | },
10 | keyframes: theme => ({
11 | fadeOut: {
12 | '0%': { backgroundColor: theme('colors.yellow.100') },
13 | '100%': { backgroundColor: theme('colors.transparent') },
14 | },
15 | }),
16 | },
17 | },
18 | variants: {},
19 | plugins: [],
20 | };
--------------------------------------------------------------------------------
/apps/remix/app/routes/jokes/new.tsx:
--------------------------------------------------------------------------------
1 | export default function NewJokeRoute() {
2 | return (
3 |
4 |
Add your own hilarious joke
5 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/apps/backend/express/src/trpc/trpc.ts:
--------------------------------------------------------------------------------
1 | import { initTRPC } from "@trpc/server";
2 | import { Context } from "./context";
3 | import { isAuthedMiddleware } from "./isAuthedMiddleware";
4 | import { loggerMiddleware } from "./loggerMiddleware";
5 |
6 | const t = initTRPC.context().create();
7 |
8 | export const middleware = t.middleware;
9 | export const router = t.router;
10 |
11 | /**
12 | * Unprotected procedure
13 | **/
14 | export const publicProcedure = t.procedure.use(loggerMiddleware);
15 |
16 | /**
17 | * Protected procedure
18 | **/
19 | export const protectedProcedure = t.procedure
20 | .use(loggerMiddleware)
21 | .use(isAuthedMiddleware);
22 |
--------------------------------------------------------------------------------
/apps/backend/express/src/app.ts:
--------------------------------------------------------------------------------
1 | import * as trpcExpress from "@trpc/server/adapters/express";
2 | import cors from "cors";
3 | import express from "express";
4 | import { appRouter } from "./trpc/appRouter";
5 | import { Express } from "express";
6 | import { createContext } from "./trpc/context";
7 |
8 | const app: Express = express();
9 | app.use(cors());
10 |
11 | app.get("/", (req, res) => {
12 | res.send("Hello World 👋");
13 | });
14 |
15 | app.use(
16 | "/trpc",
17 | (res, req, next) => {
18 | next();
19 | },
20 | trpcExpress.createExpressMiddleware({
21 | router: appRouter,
22 | createContext,
23 | })
24 | );
25 |
26 | export default app;
27 |
--------------------------------------------------------------------------------
/packages/ui/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | module.exports = {
3 | "stories": [
4 | "../src/**/*.stories.mdx",
5 | "../src/**/*.stories.@(js|jsx|ts|tsx)"
6 | ],
7 | "addons": [
8 | "@storybook/addon-essentials",
9 | "@storybook/addon-interactions",
10 | "@storybook/addon-links",
11 | {
12 | name: '@storybook/addon-postcss',
13 | options: {
14 | postcssLoaderOptions: {
15 | implementation: require('postcss'),
16 | },
17 | },
18 | },
19 | ],
20 | "framework": {
21 | "name": "@storybook/react-vite",
22 | "options": {}
23 | },
24 | "docs": {
25 | "docsPage": true
26 | }
27 | }
--------------------------------------------------------------------------------
/packages/database/prisma/migrations/20221026152715_init/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "User" (
3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
4 | "email" TEXT NOT NULL,
5 | "name" TEXT
6 | );
7 |
8 | -- CreateTable
9 | CREATE TABLE "Post" (
10 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
11 | "title" TEXT NOT NULL,
12 | "content" TEXT,
13 | "published" BOOLEAN NOT NULL DEFAULT false,
14 | "authorId" INTEGER NOT NULL,
15 | CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
16 | );
17 |
18 | -- CreateIndex
19 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
20 |
--------------------------------------------------------------------------------
/apps/vite/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\vite\\setupTests.ts":"1","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\vite\\vite.config.ts":"2"},{"size":517,"mtime":1669634556352,"results":"3","hashOfConfig":"4"},{"size":611,"mtime":1669634556931,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"gzob91",{"filePath":"8","messages":"9","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\vite\\setupTests.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\vite\\vite.config.ts",[]]
--------------------------------------------------------------------------------
/packages/database/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 = env("DATABASE_URL")
11 | }
12 |
13 | model User {
14 | id Int @id @default(autoincrement())
15 | email String @unique
16 | name String?
17 | posts Post[]
18 | }
19 |
20 | model Post {
21 | id Int @id @default(autoincrement())
22 | title String
23 | content String?
24 | published Boolean @default(false)
25 | author User @relation(fields: [authorId], references: [id])
26 | authorId Int
27 | }
28 |
--------------------------------------------------------------------------------
/packages/ui/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\ui\\setupTests.ts":"1","C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\ui\\vitest.config.ts":"2"},{"size":118,"mtime":1669634557158,"results":"3","hashOfConfig":"4"},{"size":274,"mtime":1669634557372,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1dehwf7",{"filePath":"8","messages":"9","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\ui\\setupTests.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\ui\\vitest.config.ts",[]]
--------------------------------------------------------------------------------
/apps/vite/src/router/index.tsx:
--------------------------------------------------------------------------------
1 | import { createReactRouter, createRouteConfig } from "@tanstack/react-router";
2 | import React from "react";
3 | import { About, AddUser } from "../pages";
4 | import Cats from "../pages/Cats";
5 | import { ROUTES } from "./routes";
6 | console.log(ROUTES);
7 |
8 | const routeConfig = createRouteConfig().createChildren((createRoute) => [
9 | createRoute({
10 | path: ROUTES.HOME,
11 | component: AddUser,
12 | }),
13 | createRoute({
14 | path: ROUTES.CATS,
15 | component: Cats,
16 | }),
17 | createRoute({
18 | path: ROUTES.ABOUT,
19 | component: About,
20 | }),
21 | ]);
22 |
23 | console.log(routeConfig);
24 | export const router = createReactRouter({ routeConfig });
25 |
--------------------------------------------------------------------------------
/apps/vite/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | // import basicSsl from "@vitejs/plugin-basic-ssl";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | // basicSsl(),
9 | react(),
10 | ],
11 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
12 | // @ts-ignore
13 | test: {
14 | globals: true,
15 | environment: "happy-dom",
16 | setupFiles: "./setupTests.ts",
17 | },
18 | server: {
19 | proxy: {
20 | "/api": {
21 | target: "http://localhost:4000",
22 | changeOrigin: true,
23 | rewrite: (path) => path.replace(/^\/api/, ""),
24 | },
25 | },
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/packages/ui/src/Button/Button.stories.tsx:
--------------------------------------------------------------------------------
1 | // Button.stories.ts|tsx
2 |
3 | import { Meta, StoryFn } from "@storybook/react";
4 |
5 | import { Button } from "./Button";
6 |
7 | export default {
8 | /* 👇 The title prop is optional.
9 | * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
10 | * to learn how to generate automatic titles
11 | */
12 | title: "Button",
13 | component: Button,
14 | } as Meta;
15 |
16 | //👇 We create a “template” of how args map to rendering
17 | const Template: StoryFn = (args) => ;
18 |
19 | export const PrimaryButton = Template.bind({});
20 |
21 | PrimaryButton.args = {
22 | primary: true,
23 | label: "Button",
24 | };
25 |
--------------------------------------------------------------------------------
/apps/vite/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
--------------------------------------------------------------------------------
/packages/database/src/index.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 | export class Database {
4 | private prisma: PrismaClient;
5 |
6 | constructor() {
7 | this.prisma = new PrismaClient();
8 | }
9 |
10 | async $disconnect() {
11 | await this.prisma.$disconnect();
12 | }
13 |
14 | async listUsers(reverse = false) {
15 | const users = await this.prisma.user.findMany({
16 | orderBy: { id: reverse ? "desc" : "asc" },
17 | });
18 | return users;
19 | }
20 |
21 | async createUser(name: string, email: string) {
22 | const user = await this.prisma.user.create({
23 | data: {
24 | email,
25 | name,
26 | },
27 | });
28 | return user;
29 | }
30 | }
31 |
32 | export * from "@prisma/client";
33 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/ui",
3 | "main": "./src/index.tsx",
4 | "types": "./src/index.tsx",
5 | "license": "MIT",
6 | "scripts": {
7 | "test": "vitest run",
8 | "test:watch": "vitest",
9 | "storybook": "storybook dev -p 6006",
10 | "lint": "eslint *.ts*",
11 | "build-storybook": "storybook build"
12 | },
13 | "dependencies": {
14 | "fast-deep-equal": "^3.1.3",
15 | "react": "^18.2.0",
16 | "react-hook-form": "^7.38.0"
17 | },
18 | "devDependencies": {
19 | "@fullpower-stack/eslint-config-custom": "workspace:*",
20 | "@testing-library/jest-dom": "^5.16.5",
21 | "@types/testing-library__jest-dom": "^5.14.5",
22 | "@types/react": "18.0.17",
23 | "@vitejs/plugin-react": "^3.0.0"
24 | }
25 | }
--------------------------------------------------------------------------------
/.github/workflows/dev-stack.yml:
--------------------------------------------------------------------------------
1 | # name: Add dev stack
2 | # on:
3 |
4 | # workflow_dispatch:
5 | # push:
6 | # branches:
7 | # - master
8 | # paths:
9 | # - "package.json"
10 |
11 | # jobs:
12 | # add-dev-stack:
13 | # runs-on: ubuntu-latest
14 | # steps:
15 | # - uses: actions/checkout@v3
16 | # - uses: vithano/add-dev-stack@master
17 | # with:
18 | # DEV_STACK: '### Dev stack'
19 | # ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}}
20 | # IMG_WIDTH: '50'
21 | # FONT_SIZE: '18'
22 | # PATH: '/README.md'
23 | # COMMIT_MESSAGE: 'docs(README): update dev stack'
24 | # TYPES_TO_SHOW: 'Package manager,Monorepo manager,Frontend Framework,CSS Framework,Server-client communication,Backend Framework,ORM,Test runner,Testing components,Component library,Mocking,Lint'
25 |
--------------------------------------------------------------------------------
/packages/database/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/database",
3 | "main": "./src/index.ts",
4 | "types": "./src/index.ts",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "tsnd --rs src/index.ts",
8 | "lint": "eslint .",
9 | "prisma:generate": "prisma generate",
10 | "prisma:push": "prisma db push --skip-generate",
11 | "prisma:studio": "prisma studio",
12 | "build": "tsup src/index.ts --format cjs --dts",
13 | "test": "vitest run",
14 | "test:watch": "vitest",
15 | "postinstall": "prisma generate"
16 | },
17 | "dependencies": {
18 | "@fullpower-stack/schema": "workspace:*",
19 | "@prisma/client": "^4.8.1"
20 | },
21 | "devDependencies": {
22 | "@fakerjs/faker": "^3.0.0",
23 | "@fullpower-stack/eslint-config-custom": "workspace:*",
24 | "prisma": "^4.8.1"
25 | }
26 | }
--------------------------------------------------------------------------------
/.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 | build
15 | dist
16 |
17 | #remix
18 | node_modules
19 |
20 | .cache
21 | build
22 | public/build
23 | .env
24 |
25 |
26 | # misc
27 | .DS_Store
28 | *.pem
29 |
30 | # debug
31 | npm-debug.log*
32 | yarn-debug.log*
33 | yarn-error.log*
34 | .pnpm-debug.log*
35 |
36 | # local env files
37 | .env.local
38 | .env.development.local
39 | .env.test.local
40 | .env.production.local
41 |
42 | # turbo
43 | .turbo
44 |
45 |
46 | packages/database/prisma/dev.db
47 | .eslintcache
48 |
49 | **/playwright-report/**/*
50 | **/test-results/**/*
51 | **/test-report/**/*
52 | **/playwright-report/**/*
53 | **/playwright/.cache/**/*
54 | /screenshots/**/*
--------------------------------------------------------------------------------
/e2e/index-page-next.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | test.describe("index page tests", () => {
4 | test.beforeEach(async ({ page }) => {
5 | await page.goto("http://localhost:3000/");
6 | await page.waitForLoadState("networkidle");
7 | });
8 | test("@smoke", async ({ page }) => {
9 | const isFinished = (response) =>
10 | response.url().includes("/cat") && response.status() === 200;
11 | await page.getByRole("button", { name: "Load Cat" }).click();
12 | const response = await page.waitForResponse(isFinished);
13 | const json = await response.json();
14 | expect(json.result.data).toBeDefined();
15 | });
16 |
17 | test("@visual", async ({ page }) => {
18 | await page.screenshot({
19 | animations: "disabled",
20 | path: `screenshots/index-page-next.png`,
21 | fullPage: true,
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/apps/nextjs/services/trpc.ts:
--------------------------------------------------------------------------------
1 | // https://trpc.io/docs/v10/react
2 | import { httpLink } from "@trpc/client";
3 | import { createTRPCNext } from "@trpc/next";
4 | import type { AppRouter } from "@fullpower-stack/express-backend";
5 |
6 | export const trpc = createTRPCNext({
7 | config({ ctx }) {
8 | return {
9 | links: [
10 | httpLink({
11 | /**
12 | * If you want to use SSR, you need to use the server's full URL
13 | * @link https://trpc.io/docs/ssr
14 | **/
15 | url: `http://localhost:4000/trpc`,
16 | }),
17 | ],
18 | /**
19 | * @link https://tanstack.com/query/v4/docs/reference/QueryClient
20 | **/
21 | // queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
22 | };
23 | },
24 | /**
25 | * @link https://trpc.io/docs/ssr
26 | **/
27 | ssr: false,
28 | });
29 |
--------------------------------------------------------------------------------
/apps/nextjs/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const { join } = require("path");
3 |
4 | /** @type {import('next').NextConfig} */
5 | const nextConfig = {
6 | reactStrictMode: true,
7 |
8 | // migrated from next-transpile-modules
9 | // https://github.com/martpie/next-transpile-modules/releases/tag/the-end
10 | transpilePackages: ["@fullpower-stack/ui"],
11 |
12 | // https://nextjs.org/docs/advanced-features/output-file-tracing#caveats
13 | // https://github.com/vercel/turbo/blob/main/examples/with-docker/apps/web/next.config.js
14 | };
15 |
16 | const standaloneConfig = process.env.STANDALONE_BUILD
17 | ? {
18 | output: "standalone",
19 | experimental: {
20 | // this includes files from the monorepo base two directories up
21 | outputFileTracingRoot: join(__dirname, "../../"),
22 | },
23 | }
24 | : {};
25 |
26 | module.exports = { ...nextConfig, ...standaloneConfig };
27 |
--------------------------------------------------------------------------------
/apps/astro/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/astro/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | export interface Props {
3 | title: string;
4 | }
5 |
6 | const { title } = Astro.props;
7 | ---
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {title}
17 |
18 |
19 |
20 |
21 |
22 |
36 |
--------------------------------------------------------------------------------
/apps/backend/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/express-backend",
3 | "private": true,
4 | "main": "./src/server.ts",
5 | "types": "./src/server.ts",
6 | "scripts": {
7 | "clean": "pnpm rimraf dist",
8 | "dev": "tsnd --rs --deps --transpile-only src/server.ts",
9 | "lint": "eslint .",
10 | "build": "tsc",
11 | "test": "vitest run",
12 | "test:watch": "vitest"
13 | },
14 | "dependencies": {
15 | "@fullpower-stack/database": "workspace:*",
16 | "@fullpower-stack/schema": "workspace:*",
17 | "@trpc/server": "^10.4.0",
18 | "@types/cors": "^2.8.12",
19 | "@types/express": "^4.17.14",
20 | "@types/supertest": "^2.0.12",
21 | "axios": "^1.1.3",
22 | "cors": "^2.8.5",
23 | "express": "^4.18.2",
24 | "pino": "^8.7.0",
25 | "supertest": "^6.3.1",
26 | "zod": "^3.19.1"
27 | },
28 | "devDependencies": {
29 | "@fullpower-stack/eslint-config-custom": "workspace:*",
30 | "@types/node": "^17.0.45"
31 | }
32 | }
--------------------------------------------------------------------------------
/apps/vite/src/mocks/handlers.ts:
--------------------------------------------------------------------------------
1 | // src/mocks/handlers.js
2 | import { rest } from "msw";
3 |
4 | export const handlers = [
5 | rest.get("/login", (req, res, ctx) => {
6 | // Persist user's authentication in the session
7 | sessionStorage.setItem("is-authenticated", "true");
8 |
9 | return res(
10 | // Respond with a 200 status code
11 | ctx.status(200)
12 | );
13 | }),
14 |
15 | rest.get("/user", (req, res, ctx) => {
16 | // Check if the user is authenticated in this session
17 | const isAuthenticated = sessionStorage.getItem("is-authenticated");
18 |
19 | if (!isAuthenticated) {
20 | // If not authenticated, respond with a 403 error
21 | return res(
22 | ctx.status(403),
23 | ctx.json({
24 | errorMessage: "Not authorized",
25 | })
26 | );
27 | }
28 |
29 | // If authenticated, return a mocked user details
30 | return res(
31 | ctx.status(200),
32 | ctx.json({
33 | username: "admin",
34 | })
35 | );
36 | }),
37 | ];
38 |
--------------------------------------------------------------------------------
/packages/ui/src/UsersList/UsersList.stories.tsx:
--------------------------------------------------------------------------------
1 | // Button.stories.ts|tsx
2 |
3 | import { ComponentMeta, ComponentStory } from "@storybook/react";
4 |
5 | import { UsersList } from "./UsersList";
6 | import { faker } from "@faker-js/faker";
7 |
8 | export default {
9 | /* 👇 The title prop is optional.
10 | * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
11 | * to learn how to generate automatic titles
12 | */
13 | title: "UsersList",
14 | component: UsersList,
15 | } as ComponentMeta;
16 |
17 | //👇 We create a “template” of how args map to rendering
18 | const Template: ComponentStory = (args) => (
19 |
20 | );
21 |
22 | export const PrimaryUserList = Template.bind({});
23 |
24 | // generate 10 random users
25 | const users = Array.from({ length: 10 }, (_, idx) => ({
26 | id: idx,
27 | name: faker.name.firstName(),
28 | email: faker.internet.email(),
29 | highlight: faker.datatype.boolean(),
30 | }));
31 |
32 | PrimaryUserList.args = {
33 | users,
34 | };
35 |
--------------------------------------------------------------------------------
/apps/nextjs/__tests__/index.spec.tsx:
--------------------------------------------------------------------------------
1 | import { fireEvent, render, screen, within } from "@testing-library/react";
2 | import { describe, expect, it, vi } from "vitest";
3 | import Home from "../pages/index";
4 |
5 | global.window.alert = vi.fn();
6 |
7 | vi.mock("../services/trpc", () => ({
8 | trpc: {
9 | cat: {
10 | useQuery: () => ({
11 | data: { url: "cat" },
12 | }),
13 | },
14 | },
15 | }));
16 |
17 | describe("Index Page", () => {
18 | it("toBe true", () => {
19 | expect(true).toBe(true);
20 | });
21 |
22 | describe("#Home", async () => {
23 | it("render", () => {
24 | render( );
25 |
26 | const main = within(screen.getByRole("main"));
27 | expect(main.getByTestId("get-started")).toBeDefined();
28 | });
29 |
30 | it("should show an alert when clicking the 'boop' button", async () => {
31 | render( );
32 |
33 | const button = screen.getByText("Boop");
34 | fireEvent.click(button);
35 |
36 | expect(window.alert).toBeCalledWith("👉 + 🐈 = Boop");
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turborepo.org/schema.json",
3 | "globalDependencies": [
4 | ".env"
5 | ],
6 | "globalEnv": [
7 | "NODE_ENV",
8 | "CI",
9 | "PLAYWRIGHT_TEST_BASE_URL",
10 | "PLAYWRIGHT_HEADLESS"
11 | ],
12 | "pipeline": {
13 | "clean": {
14 | "dependsOn": [
15 | "^clean"
16 | ]
17 | },
18 | "build": {
19 | "dependsOn": [
20 | "clean",
21 | "^build",
22 | "prisma:generate"
23 | ],
24 | "outputs": [
25 | "dist/**",
26 | ".next/**"
27 | ]
28 | },
29 | "test": {
30 | "outputs": []
31 | },
32 | "test:ci": {
33 | "outputs": []
34 | },
35 | "lint": {
36 | "outputs": []
37 | },
38 | "dev": {
39 | "dependsOn": [
40 | "^prisma:generate"
41 | ],
42 | "cache": false
43 | },
44 | "start": {
45 | "dependsOn": [
46 | "^build"
47 | ]
48 | },
49 | "prisma:generate": {
50 | "cache": false
51 | },
52 | "prisma:push": {
53 | "cache": false
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/packages/schema/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\schema\\src\\index.ts":"1","C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\schema\\src\\zod\\catSchema.ts":"2","C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\schema\\src\\zod\\userSchema.ts":"3"},{"size":67,"mtime":1669634557068,"results":"4","hashOfConfig":"5"},{"size":391,"mtime":1669634557100,"results":"6","hashOfConfig":"5"},{"size":131,"mtime":1669634557135,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"17hkxwl",{"filePath":"10","messages":"11","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"12","messages":"13","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\schema\\src\\index.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\schema\\src\\zod\\catSchema.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\schema\\src\\zod\\userSchema.ts",[]]
--------------------------------------------------------------------------------
/apps/nextjs/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/vite/src/pages/AddUser/index.tsx:
--------------------------------------------------------------------------------
1 | import { UsersList } from "@fullpower-stack/ui";
2 | import { useAtom } from "jotai";
3 | import { AddUserForm } from "../../components/AddUserForm/AddUserForm";
4 | import { usePrevious } from "../../hooks/usePrevious";
5 | import { trpc } from "../../services/trpc";
6 | import { countAtom } from "../../store";
7 |
8 | const Counter = () => {
9 | const [count] = useAtom(countAtom);
10 | return Count is {count} ;
11 | };
12 |
13 | const AddUser = () => {
14 | const { data } = trpc.list.useQuery();
15 | const { users = [] } = data ?? {};
16 |
17 | const prevUsers = usePrevious(users);
18 |
19 | if (prevUsers?.length) {
20 | users.forEach((user, index) => {
21 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
22 | // @ts-ignore
23 | user["highlight"] = !prevUsers.find(
24 | (prevUser) => prevUser.id === user.id
25 | );
26 | });
27 | }
28 |
29 | return (
30 |
35 | );
36 | };
37 |
38 | export { AddUser };
39 |
--------------------------------------------------------------------------------
/apps/remix/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/remix",
3 | "private": true,
4 | "sideEffects": false,
5 | "scripts": {
6 | "clean": "pnpm rimraf .cache build",
7 | "build": "npm run build:css && remix build",
8 | "build:css": "tailwindcss -m -i ./styles/app.css -o app/styles/app.css",
9 | "dev": "cross-env PORT=3003 concurrently \"npm run dev:css\" \"remix dev\"",
10 | "dev:css": "tailwindcss -w -i ./styles/app.css -o app/styles/app.css",
11 | "start": "cross-env PORT=3003 remix-serve build",
12 | "test": "vitest run",
13 | "test:watch": "vitest",
14 | "lint": "eslint *.ts*"
15 | },
16 | "dependencies": {
17 | "@fullpower-stack/ui": "workspace:*",
18 | "@remix-run/node": "^1.7.4",
19 | "@remix-run/react": "^1.7.4",
20 | "@remix-run/serve": "^1.7.4",
21 | "@tsconfig/remix": "^1.0.2",
22 | "concurrently": "^7.5.0",
23 | "isbot": "^3.5.4",
24 | "react": "^18.2.0",
25 | "react-dom": "^18.2.0"
26 | },
27 | "devDependencies": {
28 | "@remix-run/dev": "^1.7.4",
29 | "@remix-run/eslint-config": "^1.7.4",
30 | "@types/react": "^18.0.15",
31 | "@types/react-dom": "^18.0.6"
32 | },
33 | "engines": {
34 | "node": ">=14"
35 | }
36 | }
--------------------------------------------------------------------------------
/apps/remix/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to Remix!
2 |
3 | - [Remix Docs](https://remix.run/docs)
4 |
5 | ## Development
6 |
7 | From your terminal:
8 |
9 | ```sh
10 | npm run dev
11 | ```
12 |
13 | This starts your app in development mode, rebuilding assets on file changes.
14 |
15 | ## Deployment
16 |
17 | First, build your app for production:
18 |
19 | ```sh
20 | npm run build
21 | ```
22 |
23 | Then run the app in production mode:
24 |
25 | ```sh
26 | npm start
27 | ```
28 |
29 | Now you'll need to pick a host to deploy it to.
30 |
31 | ### DIY
32 |
33 | If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
34 |
35 | Make sure to deploy the output of `remix build`
36 |
37 | - `build/`
38 | - `public/build/`
39 |
40 | ### Using a Template
41 |
42 | When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server.
43 |
44 | ```sh
45 | cd ..
46 | # create a new project, and pick a pre-configured host
47 | npx create-remix@latest
48 | cd my-new-remix-app
49 | # remove the new project's app (not the old one!)
50 | rm -rf app
51 | # copy your app over
52 | cp -R ../my-old-remix-app/app app
53 | ```
54 |
--------------------------------------------------------------------------------
/apps/backend/express/src/trpc/appRouter.ts:
--------------------------------------------------------------------------------
1 | import { Database } from "@fullpower-stack/database";
2 | import {
3 | addUserSchemaInput,
4 | getCatImageInputSchema,
5 | getCatImageOutputSchema,
6 | } from "@fullpower-stack/schema";
7 | import { z } from "zod";
8 | import { getCatImage } from "../controllers/cat";
9 | import { protectedProcedure, publicProcedure, router } from "./trpc";
10 |
11 | const db = new Database();
12 |
13 | export const appRouter = router({
14 | cat: publicProcedure
15 | .input(getCatImageInputSchema)
16 | .query(async ({ ctx, input }) => {
17 | const apiResult = await getCatImage(input?.text);
18 | return apiResult as z.infer;
19 | }),
20 |
21 | list: publicProcedure.query(async (req) => {
22 | const users = await db.listUsers(true);
23 | return { ok: true, isAdmin: false, users };
24 | }),
25 |
26 | admin: protectedProcedure.query((req) => {
27 | return { ok: true, isAdmin: true };
28 | }),
29 |
30 | createUser: protectedProcedure
31 | .input(addUserSchemaInput)
32 | .mutation(async (req) => {
33 | const { name, email } = req.input;
34 | const user = await db.createUser(name, email);
35 |
36 | return user;
37 | }),
38 | });
39 |
40 | export type AppRouter = typeof appRouter;
41 |
--------------------------------------------------------------------------------
/apps/nextjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/nextjs",
3 | "private": true,
4 | "scripts": {
5 | "clean": "pnpm rimraf .next dist build ",
6 | "build": "next build",
7 | "dev": "next dev",
8 | "test": "vitest run",
9 | "test:ci": "start-server-and-test 'next start' http://127.0.0.1:3000 test:e2e",
10 | "test:e2e": "playwright test",
11 | "test:watch": "vitest",
12 | "test:e2e:recorder": "playwright codegen",
13 | "test:e2e:report": "playwright show-report",
14 | "start": "next start",
15 | "lint": "next lint"
16 | },
17 | "dependencies": {
18 | "@fullpower-stack/express-backend": "workspace:*",
19 | "@fullpower-stack/ui": "workspace:*",
20 | "@next/font": "13.1.0",
21 | "@tanstack/react-query": "^4.13.0",
22 | "@trpc/client": "^10.4.0",
23 | "@trpc/next": "^10.7.0",
24 | "@trpc/react-query": "^10.4.0",
25 | "@trpc/server": "^10.4.0",
26 | "@types/react-dom": "^18.0.10",
27 | "next": "13.1.0",
28 | "react": "18.2.0",
29 | "react-dom": "18.2.0",
30 | "zod": "^3.19.1"
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.19.6",
34 | "@fullpower-stack/eslint-config-custom": "workspace:*",
35 | "@types/node": "^17.0.45",
36 | "@types/react": "18.0.17",
37 | "next-transpile-modules": "9.0.0"
38 | }
39 | }
--------------------------------------------------------------------------------
/apps/nextjs/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/e2e/index-page-vite.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 | import { faker } from "@faker-js/faker";
3 |
4 | test.describe("index page tests", () => {
5 | test.beforeEach(async ({ page }) => {
6 | await page.goto("http://localhost:5173/");
7 | await page.waitForLoadState("networkidle");
8 | });
9 | test("@smoke", async ({ page }) => {
10 | const randomEmail = faker.internet.email();
11 | const randomName = faker.name.fullName();
12 | const isFinished = (response) =>
13 | response.url().includes("/list") && response.status() === 200;
14 | await page.getByPlaceholder("name").fill(randomName);
15 | await page.getByPlaceholder("Email address").fill(randomEmail);
16 | await page.getByRole("button", { name: "Save" }).click();
17 | const response = await page.waitForResponse(isFinished);
18 | const json = await response.json();
19 | expect(json.result.data.users).toBeDefined();
20 | const email = await page.getByText(randomEmail).count();
21 | const name = await page.getByText(randomName).count();
22 | expect(email).toBe(1);
23 | expect(name).toBe(1);
24 | });
25 |
26 | test("@visual", async ({ page }) => {
27 | await page.screenshot({
28 | animations: "disabled",
29 | path: `screenshots/index-page-vite.png`,
30 | fullPage: true,
31 | });
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/apps/backend/express/src/trpc/context.ts:
--------------------------------------------------------------------------------
1 | import { faker } from "@faker-js/faker";
2 | import * as trpc from "@trpc/server";
3 | import { CreateExpressContextOptions } from "@trpc/server/adapters/express";
4 |
5 | //eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | interface CreateContextOptions {
7 | // session: Session | null
8 | }
9 |
10 | /**
11 | * Inner function for `createContext` where we create the context.
12 | * This is useful for testing when we don't want to mock Next.js' request/response
13 | */
14 | export async function createContextInner(
15 | _opts: CreateContextOptions & CreateExpressContextOptions
16 | ) {
17 | const req = _opts.req;
18 | const res = _opts.res;
19 |
20 | const session = {
21 | user: {
22 | email: faker.internet.email(),
23 | name: faker.name.firstName(),
24 | id: faker.random.alphaNumeric(10),
25 | },
26 | };
27 |
28 | return {
29 | req,
30 | res,
31 | session,
32 | };
33 | }
34 |
35 | /**
36 | * Creates context for an incoming request
37 | * @link https://trpc.io/docs/context
38 | */
39 |
40 | export async function createContext(
41 | opts: CreateExpressContextOptions
42 | ): Promise {
43 | // for API-response caching see https://trpc.io/docs/caching
44 |
45 | return await createContextInner(opts);
46 | }
47 |
48 | export type Context = trpc.inferAsyncReturnType;
49 |
--------------------------------------------------------------------------------
/apps/astro/src/components/Card.astro:
--------------------------------------------------------------------------------
1 | ---
2 | export interface Props {
3 | title: string;
4 | body: string;
5 | href: string;
6 | }
7 |
8 | const { href, title, body } = Astro.props;
9 | ---
10 |
11 |
12 |
13 |
14 | {title}
15 | →
16 |
17 |
18 | {body}
19 |
20 |
21 |
22 |
63 |
--------------------------------------------------------------------------------
/apps/vite/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/database/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\setupTests.ts":"1","C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\src\\client.ts":"2","C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\src\\index.spec.ts":"3","C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\src\\index.ts":"4"},{"size":118,"mtime":1669634556967,"results":"5","hashOfConfig":"6"},{"size":89,"mtime":1669634556987,"results":"7","hashOfConfig":"6"},{"size":165,"mtime":1669634557008,"results":"8","hashOfConfig":"6"},{"size":613,"mtime":1669634557042,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"x05t7u",{"filePath":"12","messages":"13","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"14","messages":"15","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"16","messages":"17","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\setupTests.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\src\\client.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\src\\index.spec.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\packages\\database\\src\\index.ts",[]]
--------------------------------------------------------------------------------
/apps/nextjs/README.md:
--------------------------------------------------------------------------------
1 | ## Getting Started
2 |
3 | First, run the development server:
4 |
5 | ```bash
6 | yarn dev
7 | ```
8 |
9 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
10 |
11 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
12 |
13 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
14 |
15 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on Vercel
27 |
28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/apps/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fullpower-stack/vite",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "clean": "pnpm rimraf dist",
7 | "dev": "vite --host",
8 | "lint": "eslint *.ts*",
9 | "test": "vitest run",
10 | "build": "tsc && vite build",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "@fullpower-stack/express-backend": "workspace:*",
15 | "@fullpower-stack/schema": "workspace:*",
16 | "@fullpower-stack/ui": "workspace:*",
17 | "@headlessui/react": "^1.7.4",
18 | "@heroicons/react": "^2.0.13",
19 | "@hookform/resolvers": "^2.9.10",
20 | "@tanstack/react-query": "^4.13.0",
21 | "@tanstack/react-router": "0.0.1-beta.25",
22 | "@trpc/client": "^10.4.0",
23 | "@trpc/react-query": "^10.4.0",
24 | "@trpc/server": "^10.4.0",
25 | "fast-deep-equal": "^3.1.3",
26 | "jotai": "^1.10.0",
27 | "react": "^18.2.0",
28 | "react-dom": "^18.2.0",
29 | "react-hook-form": "^7.38.0",
30 | "react-hot-toast": "^2.4.0",
31 | "vite": "^5.0.0"
32 | },
33 | "devDependencies": {
34 | "@fullpower-stack/eslint-config-custom": "workspace:*",
35 | "@tanstack/react-query-devtools": "^4.13.4",
36 | "@tanstack/react-router-devtools": "0.0.1-beta.19",
37 | "@types/react": "^18.0.26",
38 | "@types/react-dom": "^18.0.10",
39 | "@vitejs/plugin-basic-ssl": "^0.1.2",
40 | "msw": "^0.48.1"
41 | },
42 | "msw": {
43 | "workerDirectory": "public"
44 | }
45 | }
--------------------------------------------------------------------------------
/apps/astro/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../layouts/Layout.astro";
3 | import Card from "../components/Card.astro";
4 | import { Button } from "@fullpower-stack/ui";
5 | ---
6 |
7 |
8 |
9 | Welcome to Fullpower Stack
10 |
11 |
12 |
13 |
14 |
58 |
--------------------------------------------------------------------------------
/docs/apps/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/apps/astro/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-bullseye-slim AS builder
2 | #https://turbo.build/repo/docs/handbook/deploying-with-docker
3 |
4 | # Install pnpm
5 | ENV PNPM_HOME="/root/.local/share/pnpm"
6 | ENV PATH="${PATH}:${PNPM_HOME}"
7 | SHELL ["/bin/bash", "-c"]
8 | RUN npm install --global pnpm \
9 | && SHELL=bash pnpm setup \
10 | && source /root/.bashrc
11 |
12 | WORKDIR /app
13 | RUN pnpm install -g turbo
14 | COPY . .
15 | RUN turbo prune --scope=@fullpower-stack/astro --docker
16 |
17 | # Add lockfile and package.json's of isolated subworkspace
18 | FROM node:lts-bullseye-slim AS installer
19 | WORKDIR /app
20 |
21 | # Install pnpm
22 | ENV PNPM_HOME="/root/.local/share/pnpm"
23 | ENV PATH="${PATH}:${PNPM_HOME}"
24 | SHELL ["/bin/bash", "-c"]
25 | RUN npm install --global pnpm \
26 | && SHELL=bash pnpm setup \
27 | && source /root/.bashrc
28 |
29 | # First install the dependencies (as they change less often)
30 | COPY .gitignore .gitignore
31 | COPY --from=builder /app/out/json/ .
32 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
33 |
34 | # https://playwright.dev/docs/browsers
35 | ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 1
36 |
37 | # Build the project
38 | COPY --from=builder /app/out/full/ .
39 | COPY turbo.json turbo.json
40 |
41 | RUN pnpm install
42 | RUN pnpm turbo run build --filter=@fullpower-stack/astro
43 |
44 | ENV NODE_ENV production
45 |
46 | # RUN addgroup --system --gid 1001 nodejs
47 | # RUN adduser --system --uid 1001 astro
48 | # USER astro
49 |
50 | EXPOSE 3002
51 | ENV PORT 3002
52 |
53 | CMD pnpm turbo run start --filter=@fullpower-stack/astro
54 |
--------------------------------------------------------------------------------
/apps/remix/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-bullseye-slim AS builder
2 | #https://turbo.build/repo/docs/handbook/deploying-with-docker
3 |
4 | # Install pnpm
5 | ENV PNPM_HOME="/root/.local/share/pnpm"
6 | ENV PATH="${PATH}:${PNPM_HOME}"
7 | SHELL ["/bin/bash", "-c"]
8 | RUN npm install --global pnpm \
9 | && SHELL=bash pnpm setup \
10 | && source /root/.bashrc
11 |
12 | WORKDIR /app
13 | RUN pnpm install -g turbo
14 | COPY . .
15 | RUN turbo prune --scope=@fullpower-stack/remix --docker
16 |
17 | # Add lockfile and package.json's of isolated subworkspace
18 | FROM node:lts-bullseye-slim AS installer
19 | WORKDIR /app
20 |
21 | # Install pnpm
22 | ENV PNPM_HOME="/root/.local/share/pnpm"
23 | ENV PATH="${PATH}:${PNPM_HOME}"
24 | SHELL ["/bin/bash", "-c"]
25 | RUN npm install --global pnpm \
26 | && SHELL=bash pnpm setup \
27 | && source /root/.bashrc
28 |
29 | # First install the dependencies (as they change less often)
30 | COPY .gitignore .gitignore
31 | COPY --from=builder /app/out/json/ .
32 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
33 |
34 | # https://playwright.dev/docs/browsers
35 | ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 1
36 |
37 | # Build the project
38 | COPY --from=builder /app/out/full/ .
39 | COPY turbo.json turbo.json
40 |
41 | RUN pnpm install
42 | RUN pnpm turbo run build --filter=@fullpower-stack/remix
43 |
44 | ENV NODE_ENV production
45 |
46 | # RUN addgroup --system --gid 1001 nodejs
47 | # RUN adduser --system --uid 1001 remix
48 | # USER remix
49 |
50 | EXPOSE 3003
51 | ENV PORT 3003
52 |
53 | CMD pnpm turbo run start --filter=@fullpower-stack/remix
54 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | express-backend:
5 | container_name: fullpower-stack-backend
6 | build:
7 | context: .
8 | dockerfile: ./apps/backend/express/Dockerfile
9 | restart: always
10 | ports:
11 | - 4000:4000
12 | networks:
13 | - app_network
14 | vite:
15 | container_name: fullpower-stack-vite
16 | build:
17 | context: .
18 | dockerfile: ./apps/vite/Dockerfile
19 | restart: always
20 | ports:
21 | - 5173:5173
22 | networks:
23 | - app_network
24 | depends_on:
25 | - express-backend
26 |
27 | nextjs:
28 | container_name: fullpower-stack-nextjs
29 | build:
30 | context: .
31 | dockerfile: ./apps/nextjs/Dockerfile
32 | restart: always
33 | ports:
34 | - 3000:3000
35 | networks:
36 | - app_network
37 | depends_on:
38 | - express-backend
39 | remix:
40 | container_name: fullpower-stack-remix
41 | build:
42 | context: .
43 | dockerfile: ./apps/remix/Dockerfile
44 | restart: always
45 | ports:
46 | - 3003:3003
47 | networks:
48 | - app_network
49 | depends_on:
50 | - express-backend
51 | astro:
52 | container_name: fullpower-stack-astro
53 | build:
54 | context: .
55 | dockerfile: ./apps/astro/Dockerfile
56 | restart: always
57 | ports:
58 | - 3002:3002
59 | networks:
60 | - app_network
61 | depends_on:
62 | - express-backend
63 |
64 |
65 | # Define a network, which allows containers to communicate
66 | # with each other, by using their container name as a hostname
67 | networks:
68 | app_network:
69 | # external: true
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test 👷♂️
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | paths-ignore:
7 | - "docs/**"
8 | - "**/*.md"
9 |
10 | pull_request:
11 | types: [opened, synchronize]
12 |
13 | jobs:
14 | build:
15 | name: Build and Test
16 | timeout-minutes: 15
17 | runs-on: ubuntu-latest
18 | # To use Remote Caching, uncomment the next lines and follow the steps below.
19 | env:
20 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
21 | TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
22 |
23 | steps:
24 | - name: Check out code
25 | uses: actions/checkout@v3
26 | with:
27 | fetch-depth: 2
28 |
29 | - uses: pnpm/action-setup@v2.0.1
30 | with:
31 | version: 7.13.6
32 |
33 | - name: Setup Node.js environment
34 | uses: actions/setup-node@v3
35 | with:
36 | node-version: 18
37 | cache: "pnpm"
38 |
39 | - name: Install dependencies
40 | run: pnpm install
41 |
42 | - name: Unit Tests
43 | run: pnpm test
44 |
45 | - name: Build
46 | run: pnpm docker:build
47 |
48 | - name: Run
49 | run: pnpm docker:up
50 |
51 | - name: 🌐 Install Playwright Browsers
52 | run: pnpm playwright:install
53 |
54 | - name: Wait for server to be ready
55 | uses: iFaxity/wait-on-action@v1
56 | with:
57 | resource: http://localhost:3000
58 |
59 | - name: Run e2e tests
60 | run: pnpm test:e2e
61 |
62 | - uses: actions/upload-artifact@v3
63 | if: always()
64 | with:
65 | name: playwright-report
66 | path: playwright-report/
67 | retention-days: 30
68 |
--------------------------------------------------------------------------------
/apps/vite/src/pages/About/index.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "@tanstack/react-router";
2 | import React from "react";
3 |
4 | function About() {
5 | return (
6 | // a tailwind flex container with center
7 |
8 |
16 |
21 |
22 |
30 |
35 |
36 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae fuga
37 | illo animi dolorem consequatur explicabo facere iste porro expedita at,
38 | nulla error quo vel ullam eveniet culpa molestias facilis? Iure.
39 |
40 | );
41 | }
42 |
43 | export { About };
44 |
--------------------------------------------------------------------------------
/apps/vite/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { QueryClientProvider } from "@tanstack/react-query";
2 | import { httpBatchLink, httpLink } from "@trpc/client";
3 | import { Toaster } from "react-hot-toast";
4 | import "./App.css";
5 |
6 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
7 | import { Outlet, RouterProvider } from "@tanstack/react-router";
8 | import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
9 | import { Provider as JotaiProvider } from "jotai";
10 | import Navbar from "./components/Navbar";
11 | import { router } from "./router";
12 | import { queryClient } from "./services/queryClient";
13 | import { trpc } from "./services/trpc";
14 |
15 | const getApiUrl = () => {
16 | let apiUrl;
17 |
18 | switch (process.env.NODE_ENV) {
19 | case "test":
20 | apiUrl = "http://localhost:4000/trpc";
21 | break;
22 |
23 | case "production":
24 | apiUrl = "TO BE ADDED";
25 | break;
26 |
27 | case "development":
28 | default:
29 | apiUrl = "/api/trpc";
30 | break;
31 | }
32 |
33 | return apiUrl;
34 | };
35 |
36 | const trpcClient = trpc.createClient({
37 | links: [
38 | httpLink({
39 | url: getApiUrl(),
40 | // optional
41 | headers() {
42 | return {
43 | authorization: "fake-cookie",
44 | };
45 | },
46 | }),
47 | ],
48 | });
49 |
50 | export default function App() {
51 | return (
52 |
53 |
54 |
55 |
56 | {import.meta.env.NODE_ENV !== "production" && (
57 |
58 |
59 |
60 | )}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/apps/backend/express/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-bullseye-slim AS builder
2 | #https://turbo.build/repo/docs/handbook/deploying-with-docker
3 | #https://github.com/vercel/turbo/tree/main/examples/with-docker
4 |
5 | # Install pnpm
6 | ENV PNPM_HOME="/root/.local/share/pnpm"
7 | ENV PATH="${PATH}:${PNPM_HOME}"
8 | SHELL ["/bin/bash", "-c"]
9 | RUN npm install --global pnpm \
10 | && SHELL=bash pnpm setup \
11 | && source /root/.bashrc
12 |
13 | WORKDIR /app
14 | RUN pnpm install -g turbo
15 | COPY . .
16 | RUN turbo prune --scope=@fullpower-stack/express-backend --docker
17 |
18 | # Add lockfile and package.json's of isolated subworkspace
19 | FROM node:lts-bullseye-slim AS installer
20 | WORKDIR /app
21 |
22 | # Install pnpm
23 | ENV PNPM_HOME="/root/.local/share/pnpm"
24 | ENV PATH="${PATH}:${PNPM_HOME}"
25 | SHELL ["/bin/bash", "-c"]
26 | RUN npm install --global pnpm \
27 | && SHELL=bash pnpm setup \
28 | && source /root/.bashrc
29 |
30 | # First install the dependencies (as they change less often)
31 | COPY .gitignore .gitignore
32 | COPY --from=builder /app/out/json/ .
33 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
34 |
35 | # https://playwright.dev/docs/browsers
36 | ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 1
37 |
38 |
39 | # Build the project
40 | COPY --from=builder /app/out/full/ .
41 | COPY turbo.json turbo.json
42 |
43 | RUN pnpm install
44 | # RUN pnpm turbo run build --filter=@fullpower-stack/express-backend
45 |
46 | # Run the app
47 | FROM node:lts-bullseye-slim AS runner
48 | ENV NODE_ENV production
49 |
50 | # Install pnpm
51 | ENV PNPM_HOME="/root/.local/share/pnpm"
52 | ENV PATH="${PATH}:${PNPM_HOME}"
53 | SHELL ["/bin/bash", "-c"]
54 | RUN npm install --global pnpm \
55 | && SHELL=bash pnpm setup \
56 | && source /root/.bashrc
57 |
58 |
59 | WORKDIR /app
60 |
61 | # Don't run production as root
62 | RUN addgroup --system --gid 1001 express
63 | RUN adduser --system --uid 1001 express
64 | RUN mkdir generated
65 | RUN chown express:express generated
66 |
67 |
68 | COPY --from=installer --chown=express:express /app .
69 |
70 | USER express
71 |
72 | CMD pnpm turbo run dev --filter=@fullpower-stack/express-backend
73 |
--------------------------------------------------------------------------------
/apps/astro/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to [Astro](https://astro.build)
2 |
3 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
4 | [](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics)
5 |
6 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
7 |
8 | 
9 |
10 | ## 🚀 Project Structure
11 |
12 | Inside of your Astro project, you'll see the following folders and files:
13 |
14 | ```
15 | /
16 | ├── public/
17 | │ └── favicon.svg
18 | ├── src/
19 | │ ├── components/
20 | │ │ └── Card.astro
21 | │ ├── layouts/
22 | │ │ └── Layout.astro
23 | │ └── pages/
24 | │ └── index.astro
25 | └── package.json
26 | ```
27 |
28 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
29 |
30 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
31 |
32 | Any static assets, like images, can be placed in the `public/` directory.
33 |
34 | ## 🧞 Commands
35 |
36 | All commands are run from the root of the project, from a terminal:
37 |
38 | | Command | Action |
39 | | :--------------------- | :------------------------------------------------- |
40 | | `npm install` | Installs dependencies |
41 | | `npm run dev` | Starts local dev server at `localhost:3000` |
42 | | `npm run build` | Build your production site to `./dist/` |
43 | | `npm run preview` | Preview your build locally, before deploying |
44 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
45 | | `npm run astro --help` | Get help using the Astro CLI |
46 |
47 | ## 👀 Want to learn more?
48 |
49 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
50 |
--------------------------------------------------------------------------------
/apps/vite/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-bullseye-slim AS builder
2 | #https://turbo.build/repo/docs/handbook/deploying-with-docker
3 | #https://github.com/vercel/turbo/tree/main/examples/with-docker
4 |
5 | # Install pnpm
6 | ENV PNPM_HOME="/root/.local/share/pnpm"
7 | ENV PATH="${PATH}:${PNPM_HOME}"
8 | SHELL ["/bin/bash", "-c"]
9 | RUN npm install --global pnpm \
10 | && SHELL=bash pnpm setup \
11 | && source /root/.bashrc
12 |
13 | WORKDIR /app
14 | RUN pnpm install -g turbo
15 | COPY . .
16 | RUN turbo prune --scope=@fullpower-stack/vite --docker
17 |
18 | # Add lockfile and package.json's of isolated subworkspace
19 | FROM node:lts-bullseye-slim AS installer
20 |
21 | # we need this so vite can inject the correct backend api
22 | ENV NODE_ENV test
23 |
24 | WORKDIR /app
25 |
26 | # Install pnpm
27 | ENV PNPM_HOME="/root/.local/share/pnpm"
28 | ENV PATH="${PATH}:${PNPM_HOME}"
29 | SHELL ["/bin/bash", "-c"]
30 | RUN npm install --global pnpm \
31 | && SHELL=bash pnpm setup \
32 | && source /root/.bashrc
33 |
34 | # First install the dependencies (as they change less often)
35 | COPY .gitignore .gitignore
36 | COPY --from=builder /app/out/json/ .
37 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
38 |
39 | # https://playwright.dev/docs/browsers
40 | ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 1
41 |
42 | # Build the project
43 | COPY --from=builder /app/out/full/ .
44 | COPY turbo.json turbo.json
45 |
46 | RUN pnpm install
47 | RUN pnpm turbo run build --scope=@fullpower-stack/vite
48 |
49 | # Run the app
50 | FROM node:lts-bullseye-slim AS runner
51 | ENV NODE_ENV production
52 |
53 | # Install pnpm
54 | ENV PNPM_HOME="/root/.local/share/pnpm"
55 | ENV PATH="${PATH}:${PNPM_HOME}"
56 | SHELL ["/bin/bash", "-c"]
57 | RUN npm install --global pnpm \
58 | && SHELL=bash pnpm setup \
59 | && source /root/.bashrc
60 |
61 |
62 | WORKDIR /app
63 |
64 | # maybe we dont need this if we use vite dev server
65 | RUN npm i -g http-server
66 |
67 | RUN addgroup --system --gid 1001 nodejs
68 | RUN adduser --system --uid 1001 vite
69 | USER vite
70 |
71 | # COPY --from=installer --chown=vite:nodejs /app ./
72 | # EXPOSE 5173
73 | # CMD pnpm dev:vite
74 |
75 | COPY --from=installer --chown=vite:nodejs /app/apps/vite/dist ./
76 | EXPOSE 5173
77 | CMD http-server -p 5173
78 |
--------------------------------------------------------------------------------
/apps/nextjs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-bullseye-slim AS builder
2 | #https://turbo.build/repo/docs/handbook/deploying-with-docker
3 | #https://github.com/vercel/turbo/tree/main/examples/with-docker
4 |
5 | # Install pnpm
6 | ENV PNPM_HOME="/root/.local/share/pnpm"
7 | ENV PATH="${PATH}:${PNPM_HOME}"
8 | SHELL ["/bin/bash", "-c"]
9 | RUN npm install --global pnpm \
10 | && SHELL=bash pnpm setup \
11 | && source /root/.bashrc
12 |
13 | WORKDIR /app
14 | RUN pnpm install -g turbo
15 | COPY . .
16 | RUN turbo prune --scope=@fullpower-stack/nextjs --docker
17 |
18 | # Add lockfile and package.json's of isolated subworkspace
19 | FROM node:lts-bullseye-slim AS installer
20 | WORKDIR /app
21 |
22 | # Install pnpm
23 | ENV PNPM_HOME="/root/.local/share/pnpm"
24 | ENV PATH="${PATH}:${PNPM_HOME}"
25 | SHELL ["/bin/bash", "-c"]
26 | RUN npm install --global pnpm \
27 | && SHELL=bash pnpm setup \
28 | && source /root/.bashrc
29 |
30 | # First install the dependencies (as they change less often)
31 | COPY .gitignore .gitignore
32 | COPY --from=builder /app/out/json/ .
33 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
34 |
35 | # https://playwright.dev/docs/browsers
36 | ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 1
37 | ENV STANDALONE_BUILD true
38 |
39 | # Build the project
40 | COPY --from=builder /app/out/full/ .
41 | COPY turbo.json turbo.json
42 | RUN pnpm install
43 | RUN pnpm turbo run build --filter=@fullpower-stack/nextjs
44 |
45 | # Run the app
46 | FROM node:lts-bullseye-slim AS runner
47 | ENV NODE_ENV production
48 | ENV STANDALONE_BUILD true
49 |
50 | # Install pnpm
51 | ENV PNPM_HOME="/root/.local/share/pnpm"
52 | ENV PATH="${PATH}:${PNPM_HOME}"
53 | SHELL ["/bin/bash", "-c"]
54 | RUN npm install --global pnpm \
55 | && SHELL=bash pnpm setup \
56 | && source /root/.bashrc
57 |
58 |
59 | WORKDIR /app
60 |
61 | RUN addgroup --system --gid 1001 nodejs
62 | RUN adduser --system --uid 1001 nextjs
63 | USER nextjs
64 |
65 | # Automatically leverage output traces to reduce image size
66 | # https://nextjs.org/docs/advanced-features/output-file-tracing
67 | COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/standalone ./
68 | COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
69 |
70 | COPY --from=installer /app/apps/nextjs/public ./apps/nextjs/public
71 | COPY --from=installer /app/apps/nextjs/next.config.js ./apps/nextjs/next.config.js
72 | COPY --from=installer /app/apps/nextjs/package.json ./apps/nextjs/package.json
73 |
74 | EXPOSE 3000
75 | ENV PORT 3000
76 |
77 |
78 | CMD node apps/nextjs/server.js
79 |
--------------------------------------------------------------------------------
/apps/nextjs/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@fullpower-stack/ui";
2 | import Head from "next/head";
3 | import Image from "next/image";
4 | import styles from "../styles/Home.module.css";
5 | import { trpc } from "../services/trpc";
6 |
7 | // this causes vitest to crash
8 | // const inter = Inter({subsets: ["latin"]});
9 | const inter = { className: "" };
10 |
11 | export default function Home() {
12 | const randomCat = trpc.cat.useQuery({ text: "randomCat" });
13 | const { data: { url } = { url: "" } } = randomCat;
14 |
15 | return (
16 | <>
17 |
18 | Create Next App
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Get started by editing
27 | pages/index.tsx
28 |
29 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {
55 | randomCat.refetch();
56 | }}
57 | >
58 | Load Cat
59 |
60 |
61 |
62 |
63 |
82 |
83 | >
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/apps/nextjs/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --max-width: 1100px;
7 | --border-radius: 12px;
8 | --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
9 | "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
10 | "Fira Mono", "Droid Sans Mono", "Courier New", monospace;
11 |
12 | --foreground-rgb: 0, 0, 0;
13 | --background-start-rgb: 214, 219, 220;
14 | --background-end-rgb: 255, 255, 255;
15 |
16 | --primary-glow: conic-gradient(
17 | from 180deg at 50% 50%,
18 | #16abff33 0deg,
19 | #0885ff33 55deg,
20 | #54d6ff33 120deg,
21 | #0071ff33 160deg,
22 | transparent 360deg
23 | );
24 | --secondary-glow: radial-gradient(
25 | rgba(255, 255, 255, 1),
26 | rgba(255, 255, 255, 0)
27 | );
28 |
29 | --tile-start-rgb: 239, 245, 249;
30 | --tile-end-rgb: 228, 232, 233;
31 | --tile-border: conic-gradient(
32 | #00000080,
33 | #00000040,
34 | #00000030,
35 | #00000020,
36 | #00000010,
37 | #00000010,
38 | #00000080
39 | );
40 |
41 | --callout-rgb: 238, 240, 241;
42 | --callout-border-rgb: 172, 175, 176;
43 | --card-rgb: 180, 185, 188;
44 | --card-border-rgb: 131, 134, 135;
45 | }
46 |
47 | @media (prefers-color-scheme: dark) {
48 | :root {
49 | --foreground-rgb: 255, 255, 255;
50 | --background-start-rgb: 0, 0, 0;
51 | --background-end-rgb: 0, 0, 0;
52 |
53 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
54 | --secondary-glow: linear-gradient(
55 | to bottom right,
56 | rgba(1, 65, 255, 0),
57 | rgba(1, 65, 255, 0),
58 | rgba(1, 65, 255, 0.3)
59 | );
60 |
61 | --tile-start-rgb: 2, 13, 46;
62 | --tile-end-rgb: 2, 5, 19;
63 | --tile-border: conic-gradient(
64 | #ffffff80,
65 | #ffffff40,
66 | #ffffff30,
67 | #ffffff20,
68 | #ffffff10,
69 | #ffffff10,
70 | #ffffff80
71 | );
72 |
73 | --callout-rgb: 20, 20, 20;
74 | --callout-border-rgb: 108, 108, 108;
75 | --card-rgb: 100, 100, 100;
76 | --card-border-rgb: 200, 200, 200;
77 | }
78 | }
79 |
80 | * {
81 | box-sizing: border-box;
82 | padding: 0;
83 | margin: 0;
84 | }
85 |
86 | html,
87 | body {
88 | max-width: 100vw;
89 | overflow-x: hidden;
90 | }
91 |
92 | body {
93 | color: rgb(var(--foreground-rgb));
94 | background: linear-gradient(
95 | to bottom,
96 | transparent,
97 | rgb(var(--background-end-rgb))
98 | )
99 | rgb(var(--background-start-rgb));
100 | }
101 |
102 | a {
103 | color: inherit;
104 | text-decoration: none;
105 | }
106 |
107 | @media (prefers-color-scheme: dark) {
108 | html {
109 | color-scheme: dark;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/packages/ui/src/UsersList/UsersList.tsx:
--------------------------------------------------------------------------------
1 | import { FC, memo } from "react";
2 | import equals from "fast-deep-equal/react";
3 | type Props = {
4 | users?: {
5 | id?: number;
6 | name?: string | null;
7 | email?: string;
8 | highlight?: boolean;
9 | }[];
10 | };
11 |
12 | export const UsersList: FC = memo(({ users }) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
29 | ID
30 |
31 |
35 | NAME
36 |
37 |
41 | EMAIL
42 |
43 |
44 |
45 |
46 | {users?.map(({ id, email, name, highlight }) => (
47 |
51 |
52 |
56 |
57 |
58 | {id}
59 |
60 |
61 | {name}
62 |
63 |
64 | {email}
65 |
66 |
67 | ))}
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | }, equals);
76 |
--------------------------------------------------------------------------------
/apps/remix/app/entry.server.tsx:
--------------------------------------------------------------------------------
1 | import { PassThrough } from "stream";
2 | import type { EntryContext } from "@remix-run/node";
3 | import { Response } from "@remix-run/node";
4 | import { RemixServer } from "@remix-run/react";
5 | import isbot from "isbot";
6 | import { renderToPipeableStream } from "react-dom/server";
7 |
8 | const ABORT_DELAY = 5000;
9 |
10 | export default function handleRequest(
11 | request: Request,
12 | responseStatusCode: number,
13 | responseHeaders: Headers,
14 | remixContext: EntryContext
15 | ) {
16 | return isbot(request.headers.get("user-agent"))
17 | ? handleBotRequest(
18 | request,
19 | responseStatusCode,
20 | responseHeaders,
21 | remixContext
22 | )
23 | : handleBrowserRequest(
24 | request,
25 | responseStatusCode,
26 | responseHeaders,
27 | remixContext
28 | );
29 | }
30 |
31 | function handleBotRequest(
32 | request: Request,
33 | responseStatusCode: number,
34 | responseHeaders: Headers,
35 | remixContext: EntryContext
36 | ) {
37 | return new Promise((resolve, reject) => {
38 | let didError = false;
39 |
40 | const { pipe, abort } = renderToPipeableStream(
41 | ,
42 | {
43 | onAllReady() {
44 | const body = new PassThrough();
45 |
46 | responseHeaders.set("Content-Type", "text/html");
47 |
48 | resolve(
49 | new Response(body, {
50 | headers: responseHeaders,
51 | status: didError ? 500 : responseStatusCode,
52 | })
53 | );
54 |
55 | pipe(body);
56 | },
57 | onShellError(error: unknown) {
58 | reject(error);
59 | },
60 | onError(error: unknown) {
61 | didError = true;
62 |
63 | console.error(error);
64 | },
65 | }
66 | );
67 |
68 | setTimeout(abort, ABORT_DELAY);
69 | });
70 | }
71 |
72 | function handleBrowserRequest(
73 | request: Request,
74 | responseStatusCode: number,
75 | responseHeaders: Headers,
76 | remixContext: EntryContext
77 | ) {
78 | return new Promise((resolve, reject) => {
79 | let didError = false;
80 |
81 | const { pipe, abort } = renderToPipeableStream(
82 | ,
83 | {
84 | onShellReady() {
85 | const body = new PassThrough();
86 |
87 | responseHeaders.set("Content-Type", "text/html");
88 |
89 | resolve(
90 | new Response(body, {
91 | headers: responseHeaders,
92 | status: didError ? 500 : responseStatusCode,
93 | })
94 | );
95 |
96 | pipe(body);
97 | },
98 | onShellError(err: unknown) {
99 | reject(err);
100 | },
101 | onError(error: unknown) {
102 | didError = true;
103 |
104 | console.error(error);
105 | },
106 | }
107 | );
108 |
109 | setTimeout(abort, ABORT_DELAY);
110 | });
111 | }
112 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fullpower-stack",
3 | "private": true,
4 | "description": "a boiler plate for a cutting edge technologies",
5 | "author": "Pavel 'PK' Kaminsky",
6 | "license": "MIT",
7 | "workspaces": [
8 | "apps/*",
9 | "packages/*"
10 | ],
11 | "scripts": {
12 | "clean": "turbo run clean",
13 | "build": "turbo run build",
14 | "dev": "turbo run dev --parallel --no-cache",
15 | "dev:vite": "turbo run dev --parallel --no-cache --filter=@fullpower-stack/vite --filter=@fullpower-stack/express-backend",
16 | "dev:nextjs": "turbo run dev --parallel --no-cache --filter=@fullpower-stack/nextjs --filter=@fullpower-stack/express-backend",
17 | "test": "turbo run test",
18 | "test:ci": "turbo run test:ci",
19 | "test:watch": "turbo run test:watch",
20 | "lint": "turbo run lint -- --fix --cache",
21 | "format": "prettier --write \"**/*.{ts,tsx,md}\" --cache",
22 | "prepare": "husky install",
23 | "docker:build": "cross-env COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml build --parallel",
24 | "docker:up": "docker-compose up -d",
25 | "docker:down": "docker-compose down",
26 | "docker:artillery": "pnpm artillery run ./e2e/artillery-vite.yml",
27 | "test:e2e": "playwright test",
28 | "test:e2e:smoke": "pnpm test:e2e -g @smoke",
29 | "test:e2e:visual": "pnpm test:e2e -g @visual",
30 | "playwright:install": "playwright install --with-deps"
31 | },
32 | "devDependencies": {
33 | "@faker-js/faker": "^7.6.0",
34 | "@fullpower-stack/eslint-config-custom": "workspace:*",
35 | "@playwright/test": "^1.30.0",
36 | "@storybook/addon-essentials": "7.0.0-alpha.54",
37 | "@storybook/addon-interactions": "7.0.0-alpha.54",
38 | "@storybook/addon-links": "7.0.0-alpha.54",
39 | "@storybook/addon-postcss": "^2.0.0",
40 | "@storybook/react": "7.0.0-alpha.54",
41 | "@storybook/react-vite": "7.0.0-alpha.54",
42 | "@storybook/testing-library": "0.0.13",
43 | "@testing-library/jest-dom": "^5.16.5",
44 | "@testing-library/react": "^13.4.0",
45 | "@tsconfig/next": "^1.0.5",
46 | "@tsconfig/node18": "^1.0.1",
47 | "@tsconfig/vite-react": "^1.0.1",
48 | "@types/node": "^17.0.45",
49 | "@vitejs/plugin-react": "^3.0.1",
50 | "artillery": "2.0.0-27",
51 | "autoprefixer": "^10.4.13",
52 | "cross-env": "^7.0.3",
53 | "eslint": "7.32.0",
54 | "eslint-config-prettier": "^8.6.0",
55 | "happy-dom": "^7.8.1",
56 | "husky": "^8.0.3",
57 | "lint-staged": "^13.1.0",
58 | "playwright": "^1.30.0",
59 | "postcss": "^8.4.21",
60 | "prettier": "^2.8.3",
61 | "prettier-plugin-tailwindcss": "^0.1.13",
62 | "start-server-and-test": "^1.15.3",
63 | "storybook": "7.0.0-alpha.54",
64 | "ts-node-dev": "latest",
65 | "turbo": "latest",
66 | "vitest": "^0.25.8"
67 | },
68 | "engines": {
69 | "node": ">=16.0.0"
70 | },
71 | "dependencies": {
72 | "@commitlint/cli": "^17.4.2",
73 | "@commitlint/config-conventional": "^17.4.2",
74 | "dotenv": "^16.0.3",
75 | "rimraf": "^3.0.2",
76 | "tailwindcss": "^3.2.4",
77 | "tsup": "^6.5.0",
78 | "typescript": "5.0.0-beta",
79 | "zod": "^3.20.2"
80 | },
81 | "repository": {
82 | "type": "git",
83 | "url": "https://github.com/kaminskypavel/fullpower-stack.git"
84 | },
85 | "packageManager": "pnpm@7.14.2"
86 | }
87 |
--------------------------------------------------------------------------------
/docs/apps/nextjs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | next-black
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from "@playwright/test";
2 | import { devices } from "@playwright/test";
3 |
4 | /**
5 | * Read environment variables from file.
6 | * https://github.com/motdotla/dotenv
7 | */
8 | // require('dotenv').config();
9 |
10 | const opts = {
11 | // launch headless on CI, in browser locally
12 | headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS,
13 | // collectCoverage: !!process.env.PLAYWRIGHT_HEADLESS
14 | };
15 | const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL || "http://localhost:3000";
16 |
17 | /**
18 | * See https://playwright.dev/docs/test-configuration.
19 | */
20 | const config: PlaywrightTestConfig = {
21 | testDir: "./e2e",
22 | /* Maximum time one test can run for. */
23 | timeout: 30 * 1000,
24 | expect: {
25 | /**
26 | * Maximum time expect() should wait for the condition to be met.
27 | * For example in `await expect(locator).toHaveText();`
28 | */
29 | timeout: 5000,
30 | },
31 | /* Run tests in files in parallel */
32 | fullyParallel: true,
33 | /* Fail the build on CI if you accidentally left test.only in the source code. */
34 | forbidOnly: !!process.env.CI,
35 | /* Retry on CI only */
36 | retries: process.env.CI ? 2 : 0,
37 | /* Opt out of parallel tests on CI. */
38 | workers: process.env.CI ? 1 : undefined,
39 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
40 | reporter: "html",
41 |
42 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
43 | use: {
44 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
45 | actionTimeout: 0,
46 | /* Base URL to use in actions like `await page.goto('/')`. */
47 | baseURL: baseUrl,
48 |
49 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
50 | trace: "on-first-retry",
51 | },
52 |
53 | /* Configure projects for major browsers */
54 | projects: [
55 | {
56 | name: "chromium",
57 | use: {
58 | ...devices["Desktop Chrome"],
59 | video: "on",
60 | headless: opts.headless,
61 | },
62 | },
63 |
64 | // {
65 | // name: 'firefox',
66 | // use: {
67 | // ...devices['Desktop Firefox'],
68 | // },
69 | // },
70 |
71 | // {
72 | // name: 'webkit',
73 | // use: {
74 | // ...devices['Desktop Safari'],
75 | // },
76 | // },
77 |
78 | /* Test against mobile viewports. */
79 | // {
80 | // name: "Mobile Chrome",
81 | // use: {
82 | // ...devices["Pixel 5"],
83 | // },
84 | // },
85 | // {
86 | // name: 'Mobile Safari',
87 | // use: {
88 | // ...devices['iPhone 12'],
89 | // },
90 | // },
91 |
92 | /* Test against branded browsers. */
93 | // {
94 | // name: 'Microsoft Edge',
95 | // use: {
96 | // channel: 'msedge',
97 | // },
98 | // },
99 | // {
100 | // name: 'Google Chrome',
101 | // use: {
102 | // channel: 'chrome',
103 | // },
104 | // },
105 | ],
106 |
107 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */
108 | // outputDir: "test-results/",
109 |
110 | /* Run your local dev server before starting the tests */
111 | // webServer: {
112 | // command: 'npm run start',
113 | // port: 3000,
114 | // },
115 | };
116 |
117 | export default config;
118 |
--------------------------------------------------------------------------------
/apps/nextjs/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from "@playwright/test";
2 | import { devices } from "@playwright/test";
3 |
4 | /**
5 | * Read environment variables from file.
6 | * https://github.com/motdotla/dotenv
7 | */
8 | // require('dotenv').config();
9 |
10 | const opts = {
11 | // launch headless on CI, in browser locally
12 | headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS,
13 | // collectCoverage: !!process.env.PLAYWRIGHT_HEADLESS
14 | };
15 | const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL || "http://localhost:3000";
16 |
17 | /**
18 | * See https://playwright.dev/docs/test-configuration.
19 | */
20 | const config: PlaywrightTestConfig = {
21 | testDir: "./__tests__/e2e",
22 | /* Maximum time one test can run for. */
23 | timeout: 30 * 1000,
24 | expect: {
25 | /**
26 | * Maximum time expect() should wait for the condition to be met.
27 | * For example in `await expect(locator).toHaveText();`
28 | */
29 | timeout: 5000,
30 | },
31 | /* Run tests in files in parallel */
32 | fullyParallel: true,
33 | /* Fail the build on CI if you accidentally left test.only in the source code. */
34 | forbidOnly: !!process.env.CI,
35 | /* Retry on CI only */
36 | retries: process.env.CI ? 2 : 0,
37 | /* Opt out of parallel tests on CI. */
38 | workers: process.env.CI ? 1 : undefined,
39 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
40 | reporter: process.env.CI ? "github" : "html",
41 |
42 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
43 | use: {
44 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
45 | actionTimeout: 0,
46 | /* Base URL to use in actions like `await page.goto('/')`. */
47 | baseURL: baseUrl,
48 |
49 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
50 | trace: "on-first-retry",
51 | },
52 |
53 | /* Configure projects for major browsers */
54 | projects: [
55 | {
56 | name: "chromium",
57 | use: {
58 | ...devices["Desktop Chrome"],
59 | video: "on",
60 | headless: opts.headless,
61 | },
62 | },
63 |
64 | // {
65 | // name: 'firefox',
66 | // use: {
67 | // ...devices['Desktop Firefox'],
68 | // },
69 | // },
70 |
71 | // {
72 | // name: 'webkit',
73 | // use: {
74 | // ...devices['Desktop Safari'],
75 | // },
76 | // },
77 |
78 | /* Test against mobile viewports. */
79 | // {
80 | // name: "Mobile Chrome",
81 | // use: {
82 | // ...devices["Pixel 5"],
83 | // },
84 | // },
85 | // {
86 | // name: 'Mobile Safari',
87 | // use: {
88 | // ...devices['iPhone 12'],
89 | // },
90 | // },
91 |
92 | /* Test against branded browsers. */
93 | // {
94 | // name: 'Microsoft Edge',
95 | // use: {
96 | // channel: 'msedge',
97 | // },
98 | // },
99 | // {
100 | // name: 'Google Chrome',
101 | // use: {
102 | // channel: 'chrome',
103 | // },
104 | // },
105 | ],
106 |
107 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */
108 | // outputDir: 'test-results/',
109 |
110 | /* Run your local dev server before starting the tests */
111 | // webServer: {
112 | // command: 'npm run start',
113 | // port: 3000,
114 | // },
115 | };
116 |
117 | export default config;
118 |
--------------------------------------------------------------------------------
/docs/apps/astro.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/backend/express/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\app.spec.ts":"1","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\app.ts":"2","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\controllers\\cat.ts":"3","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\server.ts":"4","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\appRouter.ts":"5","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\context.ts":"6","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\isAuthedMiddleware.ts":"7","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\loggerMiddleware.ts":"8","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\trpc.ts":"9","C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\vitest.config.js":"10"},{"size":344,"mtime":1669634555618,"results":"11","hashOfConfig":"12"},{"size":620,"mtime":1669634555649,"results":"13","hashOfConfig":"12"},{"size":218,"mtime":1669634555675,"results":"14","hashOfConfig":"12"},{"size":265,"mtime":1669634555696,"results":"15","hashOfConfig":"12"},{"size":1111,"mtime":1669634607968,"results":"16","hashOfConfig":"12"},{"size":592,"mtime":1669634555767,"results":"17","hashOfConfig":"12"},{"size":472,"mtime":1669634555792,"results":"18","hashOfConfig":"12"},{"size":501,"mtime":1669634555820,"results":"19","hashOfConfig":"12"},{"size":547,"mtime":1669634591645,"results":"20","hashOfConfig":"12"},{"size":153,"mtime":1666180991359,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"fkfikt",{"filePath":"24","messages":"25","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"28","messages":"29","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"32","messages":"33","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"34","messages":"35","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"36","messages":"37","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"38","messages":"39","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\app.spec.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\app.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\controllers\\cat.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\server.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\appRouter.ts",["42","43","44"],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\context.ts",["45"],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\isAuthedMiddleware.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\loggerMiddleware.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\src\\trpc\\trpc.ts",[],"C:\\Users\\kamin\\Desktop\\fullpower-stack\\apps\\backend\\express\\vitest.config.js",[],{"ruleId":"46","severity":1,"message":"47","line":16,"column":21,"nodeType":"48","messageId":"49","endLine":16,"endColumn":24},{"ruleId":"46","severity":1,"message":"50","line":21,"column":38,"nodeType":"48","messageId":"49","endLine":21,"endColumn":41},{"ruleId":"46","severity":1,"message":"50","line":26,"column":36,"nodeType":"48","messageId":"49","endLine":26,"endColumn":39},{"ruleId":"46","severity":1,"message":"51","line":9,"column":37,"nodeType":"48","messageId":"49","endLine":9,"endColumn":71},"@typescript-eslint/no-unused-vars","'ctx' is defined but never used.","Identifier","unusedVar","'req' is defined but never used.","'opts' is defined but never used."]
--------------------------------------------------------------------------------
/apps/nextjs/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | width: var(--max-width);
46 | max-width: 100%;
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 2rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: "";
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo,
108 | .thirteen {
109 | position: relative;
110 | }
111 |
112 | .thirteen {
113 | display: flex;
114 | justify-content: center;
115 | align-items: center;
116 | width: 75px;
117 | height: 75px;
118 | padding: 25px 10px;
119 | margin-left: 16px;
120 | transform: translateZ(0);
121 | border-radius: var(--border-radius);
122 | overflow: hidden;
123 | box-shadow: 0px 2px 8px -1px #0000001a;
124 | }
125 |
126 | .thirteen::before,
127 | .thirteen::after {
128 | content: "";
129 | position: absolute;
130 | z-index: -1;
131 | }
132 |
133 | /* Conic Gradient Animation */
134 | .thirteen::before {
135 | animation: 6s rotate linear infinite;
136 | width: 200%;
137 | height: 200%;
138 | background: var(--tile-border);
139 | }
140 |
141 | /* Inner Square */
142 | .thirteen::after {
143 | inset: 0;
144 | padding: 1px;
145 | border-radius: var(--border-radius);
146 | background: linear-gradient(
147 | to bottom right,
148 | rgba(var(--tile-start-rgb), 1),
149 | rgba(var(--tile-end-rgb), 1)
150 | );
151 | background-clip: content-box;
152 | }
153 |
154 | /* Enable hover only on non-touch devices */
155 | @media (hover: hover) and (pointer: fine) {
156 | .card:hover {
157 | background: rgba(var(--card-rgb), 0.1);
158 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
159 | }
160 |
161 | .card:hover span {
162 | transform: translateX(4px);
163 | }
164 | }
165 |
166 | @media (prefers-reduced-motion) {
167 | .thirteen::before {
168 | animation: none;
169 | }
170 |
171 | .card:hover span {
172 | transform: none;
173 | }
174 | }
175 |
176 | /* Mobile */
177 | @media (max-width: 700px) {
178 | .content {
179 | padding: 4rem;
180 | }
181 |
182 | .grid {
183 | grid-template-columns: 1fr;
184 | margin-bottom: 120px;
185 | max-width: 320px;
186 | text-align: center;
187 | }
188 |
189 | .card {
190 | padding: 1rem 2.5rem;
191 | }
192 |
193 | .card h2 {
194 | margin-bottom: 0.5rem;
195 | }
196 |
197 | .center {
198 | padding: 8rem 0 6rem;
199 | }
200 |
201 | .center::before {
202 | transform: none;
203 | height: 300px;
204 | }
205 |
206 | .description {
207 | font-size: 0.8rem;
208 | }
209 |
210 | .description a {
211 | padding: 1rem;
212 | }
213 |
214 | .description p,
215 | .description div {
216 | display: flex;
217 | justify-content: center;
218 | position: fixed;
219 | width: 100%;
220 | }
221 |
222 | .description p {
223 | align-items: center;
224 | inset: 0 0 auto;
225 | padding: 2rem 1rem 1.4rem;
226 | border-radius: 0;
227 | border: none;
228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
229 | background: linear-gradient(
230 | to bottom,
231 | rgba(var(--background-start-rgb), 1),
232 | rgba(var(--callout-rgb), 0.5)
233 | );
234 | background-clip: padding-box;
235 | backdrop-filter: blur(24px);
236 | }
237 |
238 | .description div {
239 | align-items: flex-end;
240 | pointer-events: none;
241 | inset: auto 0 0;
242 | padding: 2rem;
243 | height: 200px;
244 | background: linear-gradient(
245 | to bottom,
246 | transparent 0%,
247 | rgb(var(--background-end-rgb)) 40%
248 | );
249 | z-index: 1;
250 | }
251 | }
252 |
253 | /* Tablet and Smaller Desktop */
254 | @media (min-width: 701px) and (max-width: 1120px) {
255 | .grid {
256 | grid-template-columns: repeat(2, 50%);
257 | }
258 | }
259 |
260 | @media (prefers-color-scheme: dark) {
261 | .vercelLogo {
262 | filter: invert(1);
263 | }
264 |
265 | .logo,
266 | .thirteen img {
267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
268 | }
269 | }
270 |
271 | @keyframes rotate {
272 | from {
273 | transform: rotate(360deg);
274 | }
275 | to {
276 | transform: rotate(0deg);
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/apps/vite/src/components/AddUserForm/AddUserForm.tsx:
--------------------------------------------------------------------------------
1 | import { zodResolver } from "@hookform/resolvers/zod";
2 | import { useForm, UseFormProps } from "react-hook-form";
3 | import { z } from "zod";
4 | import { trpc } from "../../services/trpc";
5 | import toast from "react-hot-toast";
6 | import { addUserSchemaInput } from "@fullpower-stack/schema";
7 | import { countAtom } from "../../store/index";
8 | import { useAtom } from "jotai";
9 |
10 | // https://github.com/trpc/examples-kitchen-sink/blob/723cc6a74f03838748e517f292459d597b20447a/src/feature/react-hook-form/index.tsx
11 | function useZodForm(
12 | props: Omit, "resolver"> & {
13 | schema: TSchema;
14 | }
15 | ) {
16 | const form = useForm({
17 | ...props,
18 | resolver: zodResolver(props.schema, undefined),
19 | });
20 |
21 | return form;
22 | }
23 |
24 | const IncrementButton = () => {
25 | // Using reducer functions should be kept to small components as they are rerendered on every change
26 | const [, setCount] = useAtom(countAtom);
27 | return (
28 | setCount((count) => count + 1)}
31 | >
32 | Increment
33 |
34 | );
35 | };
36 |
37 | export const AddUserForm = () => {
38 | const methods = useZodForm({
39 | schema: addUserSchemaInput,
40 | defaultValues: {
41 | name: "",
42 | email: "",
43 | },
44 | });
45 |
46 | const utils = trpc.useContext();
47 |
48 | const mutation = trpc.createUser.useMutation({
49 | onSuccess: async () => {
50 | toast.success("User created successfully");
51 | await utils.list.invalidate();
52 | },
53 | onError: (err) => {
54 | toast.error(err.message);
55 | },
56 | });
57 |
58 | return (
59 |
60 |
61 |
62 |
63 |
69 |
74 |
75 |
76 |
77 | Add User
78 |
79 |
80 |
81 |
{
84 | await mutation.mutateAsync(values);
85 | methods.reset();
86 | })}
87 | >
88 |
89 |
119 |
120 |
121 |
125 |
126 |
132 |
137 |
138 |
139 | Save
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | );
148 | };
149 |
--------------------------------------------------------------------------------
/apps/vite/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "@tanstack/react-router";
2 | import React from "react";
3 | import { ROUTES } from "../router/routes";
4 | import { Fragment } from "react";
5 | import { Disclosure, Menu, Transition } from "@headlessui/react";
6 | import { Bars3Icon, BellIcon, XMarkIcon } from "@heroicons/react/24/outline";
7 |
8 | const classNames = (...classes: string[]) => {
9 | return classes.filter(Boolean).join(" ");
10 | };
11 |
12 | function Navbar() {
13 | return (
14 |
15 | {({ open }) => (
16 | <>
17 |
18 |
19 |
20 | {/* Mobile menu button*/}
21 |
22 | Open main menu
23 | {open ? (
24 |
25 | ) : (
26 |
27 | )}
28 |
29 |
30 |
31 |
32 |
37 |
42 |
43 |
44 |
45 | {Object.entries(ROUTES).map(([key, href]) => {
46 | return (
47 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
48 | // @ts-ignore
49 |
55 | {key}
56 |
57 | );
58 | })}
59 |
60 |
61 |
62 |
63 |
67 | View notifications
68 |
69 |
70 |
71 | {/* Profile dropdown */}
72 |
73 |
74 |
75 | Open user menu
76 |
81 |
82 |
83 |
92 |
93 |
94 | {({ active }) => (
95 |
102 | Your Profile
103 |
104 | )}
105 |
106 |
107 | {({ active }) => (
108 |
115 | Settings
116 |
117 | )}
118 |
119 |
120 | {({ active }) => (
121 |
128 | Sign out
129 |
130 | )}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | >
139 | )}
140 |
141 | );
142 | }
143 |
144 | export default Navbar;
145 |
--------------------------------------------------------------------------------
/apps/vite/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* tslint:disable */
3 |
4 | /**
5 | * Mock Service Worker (0.48.1).
6 | * @see https://github.com/mswjs/msw
7 | * - Please do NOT modify this file.
8 | * - Please do NOT serve this file on production.
9 | */
10 |
11 | const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'
12 | const activeClientIds = new Set()
13 |
14 | self.addEventListener('install', function () {
15 | self.skipWaiting()
16 | })
17 |
18 | self.addEventListener('activate', function (event) {
19 | event.waitUntil(self.clients.claim())
20 | })
21 |
22 | self.addEventListener('message', async function (event) {
23 | const clientId = event.source.id
24 |
25 | if (!clientId || !self.clients) {
26 | return
27 | }
28 |
29 | const client = await self.clients.get(clientId)
30 |
31 | if (!client) {
32 | return
33 | }
34 |
35 | const allClients = await self.clients.matchAll({
36 | type: 'window',
37 | })
38 |
39 | switch (event.data) {
40 | case 'KEEPALIVE_REQUEST': {
41 | sendToClient(client, {
42 | type: 'KEEPALIVE_RESPONSE',
43 | })
44 | break
45 | }
46 |
47 | case 'INTEGRITY_CHECK_REQUEST': {
48 | sendToClient(client, {
49 | type: 'INTEGRITY_CHECK_RESPONSE',
50 | payload: INTEGRITY_CHECKSUM,
51 | })
52 | break
53 | }
54 |
55 | case 'MOCK_ACTIVATE': {
56 | activeClientIds.add(clientId)
57 |
58 | sendToClient(client, {
59 | type: 'MOCKING_ENABLED',
60 | payload: true,
61 | })
62 | break
63 | }
64 |
65 | case 'MOCK_DEACTIVATE': {
66 | activeClientIds.delete(clientId)
67 | break
68 | }
69 |
70 | case 'CLIENT_CLOSED': {
71 | activeClientIds.delete(clientId)
72 |
73 | const remainingClients = allClients.filter((client) => {
74 | return client.id !== clientId
75 | })
76 |
77 | // Unregister itself when there are no more clients
78 | if (remainingClients.length === 0) {
79 | self.registration.unregister()
80 | }
81 |
82 | break
83 | }
84 | }
85 | })
86 |
87 | self.addEventListener('fetch', function (event) {
88 | const { request } = event
89 | const accept = request.headers.get('accept') || ''
90 |
91 | // Bypass server-sent events.
92 | if (accept.includes('text/event-stream')) {
93 | return
94 | }
95 |
96 | // Bypass navigation requests.
97 | if (request.mode === 'navigate') {
98 | return
99 | }
100 |
101 | // Opening the DevTools triggers the "only-if-cached" request
102 | // that cannot be handled by the worker. Bypass such requests.
103 | if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
104 | return
105 | }
106 |
107 | // Bypass all requests when there are no active clients.
108 | // Prevents the self-unregistered worked from handling requests
109 | // after it's been deleted (still remains active until the next reload).
110 | if (activeClientIds.size === 0) {
111 | return
112 | }
113 |
114 | // Generate unique request ID.
115 | const requestId = Math.random().toString(16).slice(2)
116 |
117 | event.respondWith(
118 | handleRequest(event, requestId).catch((error) => {
119 | if (error.name === 'NetworkError') {
120 | console.warn(
121 | '[MSW] Successfully emulated a network error for the "%s %s" request.',
122 | request.method,
123 | request.url,
124 | )
125 | return
126 | }
127 |
128 | // At this point, any exception indicates an issue with the original request/response.
129 | console.error(
130 | `\
131 | [MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
132 | request.method,
133 | request.url,
134 | `${error.name}: ${error.message}`,
135 | )
136 | }),
137 | )
138 | })
139 |
140 | async function handleRequest(event, requestId) {
141 | const client = await resolveMainClient(event)
142 | const response = await getResponse(event, client, requestId)
143 |
144 | // Send back the response clone for the "response:*" life-cycle events.
145 | // Ensure MSW is active and ready to handle the message, otherwise
146 | // this message will pend indefinitely.
147 | if (client && activeClientIds.has(client.id)) {
148 | ;(async function () {
149 | const clonedResponse = response.clone()
150 | sendToClient(client, {
151 | type: 'RESPONSE',
152 | payload: {
153 | requestId,
154 | type: clonedResponse.type,
155 | ok: clonedResponse.ok,
156 | status: clonedResponse.status,
157 | statusText: clonedResponse.statusText,
158 | body:
159 | clonedResponse.body === null ? null : await clonedResponse.text(),
160 | headers: Object.fromEntries(clonedResponse.headers.entries()),
161 | redirected: clonedResponse.redirected,
162 | },
163 | })
164 | })()
165 | }
166 |
167 | return response
168 | }
169 |
170 | // Resolve the main client for the given event.
171 | // Client that issues a request doesn't necessarily equal the client
172 | // that registered the worker. It's with the latter the worker should
173 | // communicate with during the response resolving phase.
174 | async function resolveMainClient(event) {
175 | const client = await self.clients.get(event.clientId)
176 |
177 | if (client?.frameType === 'top-level') {
178 | return client
179 | }
180 |
181 | const allClients = await self.clients.matchAll({
182 | type: 'window',
183 | })
184 |
185 | return allClients
186 | .filter((client) => {
187 | // Get only those clients that are currently visible.
188 | return client.visibilityState === 'visible'
189 | })
190 | .find((client) => {
191 | // Find the client ID that's recorded in the
192 | // set of clients that have registered the worker.
193 | return activeClientIds.has(client.id)
194 | })
195 | }
196 |
197 | async function getResponse(event, client, requestId) {
198 | const { request } = event
199 | const clonedRequest = request.clone()
200 |
201 | function passthrough() {
202 | // Clone the request because it might've been already used
203 | // (i.e. its body has been read and sent to the client).
204 | const headers = Object.fromEntries(clonedRequest.headers.entries())
205 |
206 | // Remove MSW-specific request headers so the bypassed requests
207 | // comply with the server's CORS preflight check.
208 | // Operate with the headers as an object because request "Headers"
209 | // are immutable.
210 | delete headers['x-msw-bypass']
211 |
212 | return fetch(clonedRequest, { headers })
213 | }
214 |
215 | // Bypass mocking when the client is not active.
216 | if (!client) {
217 | return passthrough()
218 | }
219 |
220 | // Bypass initial page load requests (i.e. static assets).
221 | // The absence of the immediate/parent client in the map of the active clients
222 | // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
223 | // and is not ready to handle requests.
224 | if (!activeClientIds.has(client.id)) {
225 | return passthrough()
226 | }
227 |
228 | // Bypass requests with the explicit bypass header.
229 | // Such requests can be issued by "ctx.fetch()".
230 | if (request.headers.get('x-msw-bypass') === 'true') {
231 | return passthrough()
232 | }
233 |
234 | // Notify the client that a request has been intercepted.
235 | const clientMessage = await sendToClient(client, {
236 | type: 'REQUEST',
237 | payload: {
238 | id: requestId,
239 | url: request.url,
240 | method: request.method,
241 | headers: Object.fromEntries(request.headers.entries()),
242 | cache: request.cache,
243 | mode: request.mode,
244 | credentials: request.credentials,
245 | destination: request.destination,
246 | integrity: request.integrity,
247 | redirect: request.redirect,
248 | referrer: request.referrer,
249 | referrerPolicy: request.referrerPolicy,
250 | body: await request.text(),
251 | bodyUsed: request.bodyUsed,
252 | keepalive: request.keepalive,
253 | },
254 | })
255 |
256 | switch (clientMessage.type) {
257 | case 'MOCK_RESPONSE': {
258 | return respondWithMock(clientMessage.data)
259 | }
260 |
261 | case 'MOCK_NOT_FOUND': {
262 | return passthrough()
263 | }
264 |
265 | case 'NETWORK_ERROR': {
266 | const { name, message } = clientMessage.data
267 | const networkError = new Error(message)
268 | networkError.name = name
269 |
270 | // Rejecting a "respondWith" promise emulates a network error.
271 | throw networkError
272 | }
273 | }
274 |
275 | return passthrough()
276 | }
277 |
278 | function sendToClient(client, message) {
279 | return new Promise((resolve, reject) => {
280 | const channel = new MessageChannel()
281 |
282 | channel.port1.onmessage = (event) => {
283 | if (event.data && event.data.error) {
284 | return reject(event.data.error)
285 | }
286 |
287 | resolve(event.data)
288 | }
289 |
290 | client.postMessage(message, [channel.port2])
291 | })
292 | }
293 |
294 | function sleep(timeMs) {
295 | return new Promise((resolve) => {
296 | setTimeout(resolve, timeMs)
297 | })
298 | }
299 |
300 | async function respondWithMock(response) {
301 | await sleep(response.delay)
302 | return new Response(response.body, response)
303 | }
304 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | an opinionated stack for building full power web applications
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 | ### Introduction
25 |
26 | This is a monorepo containing a set of packages that can be used to build full power web applications. It is an opinionated stack that is based on the following principles:
27 |
28 | - DX (Developer Experience)
29 | - Speed of iteration
30 |
31 | ---
32 |
33 | ## Supported Frameworks
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | all apps are built with the same set of packages and tools (see [workspace packages](./packages.json)
45 | and preconfigured to pick changes from shared packages
46 |
47 | ---
48 |
49 | # [Demo](https://codesandbox.io/p/github/kaminskypavel/fullpower-stack/master?file=%2FREADME.md)
50 |
51 | watch the demo app in action on [CodeSandBox](https://codesandbox.io/p/github/kaminskypavel/fullpower-stack/master?file=%2FREADME.md)
52 |
53 | ### Dev stack
54 |
55 |
261 |
262 | # Install
263 |
264 | ```
265 | pnpx degit https://github.com/kaminskypavel/fullpower-stack my-app
266 | ```
267 |
268 | install dependeciens (we use pnpm)
269 |
270 | ```
271 | pnpm install
272 | ```
273 |
274 | # Docker
275 |
276 | ```
277 | pnpm docker:up
278 | ```
279 |
280 | this will start all docker containers
281 |
282 | - Backend-Server -
283 | - Vite -
284 | - Nextjs -
285 | - Astro -
286 | - Remix.run -
287 |
288 | ### Contributors
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 | Pavel 'PK' Kaminsky
297 |
298 |
299 |
300 |
301 |
302 |
303 | vithano
304 |
305 |
306 |
--------------------------------------------------------------------------------
/apps/nextjs/__tests__/e2e/demo-todo-app.spec.ts:
--------------------------------------------------------------------------------
1 | // import { test, expect, type Page } from "@playwright/test";
2 |
3 | // test.beforeEach(async ({ page }) => {
4 | // await page.goto("https://demo.playwright.dev/todomvc");
5 | // });
6 |
7 | // const TODO_ITEMS = [
8 | // "buy some cheese",
9 | // "feed the cat",
10 | // "book a doctors appointment",
11 | // ];
12 |
13 | // test.describe.skip("e2e test suit", () => {
14 | // test.describe("New Todo", () => {
15 | // test("should allow me to add todo items", async ({ page }) => {
16 | // // create a new todo locator
17 | // const newTodo = page.getByPlaceholder("What needs to be done?");
18 |
19 | // // Create 1st todo.
20 | // await newTodo.fill(TODO_ITEMS[0]);
21 | // await newTodo.press("Enter");
22 |
23 | // // Make sure the list only has one todo item.
24 | // await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]);
25 |
26 | // // Create 2nd todo.
27 | // await newTodo.fill(TODO_ITEMS[1]);
28 | // await newTodo.press("Enter");
29 |
30 | // // Make sure the list now has two todo items.
31 | // await expect(page.getByTestId("todo-title")).toHaveText([
32 | // TODO_ITEMS[0],
33 | // TODO_ITEMS[1],
34 | // ]);
35 |
36 | // await checkNumberOfTodosInLocalStorage(page, 2);
37 | // });
38 |
39 | // test("should clear text input field when an item is added", async ({
40 | // page,
41 | // }) => {
42 | // // create a new todo locator
43 | // const newTodo = page.getByPlaceholder("What needs to be done?");
44 |
45 | // // Create one todo item.
46 | // await newTodo.fill(TODO_ITEMS[0]);
47 | // await newTodo.press("Enter");
48 |
49 | // // Check that input is empty.
50 | // await expect(newTodo).toBeEmpty();
51 | // await checkNumberOfTodosInLocalStorage(page, 1);
52 | // });
53 |
54 | // test("should append new items to the bottom of the list", async ({
55 | // page,
56 | // }) => {
57 | // // Create 3 items.
58 | // await createDefaultTodos(page);
59 |
60 | // // create a todo count locator
61 | // const todoCount = page.getByTestId("todo-count");
62 |
63 | // // Check test using different methods.
64 | // await expect(page.getByText("3 items left")).toBeVisible();
65 | // await expect(todoCount).toHaveText("3 items left");
66 | // await expect(todoCount).toContainText("3");
67 | // await expect(todoCount).toHaveText(/3/);
68 |
69 | // // Check all items in one call.
70 | // await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS);
71 | // await checkNumberOfTodosInLocalStorage(page, 3);
72 | // });
73 | // });
74 |
75 | // test.describe("Mark all as completed", () => {
76 | // test.beforeEach(async ({ page }) => {
77 | // await createDefaultTodos(page);
78 | // await checkNumberOfTodosInLocalStorage(page, 3);
79 | // });
80 |
81 | // test.afterEach(async ({ page }) => {
82 | // await checkNumberOfTodosInLocalStorage(page, 3);
83 | // });
84 |
85 | // test("should allow me to mark all items as completed", async ({ page }) => {
86 | // // Complete all todos.
87 | // await page.getByLabel("Mark all as complete").check();
88 |
89 | // // Ensure all todos have 'completed' class.
90 | // await expect(page.getByTestId("todo-item")).toHaveClass([
91 | // "completed",
92 | // "completed",
93 | // "completed",
94 | // ]);
95 | // await checkNumberOfCompletedTodosInLocalStorage(page, 3);
96 | // });
97 |
98 | // test("should allow me to clear the complete state of all items", async ({
99 | // page,
100 | // }) => {
101 | // const toggleAll = page.getByLabel("Mark all as complete");
102 | // // Check and then immediately uncheck.
103 | // await toggleAll.check();
104 | // await toggleAll.uncheck();
105 |
106 | // // Should be no completed classes.
107 | // await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]);
108 | // });
109 |
110 | // test("complete all checkbox should update state when items are completed / cleared", async ({
111 | // page,
112 | // }) => {
113 | // const toggleAll = page.getByLabel("Mark all as complete");
114 | // await toggleAll.check();
115 | // await expect(toggleAll).toBeChecked();
116 | // await checkNumberOfCompletedTodosInLocalStorage(page, 3);
117 |
118 | // // Uncheck first todo.
119 | // const firstTodo = page.getByTestId("todo-item").nth(0);
120 | // await firstTodo.getByRole("checkbox").uncheck();
121 |
122 | // // Reuse toggleAll locator and make sure its not checked.
123 | // await expect(toggleAll).not.toBeChecked();
124 |
125 | // await firstTodo.getByRole("checkbox").check();
126 | // await checkNumberOfCompletedTodosInLocalStorage(page, 3);
127 |
128 | // // Assert the toggle all is checked again.
129 | // await expect(toggleAll).toBeChecked();
130 | // });
131 | // });
132 |
133 | // test.describe("Item", () => {
134 | // test("should allow me to mark items as complete", async ({ page }) => {
135 | // // create a new todo locator
136 | // const newTodo = page.getByPlaceholder("What needs to be done?");
137 |
138 | // // Create two items.
139 | // for (const item of TODO_ITEMS.slice(0, 2)) {
140 | // await newTodo.fill(item);
141 | // await newTodo.press("Enter");
142 | // }
143 |
144 | // // Check first item.
145 | // const firstTodo = page.getByTestId("todo-item").nth(0);
146 | // await firstTodo.getByRole("checkbox").check();
147 | // await expect(firstTodo).toHaveClass("completed");
148 |
149 | // // Check second item.
150 | // const secondTodo = page.getByTestId("todo-item").nth(1);
151 | // await expect(secondTodo).not.toHaveClass("completed");
152 | // await secondTodo.getByRole("checkbox").check();
153 |
154 | // // Assert completed class.
155 | // await expect(firstTodo).toHaveClass("completed");
156 | // await expect(secondTodo).toHaveClass("completed");
157 | // });
158 |
159 | // test("should allow me to un-mark items as complete", async ({ page }) => {
160 | // // create a new todo locator
161 | // const newTodo = page.getByPlaceholder("What needs to be done?");
162 |
163 | // // Create two items.
164 | // for (const item of TODO_ITEMS.slice(0, 2)) {
165 | // await newTodo.fill(item);
166 | // await newTodo.press("Enter");
167 | // }
168 |
169 | // const firstTodo = page.getByTestId("todo-item").nth(0);
170 | // const secondTodo = page.getByTestId("todo-item").nth(1);
171 | // const firstTodoCheckbox = firstTodo.getByRole("checkbox");
172 |
173 | // await firstTodoCheckbox.check();
174 | // await expect(firstTodo).toHaveClass("completed");
175 | // await expect(secondTodo).not.toHaveClass("completed");
176 | // await checkNumberOfCompletedTodosInLocalStorage(page, 1);
177 |
178 | // await firstTodoCheckbox.uncheck();
179 | // await expect(firstTodo).not.toHaveClass("completed");
180 | // await expect(secondTodo).not.toHaveClass("completed");
181 | // await checkNumberOfCompletedTodosInLocalStorage(page, 0);
182 | // });
183 |
184 | // test("should allow me to edit an item", async ({ page }) => {
185 | // await createDefaultTodos(page);
186 |
187 | // const todoItems = page.getByTestId("todo-item");
188 | // const secondTodo = todoItems.nth(1);
189 | // await secondTodo.dblclick();
190 | // await expect(
191 | // secondTodo.getByRole("textbox", { name: "Edit" })
192 | // ).toHaveValue(TODO_ITEMS[1]);
193 | // await secondTodo
194 | // .getByRole("textbox", { name: "Edit" })
195 | // .fill("buy some sausages");
196 | // await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter");
197 |
198 | // // Explicitly assert the new text value.
199 | // await expect(todoItems).toHaveText([
200 | // TODO_ITEMS[0],
201 | // "buy some sausages",
202 | // TODO_ITEMS[2],
203 | // ]);
204 | // await checkTodosInLocalStorage(page, "buy some sausages");
205 | // });
206 | // });
207 |
208 | // test.describe("Editing", () => {
209 | // test.beforeEach(async ({ page }) => {
210 | // await createDefaultTodos(page);
211 | // await checkNumberOfTodosInLocalStorage(page, 3);
212 | // });
213 |
214 | // test("should hide other controls when editing", async ({ page }) => {
215 | // const todoItem = page.getByTestId("todo-item").nth(1);
216 | // await todoItem.dblclick();
217 | // await expect(todoItem.getByRole("checkbox")).not.toBeVisible();
218 | // await expect(
219 | // todoItem.locator("label", {
220 | // hasText: TODO_ITEMS[1],
221 | // })
222 | // ).not.toBeVisible();
223 | // await checkNumberOfTodosInLocalStorage(page, 3);
224 | // });
225 |
226 | // test("should save edits on blur", async ({ page }) => {
227 | // const todoItems = page.getByTestId("todo-item");
228 | // await todoItems.nth(1).dblclick();
229 | // await todoItems
230 | // .nth(1)
231 | // .getByRole("textbox", { name: "Edit" })
232 | // .fill("buy some sausages");
233 | // await todoItems
234 | // .nth(1)
235 | // .getByRole("textbox", { name: "Edit" })
236 | // .dispatchEvent("blur");
237 |
238 | // await expect(todoItems).toHaveText([
239 | // TODO_ITEMS[0],
240 | // "buy some sausages",
241 | // TODO_ITEMS[2],
242 | // ]);
243 | // await checkTodosInLocalStorage(page, "buy some sausages");
244 | // });
245 |
246 | // test("should trim entered text", async ({ page }) => {
247 | // const todoItems = page.getByTestId("todo-item");
248 | // await todoItems.nth(1).dblclick();
249 | // await todoItems
250 | // .nth(1)
251 | // .getByRole("textbox", { name: "Edit" })
252 | // .fill(" buy some sausages ");
253 | // await todoItems
254 | // .nth(1)
255 | // .getByRole("textbox", { name: "Edit" })
256 | // .press("Enter");
257 |
258 | // await expect(todoItems).toHaveText([
259 | // TODO_ITEMS[0],
260 | // "buy some sausages",
261 | // TODO_ITEMS[2],
262 | // ]);
263 | // await checkTodosInLocalStorage(page, "buy some sausages");
264 | // });
265 |
266 | // test("should remove the item if an empty text string was entered", async ({
267 | // page,
268 | // }) => {
269 | // const todoItems = page.getByTestId("todo-item");
270 | // await todoItems.nth(1).dblclick();
271 | // await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("");
272 | // await todoItems
273 | // .nth(1)
274 | // .getByRole("textbox", { name: "Edit" })
275 | // .press("Enter");
276 |
277 | // await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
278 | // });
279 |
280 | // test("should cancel edits on escape", async ({ page }) => {
281 | // const todoItems = page.getByTestId("todo-item");
282 | // await todoItems.nth(1).dblclick();
283 | // await todoItems
284 | // .nth(1)
285 | // .getByRole("textbox", { name: "Edit" })
286 | // .fill("buy some sausages");
287 | // await todoItems
288 | // .nth(1)
289 | // .getByRole("textbox", { name: "Edit" })
290 | // .press("Escape");
291 | // await expect(todoItems).toHaveText(TODO_ITEMS);
292 | // });
293 | // });
294 |
295 | // test.describe("Counter", () => {
296 | // test("should display the current number of todo items", async ({
297 | // page,
298 | // }) => {
299 | // // create a new todo locator
300 | // const newTodo = page.getByPlaceholder("What needs to be done?");
301 |
302 | // // create a todo count locator
303 | // const todoCount = page.getByTestId("todo-count");
304 |
305 | // await newTodo.fill(TODO_ITEMS[0]);
306 | // await newTodo.press("Enter");
307 |
308 | // await expect(todoCount).toContainText("1");
309 |
310 | // await newTodo.fill(TODO_ITEMS[1]);
311 | // await newTodo.press("Enter");
312 | // await expect(todoCount).toContainText("2");
313 |
314 | // await checkNumberOfTodosInLocalStorage(page, 2);
315 | // });
316 | // });
317 |
318 | // test.describe("Clear completed button", () => {
319 | // test.beforeEach(async ({ page }) => {
320 | // await createDefaultTodos(page);
321 | // });
322 |
323 | // test("should display the correct text", async ({ page }) => {
324 | // await page.locator(".todo-list li .toggle").first().check();
325 | // await expect(
326 | // page.getByRole("button", { name: "Clear completed" })
327 | // ).toBeVisible();
328 | // });
329 |
330 | // test("should remove completed items when clicked", async ({ page }) => {
331 | // const todoItems = page.getByTestId("todo-item");
332 | // await todoItems.nth(1).getByRole("checkbox").check();
333 | // await page.getByRole("button", { name: "Clear completed" }).click();
334 | // await expect(todoItems).toHaveCount(2);
335 | // await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
336 | // });
337 |
338 | // test("should be hidden when there are no items that are completed", async ({
339 | // page,
340 | // }) => {
341 | // await page.locator(".todo-list li .toggle").first().check();
342 | // await page.getByRole("button", { name: "Clear completed" }).click();
343 | // await expect(
344 | // page.getByRole("button", { name: "Clear completed" })
345 | // ).toBeHidden();
346 | // });
347 | // });
348 |
349 | // test.describe("Persistence", () => {
350 | // test("should persist its data", async ({ page }) => {
351 | // // create a new todo locator
352 | // const newTodo = page.getByPlaceholder("What needs to be done?");
353 |
354 | // for (const item of TODO_ITEMS.slice(0, 2)) {
355 | // await newTodo.fill(item);
356 | // await newTodo.press("Enter");
357 | // }
358 |
359 | // const todoItems = page.getByTestId("todo-item");
360 | // const firstTodoCheck = todoItems.nth(0).getByRole("checkbox");
361 | // await firstTodoCheck.check();
362 | // await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
363 | // await expect(firstTodoCheck).toBeChecked();
364 | // await expect(todoItems).toHaveClass(["completed", ""]);
365 |
366 | // // Ensure there is 1 completed item.
367 | // await checkNumberOfCompletedTodosInLocalStorage(page, 1);
368 |
369 | // // Now reload.
370 | // await page.reload();
371 | // await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
372 | // await expect(firstTodoCheck).toBeChecked();
373 | // await expect(todoItems).toHaveClass(["completed", ""]);
374 | // });
375 | // });
376 |
377 | // test.describe("Routing", () => {
378 | // test.beforeEach(async ({ page }) => {
379 | // await createDefaultTodos(page);
380 | // // make sure the app had a chance to save updated todos in storage
381 | // // before navigating to a new view, otherwise the items can get lost :(
382 | // // in some frameworks like Durandal
383 | // await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
384 | // });
385 |
386 | // test("should allow me to display active items", async ({ page }) => {
387 | // const todoItem = page.getByTestId("todo-item");
388 | // await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
389 |
390 | // await checkNumberOfCompletedTodosInLocalStorage(page, 1);
391 | // await page.getByRole("link", { name: "Active" }).click();
392 | // await expect(todoItem).toHaveCount(2);
393 | // await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
394 | // });
395 |
396 | // test("should respect the back button", async ({ page }) => {
397 | // const todoItem = page.getByTestId("todo-item");
398 | // await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
399 |
400 | // await checkNumberOfCompletedTodosInLocalStorage(page, 1);
401 |
402 | // await test.step("Showing all items", async () => {
403 | // await page.getByRole("link", { name: "All" }).click();
404 | // await expect(todoItem).toHaveCount(3);
405 | // });
406 |
407 | // await test.step("Showing active items", async () => {
408 | // await page.getByRole("link", { name: "Active" }).click();
409 | // });
410 |
411 | // await test.step("Showing completed items", async () => {
412 | // await page.getByRole("link", { name: "Completed" }).click();
413 | // });
414 |
415 | // await expect(todoItem).toHaveCount(1);
416 | // await page.goBack();
417 | // await expect(todoItem).toHaveCount(2);
418 | // await page.goBack();
419 | // await expect(todoItem).toHaveCount(3);
420 | // });
421 |
422 | // test("should allow me to display completed items", async ({ page }) => {
423 | // await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
424 | // await checkNumberOfCompletedTodosInLocalStorage(page, 1);
425 | // await page.getByRole("link", { name: "Completed" }).click();
426 | // await expect(page.getByTestId("todo-item")).toHaveCount(1);
427 | // });
428 |
429 | // test("should allow me to display all items", async ({ page }) => {
430 | // await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
431 | // await checkNumberOfCompletedTodosInLocalStorage(page, 1);
432 | // await page.getByRole("link", { name: "Active" }).click();
433 | // await page.getByRole("link", { name: "Completed" }).click();
434 | // await page.getByRole("link", { name: "All" }).click();
435 | // await expect(page.getByTestId("todo-item")).toHaveCount(3);
436 | // });
437 |
438 | // test("should highlight the currently applied filter", async ({ page }) => {
439 | // await expect(page.getByRole("link", { name: "All" })).toHaveClass(
440 | // "selected"
441 | // );
442 |
443 | // //create locators for active and completed links
444 | // const activeLink = page.getByRole("link", { name: "Active" });
445 | // const completedLink = page.getByRole("link", { name: "Completed" });
446 | // await activeLink.click();
447 |
448 | // // Page change - active items.
449 | // await expect(activeLink).toHaveClass("selected");
450 | // await completedLink.click();
451 |
452 | // // Page change - completed items.
453 | // await expect(completedLink).toHaveClass("selected");
454 | // });
455 | // });
456 | // });
457 |
458 | // async function createDefaultTodos(page) {
459 | // // create a new todo locator
460 | // const newTodo = page.getByPlaceholder("What needs to be done?");
461 |
462 | // for (const item of TODO_ITEMS) {
463 | // await newTodo.fill(item);
464 | // await newTodo.press("Enter");
465 | // }
466 | // }
467 |
468 | // async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
469 | // return await page.waitForFunction((e) => {
470 | // return JSON.parse(localStorage["react-todos"]).length === e;
471 | // }, expected);
472 | // }
473 |
474 | // async function checkNumberOfCompletedTodosInLocalStorage(
475 | // page: Page,
476 | // expected: number
477 | // ) {
478 | // return await page.waitForFunction((e) => {
479 | // return (
480 | // JSON.parse(localStorage["react-todos"]).filter(
481 | // (todo: any) => todo.completed
482 | // ).length === e
483 | // );
484 | // }, expected);
485 | // }
486 |
487 | // async function checkTodosInLocalStorage(page: Page, title: string) {
488 | // return await page.waitForFunction((t) => {
489 | // return JSON.parse(localStorage["react-todos"])
490 | // .map((todo: any) => todo.title)
491 | // .includes(t);
492 | // }, title);
493 | // }
494 |
--------------------------------------------------------------------------------