Bläddra i källkod

Merge pull request #3185 from pyth-network/bduran/create-pyth-app

feat(create-pyth-package): added a create-pyth-package script to generate things with best practices baked-in
Ben Duran 1 vecka sedan
förälder
incheckning
29c387a8cb
60 ändrade filer med 1846 tillägg och 215 borttagningar
  1. 1 0
      package.json
  2. 26 0
      packages/create-pyth-package/.gitignore
  3. 12 0
      packages/create-pyth-package/.prettierignore
  4. 6 0
      packages/create-pyth-package/bin/create-pyth-package
  5. 4 0
      packages/create-pyth-package/eslint.config.js
  6. 44 0
      packages/create-pyth-package/package.json
  7. 253 0
      packages/create-pyth-package/src/create-pyth-package.ts
  8. 25 0
      packages/create-pyth-package/src/get-all-monorepo-packages.ts
  9. 28 0
      packages/create-pyth-package/src/get-available-folders.ts
  10. 38 0
      packages/create-pyth-package/src/get-taken-ports.ts
  11. 33 0
      packages/create-pyth-package/src/logger.ts
  12. 133 0
      packages/create-pyth-package/src/templates/cli/.gitignore
  13. 14 0
      packages/create-pyth-package/src/templates/cli/.prettierignore
  14. 13 0
      packages/create-pyth-package/src/templates/cli/README.md
  15. 3 0
      packages/create-pyth-package/src/templates/cli/bin.js
  16. 1 0
      packages/create-pyth-package/src/templates/cli/eslint.config.js
  17. 3 0
      packages/create-pyth-package/src/templates/cli/jest.config.js
  18. 46 0
      packages/create-pyth-package/src/templates/cli/package.json
  19. 18 0
      packages/create-pyth-package/src/templates/cli/src/cli.ts
  20. 17 0
      packages/create-pyth-package/src/templates/cli/src/commands/hello-world-cmd.ts
  21. 10 0
      packages/create-pyth-package/src/templates/cli/tsconfig.build.json
  22. 8 0
      packages/create-pyth-package/src/templates/cli/tsconfig.json
  23. 133 0
      packages/create-pyth-package/src/templates/library/.gitignore
  24. 14 0
      packages/create-pyth-package/src/templates/library/.prettierignore
  25. 5 0
      packages/create-pyth-package/src/templates/library/README.md
  26. 1 0
      packages/create-pyth-package/src/templates/library/eslint.config.js
  27. 3 0
      packages/create-pyth-package/src/templates/library/jest.config.js
  28. 39 0
      packages/create-pyth-package/src/templates/library/package.json
  29. 4 0
      packages/create-pyth-package/src/templates/library/src/index.ts
  30. 10 0
      packages/create-pyth-package/src/templates/library/tsconfig.build.json
  31. 8 0
      packages/create-pyth-package/src/templates/library/tsconfig.json
  32. 133 0
      packages/create-pyth-package/src/templates/web-app/.gitignore
  33. 14 0
      packages/create-pyth-package/src/templates/web-app/.prettierignore
  34. 13 0
      packages/create-pyth-package/src/templates/web-app/README.md
  35. 1 0
      packages/create-pyth-package/src/templates/web-app/eslint.config.js
  36. 3 0
      packages/create-pyth-package/src/templates/web-app/jest.config.js
  37. 55 0
      packages/create-pyth-package/src/templates/web-app/next.config.js
  38. 64 0
      packages/create-pyth-package/src/templates/web-app/package.json
  39. 5 0
      packages/create-pyth-package/src/templates/web-app/postcss.config.js
  40. 1 0
      packages/create-pyth-package/src/templates/web-app/prettier.config.js
  41. BIN
      packages/create-pyth-package/src/templates/web-app/public/android-chrome-192x192.png
  42. BIN
      packages/create-pyth-package/src/templates/web-app/public/android-chrome-512x512.png
  43. BIN
      packages/create-pyth-package/src/templates/web-app/public/apple-touch-icon.png
  44. BIN
      packages/create-pyth-package/src/templates/web-app/public/favicon-16x16.png
  45. BIN
      packages/create-pyth-package/src/templates/web-app/public/favicon-32x32.png
  46. BIN
      packages/create-pyth-package/src/templates/web-app/public/favicon-light.ico
  47. BIN
      packages/create-pyth-package/src/templates/web-app/public/favicon.ico
  48. 11 0
      packages/create-pyth-package/src/templates/web-app/router.d.ts
  49. 20 0
      packages/create-pyth-package/src/templates/web-app/src/app/globals.scss
  50. 43 0
      packages/create-pyth-package/src/templates/web-app/src/app/layout.tsx
  51. 15 0
      packages/create-pyth-package/src/templates/web-app/src/app/page.module.scss
  52. 16 0
      packages/create-pyth-package/src/templates/web-app/src/app/page.tsx
  53. 6 0
      packages/create-pyth-package/src/templates/web-app/svg.d.ts
  54. 5 0
      packages/create-pyth-package/src/templates/web-app/tsconfig.json
  55. 36 0
      packages/create-pyth-package/src/templates/web-app/turbo.json
  56. 4 0
      packages/create-pyth-package/src/templates/web-app/vercel.json
  57. 26 0
      packages/create-pyth-package/src/types.ts
  58. 7 0
      packages/create-pyth-package/tsconfig.json
  59. 405 215
      pnpm-lock.yaml
  60. 10 0
      pnpm-workspace.yaml

