Prechádzať zdrojové kódy

chore(create-pyth-app): start of simple utility to do this

benduran 1 týždeň pred
rodič
commit
eb022f93d6
33 zmenil súbory, kde vykonal 1008 pridanie a 214 odobranie
  1. 1 0
      package.json
  2. 26 0
      packages/create-pyth-app/.gitignore
  3. 12 0
      packages/create-pyth-app/.prettierignore
  4. 6 0
      packages/create-pyth-app/bin/create-pyth-app
  5. 1 0
      packages/create-pyth-app/eslint.config.js
  6. 36 0
      packages/create-pyth-app/package.json
  7. 119 0
      packages/create-pyth-app/src/create-pyth-app.ts
  8. 41 0
      packages/create-pyth-app/src/get-available-folders.ts
  9. 33 0
      packages/create-pyth-app/src/logger.ts
  10. 9 0
      packages/create-pyth-app/src/templates/web-app/eslint.config.js
  11. 55 0
      packages/create-pyth-app/src/templates/web-app/next.config.js
  12. 58 0
      packages/create-pyth-app/src/templates/web-app/package.json
  13. 6 0
      packages/create-pyth-app/src/templates/web-app/postcss.config.js
  14. 9 0
      packages/create-pyth-app/src/templates/web-app/prettier.config.js
  15. BIN
      packages/create-pyth-app/src/templates/web-app/public/android-chrome-192x192.png
  16. BIN
      packages/create-pyth-app/src/templates/web-app/public/android-chrome-512x512.png
  17. BIN
      packages/create-pyth-app/src/templates/web-app/public/apple-touch-icon.png
  18. BIN
      packages/create-pyth-app/src/templates/web-app/public/favicon-16x16.png
  19. BIN
      packages/create-pyth-app/src/templates/web-app/public/favicon-32x32.png
  20. BIN
      packages/create-pyth-app/src/templates/web-app/public/favicon-light.ico
  21. BIN
      packages/create-pyth-app/src/templates/web-app/public/favicon.ico
  22. 11 0
      packages/create-pyth-app/src/templates/web-app/router.d.ts
  23. 26 0
      packages/create-pyth-app/src/templates/web-app/src/app/globals.scss
  24. 34 0
      packages/create-pyth-app/src/templates/web-app/src/app/layout.tsx
  25. 103 0
      packages/create-pyth-app/src/templates/web-app/src/app/page.tsx
  26. 6 0
      packages/create-pyth-app/src/templates/web-app/svg.d.ts
  27. 52 0
      packages/create-pyth-app/src/templates/web-app/tailwind.config.ts
  28. 36 0
      packages/create-pyth-app/src/templates/web-app/turbo.json
  29. 4 0
      packages/create-pyth-app/src/templates/web-app/vercel.json
  30. 20 0
      packages/create-pyth-app/src/types.ts
  31. 7 0
      packages/create-pyth-app/tsconfig.json
  32. 290 214
      pnpm-lock.yaml
  33. 7 0
      pnpm-workspace.yaml

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
   },
   "devDependencies": {
     "@better-builds/ts-duality": "catalog:",
+    "@pythnetwork/create-pyth-app": "workspace:",
     "@cprussin/tsconfig": "catalog:",
     "@cprussin/prettier-config": "catalog:",
     "prettier": "catalog:",

+ 26 - 0
packages/create-pyth-app/.gitignore

@@ -0,0 +1,26 @@
+# Coverage directory used by tools like istanbul
+coverage
+
+# Dependency directories
+node_modules/
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# dotenv environment variables file
+.env
+
+# Build directory
+dist/
+lib/
+
+tsconfig.tsbuildinfo
+
+# we want to keep binfiles here
+!bin/

+ 12 - 0
packages/create-pyth-app/.prettierignore

@@ -0,0 +1,12 @@
+.vscode/
+coverage/
+dist/
+doc/
+doc*/
+node_modules/
+dist/
+lib/
+build/
+node_modules/
+package.json
+tsconfig*.json

+ 6 - 0
packages/create-pyth-app/bin/create-pyth-app

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+THISDIR="$(dirname $0)"
+THISPKGDIR="$(realpath $THISDIR/..)"
+
+pnpm --dir "$THISPKGDIR" start

+ 1 - 0
packages/create-pyth-app/eslint.config.js

@@ -0,0 +1 @@
+export { base as default } from "@cprussin/eslint-config";

+ 36 - 0
packages/create-pyth-app/package.json

@@ -0,0 +1,36 @@
+{
+  "private": true,
+  "name": "@pythnetwork/create-pyth-app",
+  "description": "bootstrapper to quickly create a best-practices TypeScript library or application",
+  "version": "0.0.0",
+  "type": "module",
+  "bin": {
+    "create-pyth-app": "./bin/create-pyth-app"
+  },
+  "engines": {
+    "pnpm": ">=10.19.0",
+    "node": ">=22.14.0"
+  },
+  "files": [
+    "bin/**",
+    "src/**"
+  ],
+  "scripts": {
+    "start": "tsx ./src/create-pyth-app.ts",
+    "test:types": "tsc --noEmit"
+  },
+  "dependencies": {
+    "app-root-path": "catalog:",
+    "chalk": "catalog:",
+    "fs-extra": "catalog:",
+    "prompts": "catalog:",
+    "tsx": "catalog:"
+  },
+  "devDependencies": {
+    "@cprussin/tsconfig": "catalog:",
+    "@cprussin/eslint-config": "catalog:",
+    "@types/fs-extra": "catalog:",
+    "@types/prompts": "catalog:",
+    "eslint": "catalog:"
+  }
+}

+ 119 - 0
packages/create-pyth-app/src/create-pyth-app.ts

@@ -0,0 +1,119 @@
+// this rule is absolutely broken for the typings the prompts library
+// provides, so we need to hard-disable it for all usages of prompts
+
+import os from "node:os";
+import path from "node:path";
+
+import chalk from "chalk";
+import fs from "fs-extra";
+import prompts from "prompts";
+
+import { getAvailableFolders } from "./get-available-folders.js";
+import { Logger } from "./logger.js";
+import type {
+  CreatePythAppResponses,
+  InProgressCreatePythAppResponses,
+} from "./types.js";
+import { CUSTOM_FOLDER_CHOICE, PACKAGE_PREFIX, PackageType } from "./types.js";
+
+async function createPythApp() {
+  const cwd = process.cwd();
+
+  const {
+    confirm,
+    customFolderPath,
+    description,
+    folder,
+    packageName,
+    packageType,
+  } = (await prompts([
+    {
+      choices: Object.values(PackageType).map((val) => ({
+        title: val,
+        value: val,
+      })),
+      message: "Which type of package do you want to create?",
+      name: "packageType",
+      type: "select",
+    },
+    {
+      format: (val: string) => `${PACKAGE_PREFIX}${val}`,
+      message: (_, responses: InProgressCreatePythAppResponses) =>
+        `Enter the name for your ${responses.packageType ?? ""} package. ${chalk.magenta(PACKAGE_PREFIX)}`,
+      name: "packageName",
+      type: "text",
+      validate: (name: string) => {
+        const proposedName = `${PACKAGE_PREFIX}${name}`;
+        const pjsonNameRegexp = /^@pythnetwork\/(\w)(\w|\d|_|-)+$/;
+        return (
+          pjsonNameRegexp.test(proposedName) ||
+          "Please enter a valid package name (you do not need to add @pythnetwork/ as a prefix, it will be added automatically)"
+        );
+      },
+    },
+    {
+      message: "Enter a brief, friendly description for your package",
+      name: "description",
+      type: "text",
+    },
+    {
+      choices: (_, { packageType }: InProgressCreatePythAppResponses) =>
+        [
+          {
+            title: "** let me enter my own path **",
+            value: CUSTOM_FOLDER_CHOICE,
+          },
+          ...getAvailableFolders().map((val) => ({
+            title: val,
+            value: val,
+          })),
+        ].filter(
+          ({ value: relPath }) =>
+            packageType !== PackageType.WEBAPP && !relPath.startsWith("apps"),
+        ),
+      message: "Where do you want your package to live?",
+      name: "folder",
+      type: (_, { packageType }: InProgressCreatePythAppResponses) =>
+        packageType === PackageType.WEBAPP ? false : "select",
+    },
+    {
+      message:
+        "Enter the relative path to the folder where you would like to create your package",
+      name: "customFolderPath",
+      type: (_, { folder }: InProgressCreatePythAppResponses) =>
+        folder === CUSTOM_FOLDER_CHOICE ? "text" : false,
+    },
+    {
+      message: (
+        _,
+        {
+          customFolderPath,
+          folder,
+          packageName,
+          packageType,
+        }: InProgressCreatePythAppResponses,
+      ) => {
+        let msg = `Please confirm your choices:${os.EOL}`;
+        msg += `Creating a ${chalk.magenta(packageType)} package, named ${chalk.magenta(packageName)}, in ${chalk.magenta(customFolderPath ?? folder)}.${os.EOL}`;
+        msg += "Look good?";
+
+        return msg;
+      },
+      name: "confirm",
+      type: "confirm",
+    },
+  ])) as CreatePythAppResponses;
+
+  if (!confirm) {
+    Logger.warn("oops, you did not confirm your choices.");
+    return;
+  }
+
+  const relDest = customFolderPath ?? folder;
+  const absDest = path.join(cwd, relDest);
+
+  Logger.info("ensuring", relDest, "exists");
+  await fs.ensureDir(absDest);
+}
+
+await createPythApp();

+ 41 - 0
packages/create-pyth-app/src/get-available-folders.ts

@@ -0,0 +1,41 @@
+import { execSync } from "node:child_process";
+import path from "node:path";
+
+import appRootPath from "app-root-path";
+
+import { Logger } from "./logger.js";
+
+type PNPMPackageInfo = {
+  name: string;
+  path: string;
+  private: boolean;
+  version: string;
+};
+
+const repoRoot = appRootPath.toString();
+
+/**
+ * scans the entire pythcrosschain repo for all of the folders
+ * that have package.json files and returns the unique list of all parent
+ * directories for those, as a way to present the folder choice to the user.
+ */
+export function getAvailableFolders() {
+  const allPackages = JSON.parse(
+    execSync("pnpm list --recursive --depth -1 --json", {
+      cwd: repoRoot,
+      stdio: "pipe",
+    }).toString("utf8"),
+  ) as PNPMPackageInfo[];
+
+  return [
+    ...new Set(
+      allPackages
+        .filter((info) => info.path !== repoRoot)
+        .map((info) => {
+          const sPath = info.path.split(path.sep);
+          return path.relative(repoRoot, sPath.slice(0, -1).join(path.sep));
+        })
+        .filter(Boolean),
+    ),
+  ];
+}

+ 33 - 0
packages/create-pyth-app/src/logger.ts

@@ -0,0 +1,33 @@
+/* eslint-disable no-console */
+// eslint-disable-next-line unicorn/import-style
+import type { ColorName } from "chalk";
+import chalk from "chalk";
+
+type LogLevel = "error" | "info" | "warn";
+
+export const Logger = {
+  /**
+   * Prints a message to the console in whatever color your heart desires ❤️
+   */
+  colorful(color: ColorName, level: LogLevel, ...msg: unknown[]) {
+    console[level](chalk[color](...msg));
+  },
+  /**
+   * Logs an error message
+   */
+  error(...msg: unknown[]) {
+    console.error(chalk.red(...msg));
+  },
+  /**
+   * Logs an info message
+   */
+  info(...msg: unknown[]) {
+    console.info(chalk.blue(...msg));
+  },
+  /**
+   * Logs a warning message
+   */
+  warn(...msg: unknown[]) {
+    console.warn(chalk.yellow(...msg));
+  },
+};

+ 9 - 0
packages/create-pyth-app/src/templates/web-app/eslint.config.js

@@ -0,0 +1,9 @@
+import { fileURLToPath } from "node:url";
+
+import { nextjs, tailwind, storybook } from "@cprussin/eslint-config";
+
+const tailwindConfig = fileURLToPath(
+  import.meta.resolve(`./tailwind.config.ts`),
+);
+
+export default [...nextjs, ...tailwind(tailwindConfig), ...storybook];

+ 55 - 0
packages/create-pyth-app/src/templates/web-app/next.config.js

@@ -0,0 +1,55 @@
+export default {
+  reactStrictMode: true,
+
+  pageExtensions: ["ts", "tsx", "mdx"],
+
+  logging: {
+    fetches: {
+      fullUrl: true,
+    },
+  },
+
+  webpack(config) {
+    config.module.rules.push({
+      test: /\.svg$/i,
+      use: ["@svgr/webpack"],
+    });
+
+    config.resolve.extensionAlias = {
+      ".js": [".js", ".ts", ".tsx"],
+    };
+
+    return config;
+  },
+
+  headers: () => [
+    {
+      source: "/:path*",
+      headers: [
+        {
+          key: "X-XSS-Protection",
+          value: "1; mode=block",
+        },
+        {
+          key: "Referrer-Policy",
+          value: "strict-origin-when-cross-origin",
+        },
+        {
+          key: "Strict-Transport-Security",
+          value: "max-age=2592000",
+        },
+        {
+          key: "X-Content-Type-Options",
+          value: "nosniff",
+        },
+        {
+          key: "Permissions-Policy",
+          value:
+            "vibrate=(), geolocation=(), midi=(), notifications=(), push=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), speaker=(), vibrate=(), fullscreen=self",
+        },
+      ],
+    },
+  ],
+
+  rewrites: () => [],
+};

+ 58 - 0
packages/create-pyth-app/src/templates/web-app/package.json

@@ -0,0 +1,58 @@
+{
+  "private": true,
+  "name": "{{name}}",
+  "description": "{{description}}",
+  "version": "0.0.0",
+  "type": "module",
+  "engines": {
+    "node": ">=22.14.0"
+  },
+  "scripts": {
+    "build:vercel": "next build",
+    "fix:format": "prettier --write .",
+    "fix:lint:eslint": "eslint --fix .",
+    "fix:lint:stylelint": "stylelint --fix 'src/**/*.scss'",
+    "pull:env": "[ $CI ] || VERCEL_ORG_ID=team_BKQrg3JJFLxZyTqpuYtIY0rj VERCEL_PROJECT_ID=TODO_FILL_ME_IN vercel env pull",
+    "start:dev": "next dev --port 3003",
+    "start:prod": "next start --port 3003",
+    "test:format": "prettier --check .",
+    "test:lint:eslint": "eslint . --max-warnings 0",
+    "test:lint:stylelint": "stylelint 'src/**/*.scss' --max-warnings 0",
+    "test:types": "tsc"
+  },
+  "devDependencies": {
+    "@axe-core/react": "catalog:",
+    "@cprussin/eslint-config": "catalog:",
+    "@cprussin/prettier-config": "catalog:",
+    "@cprussin/tsconfig": "catalog:",
+    "@pythnetwork/jest-config": "workspace:",
+    "@types/jest": "catalog:",
+    "@types/node": "catalog:",
+    "@types/react": "catalog:",
+    "@types/react-dom": "catalog:",
+    "autoprefixer": "catalog:",
+    "eslint": "catalog:",
+    "jest": "catalog:",
+    "postcss": "catalog:",
+    "prettier": "catalog:",
+    "tailwindcss": "catalog:",
+    "tailwindcss-animate": "catalog:",
+    "tailwindcss-react-aria-components": "catalog:",
+    "vercel": "catalog:"
+  },
+  "dependencies": {
+    "@next/third-parties": "catalog:",
+    "@pythnetwork/component-library": "workspace:*",
+    "@react-hookz/web": "catalog:",
+    "clsx": "catalog:",
+    "framer-motion": "catalog:",
+    "next": "catalog:",
+    "pino": "catalog:",
+    "react": "catalog:",
+    "react-aria": "catalog:",
+    "react-aria-components": "catalog:",
+    "react-dom": "catalog:",
+    "swr": "catalog:",
+    "zod": "catalog:"
+  }
+}

+ 6 - 0
packages/create-pyth-app/src/templates/web-app/postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    autoprefixer: {},
+    tailwindcss: {},
+  },
+};