+ 1 - 0
package.json

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

+ 26 - 0
packages/create-pyth-package/.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-package/.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-package/bin/create-pyth-package

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

+ 4 - 0
packages/create-pyth-package/eslint.config.js

@@ -0,0 +1,4 @@
+import { base } from "@cprussin/eslint-config";
+import { globalIgnores } from "eslint/config";
+
+export default [globalIgnores(["src/templates/**"]), ...base];

+ 44 - 0
packages/create-pyth-package/package.json

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

+ 253 - 0
packages/create-pyth-package/src/create-pyth-package.ts

@@ -0,0 +1,253 @@
+/* eslint-disable tsdoc/syntax */
+// 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 { execSync } from "node:child_process";
+import os from "node:os";
+import path from "node:path";
+
+import appRootPath from "app-root-path";
+import chalk from "chalk";
+import glob from "fast-glob";
+import fs from "fs-extra";
+import { render as renderTemplate } from "micromustache";
+import prompts from "prompts";
+import type { PackageJson } from "type-fest";
+
+import { getAvailableFolders } from "./get-available-folders.js";
+import { getTakenPorts } from "./get-taken-ports.js";
+import { Logger } from "./logger.js";
+import type {
+  CreatePythAppResponses,
+  InProgressCreatePythAppResponses,
+} from "./types.js";
+import { PACKAGE_PREFIX, PackageType, TEMPLATES_FOLDER } from "./types.js";
+
+/**
+ * Given either a raw name ("foo") or a scoped name ("@pythnetwork/foo"),
+ * returns the normalized pair { raw, withOrg } where:
+ * - raw is the unscoped package name ("foo")
+ * - withOrg is the scoped package name ("@pythnetwork/foo")
+ */
+function normalizePackageNameInput(val: string | null | undefined = "") {
+  // if the user passed a scoped name already, extract the part after `/`
+  if (val?.startsWith("@")) {
+    const parts = val.split("/");
+    const raw = parts[1] ?? "";
+    return {
+      raw,
+      withOrg: `${PACKAGE_PREFIX}${raw}`,
+    };
+  }
+  // otherwise treat input as raw
+  const raw = val ?? "";
+  return {
+    raw,
+    withOrg: `${PACKAGE_PREFIX}${raw}`,
+  };
+}
+
+/**
+ * returns the folder that holds the correct templates, based on the user's
+ * package choice
+ */
+function getTemplatesInputFolder(packageType: PackageType) {
+  switch (packageType) {
+    case PackageType.CLI: {
+      return path.join(TEMPLATES_FOLDER, "cli");
+    }
+    case PackageType.LIBRARY: {
+      return path.join(TEMPLATES_FOLDER, "library");
+    }
+    case PackageType.WEBAPP: {
+      return path.join(TEMPLATES_FOLDER, "web-app");
+    }
+    default: {
+      throw new Error(
+        `unsupported package type of "${String(packageType)}" was found`,
+      );
+    }
+  }
+}
+
+async function createPythApp() {
+  const takenServerPorts = getTakenPorts();
+
+  const responses = (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",
+    },
+    {
+      // Store the raw name (no format). We'll normalize after prompts
+      message: (_, responses: InProgressCreatePythAppResponses) =>
+        `Enter the name for your ${responses.packageType ?? ""} package. ${chalk.magenta(PACKAGE_PREFIX)}`,
+      name: "packageName",
+      type: "text",
+      validate: (name: string) => {
+        // validate using the full scoped candidate so we ensure the raw name is valid
+        const proposedName = `${PACKAGE_PREFIX}${name.replace(/^@.*\//, "")}`;
+        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) =>
+        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:
+        "On which port do you want your web application server to listen?",
+      name: "serverPort",
+      type: (_, { packageType }: InProgressCreatePythAppResponses) =>
+        packageType === PackageType.WEBAPP ? "number" : false,
+      validate: (port: number | string) => {
+        const portStr = String(port);
+        const taken = takenServerPorts.has(Number(port));
+        const portHasFourDigits = portStr.length >= 4;
+        if (taken) {
+          return `${portStr} is already taken by another application. Please choose another port.`;
+        }
+        if (!portHasFourDigits) {
+          return "please specify a port that has at least 4 digits";
+        }
+        return true;
+      },
+    },
+    {
+      message:
+        "Are you intending on publishing this, publicly on NPM, for users outside of our org to use?",
+      name: "isPublic",
+      type: (_, { packageType }: InProgressCreatePythAppResponses) =>
+        packageType === PackageType.WEBAPP ? false : "confirm",
+    },
+    {
+      message: (
+        _,
+        { folder, packageName, packageType }: InProgressCreatePythAppResponses,
+      ) => {
+        // normalize for display
+        const { raw: pkgRaw, withOrg: pkgWithOrg } =
+          normalizePackageNameInput(packageName);
+
+        let msg = `Please confirm your choices:${os.EOL}`;
+        msg += `Creating a ${chalk.magenta(packageType)} package, named ${chalk.magenta(pkgWithOrg)}, in ${chalk.magenta(packageType === PackageType.WEBAPP ? "apps" : folder)}/${pkgRaw}.${os.EOL}`;
+        msg += "Look good?";
+
+        return msg;
+      },
+      name: "confirm",
+      type: "confirm",
+    },
+  ])) as CreatePythAppResponses;
+
+  const {
+    confirm,
+    description,
+    folder,
+    isPublic,
+    packageName,
+    packageType,
+    serverPort,
+  } = responses;
+
+  if (!confirm) {
+    Logger.warn("oops, you did not confirm your choices.");
+    return;
+  }
+
+  // normalize package-name inputs to deterministic values
+  const { raw: packageNameWithoutOrg, withOrg: packageNameWithOrg } =
+    normalizePackageNameInput(packageName);
+
+  const relDest =
+    packageType === PackageType.WEBAPP
+      ? path.join("apps", packageNameWithoutOrg)
+      : path.join(folder, packageNameWithoutOrg);
+  const absDest = path.join(appRootPath.toString(), relDest);
+
+  Logger.info("ensuring", relDest, `exists (abs path: ${absDest})`);
+  await fs.ensureDir(absDest);
+
+  Logger.info("copying files");
+  const templateInputFolder = getTemplatesInputFolder(packageType);
+  await fs.copy(templateInputFolder, absDest, { overwrite: true });
+
+  const destFiles = await glob(path.join(absDest, "**", "*"), {
+    absolute: true,
+    dot: true,
+    onlyFiles: true,
+  });
+
+  Logger.info(
+    "updating files with the choices you made in the initial prompts",
+  );
+  await Promise.all(
+    destFiles
+      .filter((fp) => !fp.includes("node_module"))
+      .map(async (fp) => {
+        const contents = await fs.readFile(fp, "utf8");
+        const updatedContents = renderTemplate(contents, {
+          description,
+          name: packageNameWithOrg,
+          packageNameWithoutOrg,
+          relativeFolder: relDest,
+          serverPort,
+        });
+        await fs.writeFile(fp, updatedContents, "utf8");
+
+        if (fp.endsWith("package.json")) {
+          const pjson = JSON.parse(updatedContents) as PackageJson;
+          // ensure package name in package.json is the scoped name
+          pjson.name = packageNameWithOrg;
+          pjson.private = !isPublic;
+          if (isPublic) {
+            pjson.publishConfig = {
+              access: "public",
+            };
+          } else {
+            // ensure publishConfig is removed if present and not public
+            if (pjson.publishConfig) {
+              delete pjson.publishConfig;
+            }
+          }
+
+          await fs.writeFile(fp, JSON.stringify(pjson, undefined, 2), "utf8");
+        }
+      }),
+  );
+
+  Logger.info("installing deps");
+  execSync("pnpm i", { cwd: appRootPath.toString(), stdio: "inherit" });
+
+  Logger.info(`Done! ${packageNameWithOrg} is ready for development`);
+  Logger.info("please checkout your package's README for more information:");
+  Logger.info(`  ${path.join(relDest, "README.md")}`);
+}
+
+await createPythApp();

+ 25 - 0
packages/create-pyth-package/src/get-all-monorepo-packages.ts

@@ -0,0 +1,25 @@
+import { execSync } from "node:child_process";
+
+import appRootPath from "app-root-path";
+
+export type PNPMPackageInfo = {
+  name: string;
+  path: string;
+  private: boolean;
+  version: string;
+};
+
+/**
+ * returns basic info about all of the monorepo packages available in
+ * the pyth crosschain repo
+ */
+export function getAllMonorepoPackages(repoRoot = appRootPath.toString()) {
+  const allPackages = JSON.parse(
+    execSync("pnpm list --recursive --depth -1 --json", {
+      cwd: repoRoot,
+      stdio: "pipe",
+    }).toString("utf8"),
+  ) as PNPMPackageInfo[];
+
+  return allPackages;
+}

+ 28 - 0
packages/create-pyth-package/src/get-available-folders.ts

@@ -0,0 +1,28 @@
+import path from "node:path";
+
+import appRootPath from "app-root-path";
+
+import { getAllMonorepoPackages } from "./get-all-monorepo-packages.js";
+
+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 = getAllMonorepoPackages();
+
+  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),
+    ),
+  ];
+}

+ 38 - 0
packages/create-pyth-package/src/get-taken-ports.ts

@@ -0,0 +1,38 @@
+import path from "node:path";
+
+import fs from "fs-extra";
+import type { PackageJson } from "type-fest";
+
+import { getAllMonorepoPackages } from "./get-all-monorepo-packages.js";
+
+/**
+ * scrapes the entire monorepo to see what next.js ports
+ * are already taken and returns a Set() containing these ports
+ * to assist the user in choosing one that doesn't have a collision
+ */
+export function getTakenPorts() {
+  // "start:dev": "next dev --port 3003",
+  //   "start:prod": "next start --port 3003",
+  const allPackages = getAllMonorepoPackages();
+
+  const out = new Set<number>();
+
+  for (const info of allPackages) {
+    const pjsonPath = path.join(info.path, "package.json");
+    const pjson = JSON.parse(fs.readFileSync(pjsonPath, "utf8")) as PackageJson;
+    const scripts = pjson.scripts ?? {};
+
+    for (const scriptVal of Object.values(scripts)) {
+      if (!scriptVal) continue;
+      if (!scriptVal.includes("next ")) continue;
+
+      const [, portViaFlagStr = ""] = /--port\s+(\d+)/.exec(scriptVal) ?? [];
+      const [, portViaEnvStr = ""] = /^PORT=(\d+)/.exec(scriptVal) ?? [];
+      const port = Number(portViaFlagStr || portViaEnvStr);
+
+      if (!Number.isNaN(port)) out.add(port);
+    }
+  }
+
+  return out;
+}