+ 9 - 0
packages/create-pyth-app/src/templates/web-app/prettier.config.js

@@ -0,0 +1,9 @@
+import { fileURLToPath } from "node:url";
+
+import { base, tailwind, mergeConfigs } from "@cprussin/prettier-config";
+
+const tailwindConfig = fileURLToPath(
+  import.meta.resolve(`./tailwind.config.ts`),
+);
+
+export default mergeConfigs([base, tailwind(tailwindConfig)]);

BIN
packages/create-pyth-app/src/templates/web-app/public/android-chrome-192x192.png


BIN
packages/create-pyth-app/src/templates/web-app/public/android-chrome-512x512.png


BIN
packages/create-pyth-app/src/templates/web-app/public/apple-touch-icon.png


BIN
packages/create-pyth-app/src/templates/web-app/public/favicon-16x16.png


BIN
packages/create-pyth-app/src/templates/web-app/public/favicon-32x32.png


BIN
packages/create-pyth-app/src/templates/web-app/public/favicon-light.ico


BIN
packages/create-pyth-app/src/templates/web-app/public/favicon.ico


+ 11 - 0
packages/create-pyth-app/src/templates/web-app/router.d.ts

@@ -0,0 +1,11 @@
+import "react-aria-components";
+
+declare module "react-aria-components" {
+  import { useRouter } from "next/navigation";
+
+  export type RouterConfig = {
+    routerOptions: NonNullable<
+      Parameters<ReturnType<typeof useRouter>["push"]>[1]
+    >;
+  };
+}