+ 33 - 0
packages/create-pyth-package/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));
+  },
+};

+ 133 - 0
packages/create-pyth-package/src/templates/cli/.gitignore

@@ -0,0 +1,133 @@
+.env*.local
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.*
+!.env.example
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Sveltekit cache directory
+.svelte-kit/
+
+# vitepress build output
+**/.vitepress/dist
+
+# vitepress cache directory
+**/.vitepress/cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# Firebase cache directory
+.firebase/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v3
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Vite logs files
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*

+ 14 - 0
packages/create-pyth-package/src/templates/cli/.prettierignore

@@ -0,0 +1,14 @@
+.next/
+coverage/
+node_modules/
+*.tsbuildinfo
+.env*.local
+.env
+.DS_Store
+dist/
+lib/
+build/
+node_modules/
+package.json
+tsconfig*.json
+turbo.json

+ 13 - 0
packages/create-pyth-package/src/templates/cli/README.md

@@ -0,0 +1,13 @@
+# {{name}}
+
+{{description}}
+
+TODO: Fill out readme with more instructions
+
+## Running locally during development
+
+From the root of this repo:
+
+```
+pnpm --dir ./{{relativeFolder}} start:dev
+```

+ 3 - 0
packages/create-pyth-package/src/templates/cli/bin.js

@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+import "./dist/cli.mjs";

+ 1 - 0
packages/create-pyth-package/src/templates/cli/eslint.config.js

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

+ 3 - 0
packages/create-pyth-package/src/templates/cli/jest.config.js

@@ -0,0 +1,3 @@
+import { defineJestConfig } from "@pythnetwork/jest-config/define-config";
+
+export default defineJestConfig();

+ 46 - 0
packages/create-pyth-package/src/templates/cli/package.json

@@ -0,0 +1,46 @@
+{
+  "name": "{{name}}",
+  "description": "{{description}}",
+  "version": "0.0.0",
+  "type": "module",
+  "bin": {
+    "{{packageNameWithoutOrg}}": "./bin.js"
+  },
+  "files": [
+    "bin.js",
+    "dist/**"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain",
+    "directory": "{{relativeFolder}}"
+  },
+  "engines": {
+    "node": ">=22.14.0"
+  },
+  "scripts": {
+    "build": "ts-duality --noCjs",
+    "clean": "rm -rf dist/",
+    "fix:lint": "eslint src/ --fix --max-warnings 0",
+    "fix:format": "prettier --write \"src/**/*.ts\"",
+    "start:dev": "tsx ./src/cli.ts",
+    "test:lint": "eslint src/ --max-warnings 0",
+    "test:format": "prettier --check \"src/**/*.ts\"",
+    "test:unit": "jest"
+  },
+  "devDependencies": {
+    "@cprussin/eslint-config": "catalog:",
+    "@cprussin/prettier-config": "catalog:",
+    "@cprussin/tsconfig": "catalog:",
+    "@pythnetwork/jest-config": "workspace:",
+    "@types/jest": "catalog:",
+    "@types/node": "catalog:",
+    "@types/yargs": "catalog:",
+    "eslint": "catalog:",
+    "jest": "catalog:",
+    "tsx": "catalog:"
+  },
+  "dependencies": {
+    "yargs": "catalog:"
+  }
+}

+ 18 - 0
packages/create-pyth-package/src/templates/cli/src/cli.ts

@@ -0,0 +1,18 @@
+import createCLI from "yargs";
+import { hideBin } from "yargs/helpers";
+
+import { setupHelloWorldCommand } from "./commands/hello-world-cmd.js";
+
+async function setupCLI() {
+  let yargs = createCLI(hideBin(process.argv));
+  yargs = setupHelloWorldCommand(yargs);
+
+  const { _ } = await yargs.help().argv;
+
+  if (_.length === 0) {
+    yargs.showHelp();
+    return;
+  }
+}
+
+setupCLI();

+ 17 - 0
packages/create-pyth-package/src/templates/cli/src/commands/hello-world-cmd.ts

@@ -0,0 +1,17 @@
+import type { Argv } from "yargs";
+
+export function setupHelloWorldCommand(yargs: Argv) {
+  return yargs.command(
+    "hello-world",
+    "prints hello world and your name, if provided",
+    (y) =>
+      y.option("name", {
+        alias: "n",
+        description: "your name",
+        type: "string",
+      }),
+    async ({ name }) => {
+      console.info("hello, world!", name || "");
+    },
+  );
+}

+ 10 - 0
packages/create-pyth-package/src/templates/cli/tsconfig.build.json

@@ -0,0 +1,10 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "noEmit": false,
+    "incremental": false,
+    "declaration": true,
+    "isolatedModules": false
+  },
+  "exclude": ["node_modules", "dist", "src/examples/", "**/__tests__/*"]
+}

+ 8 - 0
packages/create-pyth-package/src/templates/cli/tsconfig.json

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

+ 133 - 0
packages/create-pyth-package/src/templates/library/.gitignore

@@ -0,0 +1,133 @@
+.env*.local
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.*
+!.env.example
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Sveltekit cache directory
+.svelte-kit/
+
+# vitepress build output
+**/.vitepress/dist
+
+# vitepress cache directory
+**/.vitepress/cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# Firebase cache directory
+.firebase/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v3
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Vite logs files
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*

+ 14 - 0
packages/create-pyth-package/src/templates/library/.prettierignore

@@ -0,0 +1,14 @@
+.next/
+coverage/
+node_modules/
+*.tsbuildinfo
+.env*.local
+.env
+.DS_Store
+dist/
+lib/
+build/
+node_modules/
+package.json
+tsconfig*.json
+turbo.json

+ 5 - 0
packages/create-pyth-package/src/templates/library/README.md

@@ -0,0 +1,5 @@
+# {{name}}
+
+{{description}}
+
+TODO: Fill out readme with more instructions

+ 1 - 0
packages/create-pyth-package/src/templates/library/eslint.config.js

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

+ 3 - 0
packages/create-pyth-package/src/templates/library/jest.config.js

@@ -0,0 +1,3 @@
+import { defineJestConfig } from "@pythnetwork/jest-config/define-config";
+
+export default defineJestConfig();

+ 39 - 0
packages/create-pyth-package/src/templates/library/package.json

@@ -0,0 +1,39 @@
+{
+  "name": "{{name}}",
+  "description": "{{description}}",
+  "version": "0.0.0",
+  "type": "module",
+  "files": [
+    "dist/**"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain",
+    "directory": "{{relativeFolder}}"
+  },
+  "engines": {
+    "node": ">=22.14.0"
+  },
+  "scripts": {
+    "build": "ts-duality --copyOtherFiles",
+    "clean": "rm -rf ./dist",
+    "fix:lint": "eslint --fix . --max-warnings 0",
+    "fix:format": "prettier --write .",
+    "test:lint": "eslint . --max-warnings 0",
+    "test:format": "prettier --check .",
+    "test:types": "tsc",
+    "test:unit": "jest"
+  },
+  "devDependencies": {
+    "@cprussin/eslint-config": "catalog:",
+    "@cprussin/prettier-config": "catalog:",
+    "@cprussin/tsconfig": "catalog:",
+    "@pythnetwork/jest-config": "workspace:",
+    "@types/jest": "catalog:",
+    "@types/node": "catalog:",
+    "@types/yargs": "catalog:",
+    "eslint": "catalog:",
+    "jest": "catalog:"
+  },
+  "dependencies": {}
+}

+ 4 - 0
packages/create-pyth-package/src/templates/library/src/index.ts

@@ -0,0 +1,4 @@
+export function helloWorldLibrary() {
+  // eslint-disable-next-line no-console
+  console.info("hello, world from the library!");
+}

+ 10 - 0
packages/create-pyth-package/src/templates/library/tsconfig.build.json

@@ -0,0 +1,10 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "noEmit": false,
+    "incremental": false,
+    "declaration": true,
+    "isolatedModules": false
+  },
+  "exclude": ["node_modules", "dist", "src/examples/", "**/__tests__/*"]
+}

+ 8 - 0
packages/create-pyth-package/src/templates/library/tsconfig.json

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

+ 133 - 0
packages/create-pyth-package/src/templates/web-app/.gitignore

@@ -0,0 +1,133 @@
+.env*.local
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.*
+!.env.example
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Sveltekit cache directory
+.svelte-kit/
+
+# vitepress build output
+**/.vitepress/dist
+
+# vitepress cache directory
+**/.vitepress/cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# Firebase cache directory
+.firebase/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v3
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Vite logs files
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*

+ 14 - 0
packages/create-pyth-package/src/templates/web-app/.prettierignore

@@ -0,0 +1,14 @@
+.next/
+coverage/
+node_modules/
+*.tsbuildinfo
+.env*.local
+.env
+.DS_Store
+dist/
+lib/
+build/
+node_modules/
+package.json
+tsconfig*.json
+turbo.json

+ 13 - 0
packages/create-pyth-package/src/templates/web-app/README.md

@@ -0,0 +1,13 @@
+# {{name}}
+
+{{description}}
+
+TODO: Fill out readme with more instructions
+
+## Running locally
+
+From the root of this repo:
+
+```
+pnpm turbo run start:dev --filter ./{{relativeFolder}}
+```

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

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

+ 3 - 0
packages/create-pyth-package/src/templates/web-app/jest.config.js

@@ -0,0 +1,3 @@
+import { defineJestConfigForNextJs } from "@pythnetwork/jest-config/define-next-config";
+
+export default defineJestConfigForNextJs();

+ 55 - 0
packages/create-pyth-package/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: () => [],
+};

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

@@ -0,0 +1,64 @@
+{
+  "private": true,
+  "name": "{{name}}",
+  "description": "{{description}}",
+  "version": "0.0.0",
+  "type": "module",
+  "engines": {
+    "node": ">=22.14.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain",
+    "directory": "{{relativeFolder}}"
+  },
+  "scripts": {
+    "build:vercel": "next build",
+    "fix:format": "prettier --write .",
+    "fix:lint:eslint": "eslint --fix .",
+    "fix:lint:stylelint": "stylelint --fix 'src/**/*.scss'",
+    "pull:env": "echo 'once {{name}} has been setup in the Vercel UI, you can replace this script with the contents in the pull:env:placeholder, below, as well as the VERCEL_PROJECT_ID variable value'",
+    "pull:env:placeholder": "[ $CI ] || VERCEL_ORG_ID=team_BKQrg3JJFLxZyTqpuYtIY0rj VERCEL_PROJECT_ID=TODO_FILL_ME_IN vercel env pull",
+    "start:dev": "next dev --port {{serverPort}}",
+    "start:prod": "next start --port {{serverPort}}",
+    "test:format": "prettier --check .",
+    "test:lint:eslint": "eslint . --max-warnings 0",
+    "test:lint:stylelint": "stylelint 'src/**/*.scss' --max-warnings 0",
+    "test:types": "tsc",
+    "test:unit": "jest"
+  },
+  "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:",
+    "vercel": "catalog:"
+  },
+  "dependencies": {
+    "@next/third-parties": "catalog:",
+    "@pythnetwork/component-library": "workspace:*",
+    "@pythnetwork/react-hooks": "workspace:*",
+    "@react-hookz/web": "catalog:",
+    "clsx": "catalog:",
+    "framer-motion": "catalog:",
+    "next": "catalog:",
+    "nuqs": "catalog:",
+    "pino": "catalog:",
+    "react": "catalog:",
+    "react-aria": "catalog:",
+    "react-aria-components": "catalog:",
+    "react-dom": "catalog:",
+    "swr": "catalog:",
+    "zod": "catalog:"
+  }
+}

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

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

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

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

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


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


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


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


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


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


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


+ 11 - 0
packages/create-pyth-package/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]
+    >;
+  };
+}

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

@@ -0,0 +1,20 @@
+:root {
+  --background: #ffffff;
+  --foreground: #171717;
+
+  --font-sans: "Geist Sans", Arial, Helvetica, sans-serif;
+  --font-mono: "Geist Mono", monospace;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --background: #0a0a0a;
+    --foreground: #ededed;
+  }
+}
+
+body {
+  background: var(--background);
+  color: var(--foreground);
+  font-family: var(--font-sans);
+}

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

@@ -0,0 +1,43 @@
+import { AppShell } from "@pythnetwork/component-library/AppShell";
+import { NuqsAdapter } from "@pythnetwork/react-hooks/nuqs-adapters-next";
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.scss";
+import type { ReactNode } from "react";
+
+const geistSans = Geist({
+  variable: "--font-geist-sans",
+  subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+  variable: "--font-geist-mono",
+  subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+  title: "{{packageNameWithoutOrg}}",
+  description: "{{description}}",
+};
+
+export default function RootLayout({
+  children,
+}: Readonly<{
+  children: ReactNode;
+}>) {
+  return (
+    <html lang="en">
+      <body
+        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
+      >
+        <AppShell
+          appName="{{packageNameWithoutOrg}}"
+          enableAccessibilityReporting
+          providers={[NuqsAdapter]}
+        >
+          {children}
+        </AppShell>
+      </body>
+    </html>
+  );
+}

+ 15 - 0
packages/create-pyth-package/src/templates/web-app/src/app/page.module.scss

@@ -0,0 +1,15 @@
+@use "@pythnetwork/component-library/theme";
+
+.main {
+  @include theme.max-width;
+}
+
+.ol {
+  margin-left: theme.spacing(4);
+  padding: 0;
+
+  & code {
+    color: theme.color("states", "info", "normal");
+    font-family: theme.font-family("monospace");
+  }
+}

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

@@ -0,0 +1,16 @@
+import classes from "./page.module.scss";
+
+export default function Home() {
+  return (
+    <div>
+      <main className={classes.main}>
+        <ol className={classes.ol}>
+          <li>
+            Get started by editing <code>src/app/page.tsx</code>.
+          </li>
+          <li>Save and see your changes instantly.</li>
+        </ol>
+      </main>
+    </div>
+  );
+}

+ 6 - 0
packages/create-pyth-package/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;
+}

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

@@ -0,0 +1,5 @@
+{
+  "extends": "@cprussin/tsconfig/nextjs.json",
+  "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
+  "exclude": ["node_modules"]
+}

+ 36 - 0
packages/create-pyth-package/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-package/src/templates/web-app/vercel.json

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

+ 26 - 0
packages/create-pyth-package/src/types.ts

@@ -0,0 +1,26 @@
+import path from "node:path";
+
+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;
+  isPublic: boolean;
+  packageName: string;
+  packageType: PackageType;
+  serverPort?: number;
+};
+
+export type InProgressCreatePythAppResponses = Partial<CreatePythAppResponses>;
+
+export const PACKAGE_PREFIX = "@pythnetwork/";
+
+export const CUSTOM_FOLDER_CHOICE = "__custom__";
+
+export const TEMPLATES_FOLDER = path.join(import.meta.dirname, "templates");

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

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

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 405 - 215
pnpm-lock.yaml


+ 10 - 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
@@ -113,12 +117,14 @@ catalog:
   dnum: ^2.14.0
   eslint: ^9.23.0
   fuels: 0.101.3
+  fast-glob: ^3.3.3
   framer-motion: ^12.6.3
   fumadocs-core: ^15.7.12
   fumadocs-mdx: ^11.10.0
   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
@@ -126,6 +132,7 @@ catalog:
   lightweight-charts: ^5.0.5
   lucide-react: ^0.487.0
   match-sorter: ^8.1.0
+  micromustache: ^8.0.3
   modern-normalize: ^3.0.1
   motion: ^12.9.2
   next: ^15.5.0
@@ -137,6 +144,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,7 +166,9 @@ 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
+  type-fest: ^5.2.0
   typescript: ^5.9.3
   turbo: ^2.5.8
   vercel: ^41.4.1

Vissa filer visades inte eftersom för många filer har ändrats