+ 26 - 0
packages/create-pyth-app/src/templates/web-app/src/app/globals.scss

@@ -0,0 +1,26 @@
+@import "tailwindcss";
+
+:root {
+  --background: #ffffff;
+  --foreground: #171717;
+}
+
+@theme inline {
+  --color-background: var(--background);
+  --color-foreground: var(--foreground);
+  --font-sans: var(--font-geist-sans);
+  --font-mono: var(--font-geist-mono);
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --background: #0a0a0a;
+    --foreground: #ededed;
+  }
+}
+
+body {
+  background: var(--background);
+  color: var(--foreground);
+  font-family: Arial, Helvetica, sans-serif;
+}

+ 34 - 0
packages/create-pyth-app/src/templates/web-app/src/app/layout.tsx

@@ -0,0 +1,34 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.css";
+
+const geistSans = Geist({
+  variable: "--font-geist-sans",
+  subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+  variable: "--font-geist-mono",
+  subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+  title: "Create Next App",
+  description: "Generated by create next app",
+};
+
+export default function RootLayout({
+  children,
+}: Readonly<{
+  children: React.ReactNode;
+}>) {
+  return (
+    <html lang="en">
+      <body
+        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
+      >
+        {children}
+      </body>
+    </html>
+  );
+}

+ 103 - 0
packages/create-pyth-app/src/templates/web-app/src/app/page.tsx

@@ -0,0 +1,103 @@
+import Image from "next/image";
+
+export default function Home() {
+  return (
+    <div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
+      <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
+        <Image
+          className="dark:invert"
+          src="/next.svg"
+          alt="Next.js logo"
+          width={180}
+          height={38}
+          priority
+        />
+        <ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
+          <li className="mb-2 tracking-[-.01em]">
+            Get started by editing{" "}
+            <code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
+              src/app/page.tsx
+            </code>
+            .
+          </li>
+          <li className="tracking-[-.01em]">
+            Save and see your changes instantly.
+          </li>
+        </ol>
+
+        <div className="flex gap-4 items-center flex-col sm:flex-row">
+          <a
+            className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
+            href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
+            target="_blank"
+            rel="noopener noreferrer"
+          >
+            <Image
+              className="dark:invert"
+              src="/vercel.svg"
+              alt="Vercel logomark"
+              width={20}
+              height={20}
+            />
+            Deploy now
+          </a>
+          <a
+            className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
+            href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
+            target="_blank"
+            rel="noopener noreferrer"
+          >
+            Read our docs
+          </a>
+        </div>
+      </main>
+      <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
+        <a
+          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
+          href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          <Image
+            aria-hidden
+            src="/file.svg"
+            alt="File icon"
+            width={16}
+            height={16}
+          />
+          Learn
+        </a>
+        <a
+          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
+          href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          <Image
+            aria-hidden
+            src="/window.svg"
+            alt="Window icon"
+            width={16}
+            height={16}
+          />
+          Examples
+        </a>
+        <a
+          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
+          href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          <Image
+            aria-hidden
+            src="/globe.svg"
+            alt="Globe icon"
+            width={16}
+            height={16}
+          />
+          Go to nextjs.org →
+        </a>
+      </footer>
+    </div>
+  );
+}

+ 6 - 0
packages/create-pyth-app/src/templates/web-app/svg.d.ts

@@ -0,0 +1,6 @@
+declare module "*.svg" {
+  import type { ReactElement, SVGProps } from "react";
+
+  const content: (props: SVGProps<SVGElement>) => ReactElement;
+  export default content;
+}

+ 52 - 0
packages/create-pyth-app/src/templates/web-app/tailwind.config.ts

@@ -0,0 +1,52 @@
+import forms from "@tailwindcss/forms";
+import type { Config } from "tailwindcss";
+import tailwindPlugin from "tailwindcss/plugin";
+import animate from "tailwindcss-animate";
+import reactAria from "tailwindcss-react-aria-components";
+
+const tailwindConfig = {
+  darkMode: "class",
+  content: ["src/components/**/*.{ts,tsx}", "src/markdown-components.tsx"],
+  plugins: [
+    forms,
+    animate,
+    reactAria,
+    tailwindPlugin((plugin) => {
+      plugin.addVariant("search-cancel", "&::-webkit-search-cancel-button");
+      plugin.addVariant("search-decoration", "&::-webkit-search-decoration");
+    }),
+  ],
+  theme: {
+    extend: {
+      backgroundImage: {
+        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
+      },
+      fontFamily: {
+        sans: ["var(--font-sans)"],
+        mono: ["var(--font-mono)"],
+      },
+      colors: {
+        pythpurple: {
+          100: "#E6DAFE",
+          400: "#BB86FC",
+          600: "#6200EE",
+          800: "#100E21",
+          900: "#131223",
+          950: "#0C0B1A",
+        },
+      },
+      height: {
+        header: "var(--header-height)",
+      },
+      spacing: {
+        "header-height": "var(--header-height)",
+      },
+      screens: {
+        xs: "412px",
+        "3xl": "2560px",
+      },
+    },
+  },
+} satisfies Config;
+
+export default tailwindConfig;

+ 36 - 0
packages/create-pyth-app/src/templates/web-app/turbo.json

@@ -0,0 +1,36 @@
+{
+  "$schema": "https://turbo.build/schema.json",
+  "extends": ["//"],
+  "tasks": {
+    "build:vercel": {
+      "env": []
+    },
+    "fix:lint": {
+      "dependsOn": [
+        "//#install:modules",
+        "fix:lint:eslint",
+        "fix:lint:stylelint"
+      ]
+    },
+    "fix:lint:eslint": {
+      "dependsOn": ["//#install:modules", "^build"],
+      "cache": false
+    },
+    "fix:lint:stylelint": {
+      "dependsOn": ["//#install:modules"],
+      "cache": false
+    },
+    "start:prod": {
+      "dependsOn": ["//#install:modules", "build:vercel"]
+    },
+    "test:lint": {
+      "dependsOn": ["test:lint:eslint", "test:lint:stylelint"]
+    },
+    "test:lint:eslint": {
+      "dependsOn": ["//#install:modules", "^build"]
+    },
+    "test:lint:stylelint": {
+      "dependsOn": ["//#install:modules"]
+    }
+  }
+}

+ 4 - 0
packages/create-pyth-app/src/templates/web-app/vercel.json

@@ -0,0 +1,4 @@
+{
+  "$schema": "https://openapi.vercel.sh/vercel.json",
+  "buildCommand": "turbo run build:vercel --filter {{name}}"
+}

+ 20 - 0
packages/create-pyth-app/src/types.ts

@@ -0,0 +1,20 @@
+export enum PackageType {
+  CLI = "Command-line application",
+  LIBRARY = "JavaScript / TypeScript Library",
+  WEBAPP = "Web Application",
+}
+
+export type CreatePythAppResponses = {
+  confirm: boolean;
+  customFolderPath?: string;
+  description: string;
+  folder: string;
+  packageName: string;
+  packageType: PackageType;
+};
+
+export type InProgressCreatePythAppResponses = Partial<CreatePythAppResponses>;
+
+export const PACKAGE_PREFIX = "@pythnetwork/";
+
+export const CUSTOM_FOLDER_CHOICE = "__custom__";

+ 7 - 0
packages/create-pyth-app/tsconfig.json

@@ -0,0 +1,7 @@
+{
+  "extends": "@cprussin/tsconfig/base.json",
+  "exclude": ["node_modules", "dist"],
+  "compilerOptions": {
+    "lib": ["DOM", "ESNext"]
+  }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 290 - 214
pnpm-lock.yaml


+ 7 - 0
pnpm-workspace.yaml

@@ -90,19 +90,23 @@ catalog:
   "@tailwindcss/forms": ^0.5.10
   "@tailwindcss/postcss": ^4.1.6
   "@tanstack/react-query": ^5.71.5
+  "@types/fs-extra": ^11.0.4
   "@types/jest": ^29.5.14
   "@types/mdx": ^2.0.13
   "@types/node": ^22.14.0
+  "@types/prompts": 2.4.9
   "@types/react": ^19.1.0
   "@types/react-dom": ^19.1.1
   "@types/yargs": "^17.0.33"
   "@vercel/functions": ^2.0.0
   "@mysten/sui": "^1.3.0"
   "yargs": "^18.0.0"
+  app-root-path: ^3.1.0
   autoprefixer: ^10.4.21
   babel-plugin-react-compiler: 19.1.0-rc.1
   bcp-47: ^2.1.0
   bs58: ^6.0.0
+  chalk: ^5.6.2
   class-variance-authority: ^0.7.1
   clsx: ^2.1.1
   connectkit: ^1.9.0
@@ -119,6 +123,7 @@ catalog:
   fumadocs-ui: ^15.7.12
   fumadocs-openapi: ^9.3.8
   fumadocs-typescript: ^4.0.8
+  fs-extra: ^11.3.2
   highlight.js: ^11.11.1
   ip-range-check: ^0.2.0
   jest: ^29.7.0
@@ -137,6 +142,7 @@ catalog:
   prettier: ^3.5.3
   prettier-plugin-solidity: ^1.4.2
   proxycheck-ts: ^0.0.11
+  prompts: 2.4.2
   react: ^19.1.0
   react-aria: ^3.42.0
   react-aria-components: ^1.11.0
@@ -158,6 +164,7 @@ catalog:
   tailwindcss-animate: ^1.0.7
   tailwindcss-react-aria-components: ^2.0.0
   ts-node: ^10.9.2
+  tsx: 4.20.6
   typedoc: ^0.26.8
   typescript: ^5.9.3
   turbo: ^2.5.8

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov