create-caspian-app 0.3.0-rc.7 → 0.3.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{execSync,spawnSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.html","not-found.html","error.html"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,prisma:!1,mcp:!1},requiredFiles:["main.py",".prettierrc","pyproject.toml","src/app/layout.html","src/app/index.html"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,prisma:!0,mcp:!1},requiredFiles:["main.py",".prettierrc","pyproject.toml","postcss.config.js","src/app/layout.html","src/app/index.html","public/js/main.js","src/app/globals.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,prisma:!0,mcp:!1},requiredFiles:["main.py","pyproject.toml"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,prisma:!0,mcp:!0},requiredFiles:["main.py",".prettierrc","pyproject.toml","postcss.config.js","src/lib/mcp"]}};function bsConfigUrls(e){const n=e.indexOf("\\htdocs\\");if(-1===n)return console.error("Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"),{bsTarget:"",bsPathRewrite:{}};const s=e.substring(0,n+8).replace(/\\/g,"\\\\"),t=e.replace(new RegExp(`^${s}`),"").replace(/\\/g,"/");let i=`http://localhost/${t}`;i=i.endsWith("/")?i.slice(0,-1):i;const c=i.replace(/(?<!:)(\/\/+)/g,"/"),a=t.replace(/\/\/+/g,"/");return{bsTarget:`${c}/`,bsPathRewrite:{"^/":`/${a.startsWith("/")?a.substring(1):a}/`}}}async function updatePackageJson(e,n){const s=path.join(e,"package.json");if(checkExcludeFiles(s))return;const t=JSON.parse(fs.readFileSync(s,"utf8"));t.scripts={...t.scripts,projectName:"tsx settings/project-name.ts"};let i=[];n.tailwindcss&&(t.scripts={...t.scripts,tailwind:"tsx settings/run-postcss.ts watch","tailwind:build":"tsx settings/run-postcss.ts build"},i.push("tailwind")),n.typescript&&!n.backendOnly&&(t.scripts={...t.scripts,"ts:watch":"vite build --watch","ts:watch:dev":"tsx settings/run-vite-watch.ts","ts:build":"vite build"},i.push("ts:watch:dev")),n.mcp&&(t.scripts={...t.scripts,mcp:"tsx settings/restart-mcp.ts"},i.push("mcp"));let c={...t.scripts};c.browserSync="tsx settings/bs-config.ts",c.dev=`npm-run-all projectName -p browserSync ${i.join(" ")}`;let a=["projectName"];n.tailwindcss&&a.unshift("tailwind:build"),n.typescript&&!n.backendOnly&&a.unshift("ts:build"),c.build=`npm-run-all ${a.join(" ")}`,t.scripts=c,t.type="module",fs.writeFileSync(s,JSON.stringify(t,null,2))}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,n,s){const t=fs.existsSync(e),i=t&&fs.statSync(e);if(t&&i&&i.isDirectory()){const t=n.toLowerCase();if(!s.mcp&&t.includes("src\\lib\\mcp"))return;if((!s.typescript||s.backendOnly)&&(t.endsWith("\\ts")||t.includes("\\ts\\")))return;if((!s.typescript||s.backendOnly)&&(t.endsWith("\\vite-plugins")||t.includes("\\vite-plugins\\")||t.includes("\\vite-plugins")))return;if(s.backendOnly&&t.includes("public\\js")||s.backendOnly&&t.includes("public\\css")||s.backendOnly&&t.includes("public\\assets"))return;const i=n.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(i))return;fs.existsSync(n)||fs.mkdirSync(n,{recursive:!0}),fs.readdirSync(e).forEach(t=>{copyRecursiveSync(path.join(e,t),path.join(n,t),s)})}else{if(checkExcludeFiles(n))return;const t=n.replace(/\\/g,"/").toLowerCase();if(t.endsWith("/settings/run-vite-watch.ts")&&(!s.typescript||s.backendOnly))return;if(t.endsWith("/ts/tailwind-merge.ts")&&(!s.typescript||s.backendOnly||!s.tailwindcss))return;if(!s.tailwindcss&&(n.includes("globals.css")||n.includes("styles.css")))return;if(!s.mcp&&n.includes("restart-mcp.ts"))return;if(s.backendOnly&&nonBackendFiles.some(e=>n.includes(e)))return;if(s.backendOnly&&n.includes("layout.html"))return;if(s.tailwindcss&&n.includes("index.css"))return;if(!s.prisma&&n.includes("prisma-schema-config.json"))return;fs.copyFileSync(e,n,0)}}async function executeCopy(e,n,s){n.forEach(({src:n,dest:t})=>{copyRecursiveSync(path.join(__dirname,n),path.join(e,t),s)})}function modifyLayoutPHP(e,n){const s=path.join(e,"src","app","layout.html");if(!checkExcludeFiles(s))try{let e=fs.readFileSync(s,"utf8"),t="";n.backendOnly||(n.tailwindcss||(t='\n <link href="/css/index.css" rel="stylesheet" />'),t+='\n <script type="module" src="/js/main.js"><\/script>');let i="";n.backendOnly||(i=n.tailwindcss?` <link href="/css/styles.css" rel="stylesheet" />${t}`:t),e=e.replace("</head>",`${i}\n</head>`),fs.writeFileSync(s,e,{flag:"w"})}catch(e){console.error(chalk.red("Error modifying layout.html:"),e)}}async function createOrUpdateEnvFile(e,n){const s=path.join(e,".env");checkExcludeFiles(s)||fs.writeFileSync(s,n,{flag:"w"})}function writeTailwindMainJs(e){const n=path.join(e,"public","js","main.js");checkExcludeFiles(n)||(fs.mkdirSync(path.dirname(n),{recursive:!0}),fs.writeFileSync(n,'import "/js/pp-reactive-v2.js";\nimport { twMerge } from "/js/tailwind-merge.mjs";\n\nconst pp = (globalThis).pp;\n\nglobalThis.twMerge = twMerge;\n\nif (document.readyState !== "loading") {\n pp?.mount?.();\n} else {\n document.addEventListener(\n "DOMContentLoaded",\n () => pp?.mount?.(),\n { once: true },\n );\n}\n',{flag:"w"}))}function copyTailwindMergeBundle(e){const n=path.join(e,"node_modules","tailwind-merge","dist","bundle-mjs.mjs"),s=path.join(e,"public","js","tailwind-merge.mjs"),t=path.join(e,"node_modules","tailwind-merge","dist","bundle-mjs.mjs.map"),i=path.join(e,"public","js","bundle-mjs.mjs.map");if(!checkExcludeFiles(s)){if(!fs.existsSync(n))throw new Error(`tailwind-merge bundle not found at ${n}`);fs.mkdirSync(path.dirname(s),{recursive:!0}),fs.copyFileSync(n,s),!checkExcludeFiles(i)&&fs.existsSync(t)&&fs.copyFileSync(t,i)}}function writeTailwindTypeScriptMain(e){const n=path.join(e,"ts","main.ts");checkExcludeFiles(n)||(fs.mkdirSync(path.dirname(n),{recursive:!0}),fs.writeFileSync(n,'import "/js/pp-reactive-v2.js";\n\n// The following global names have already been declared elsewhere in the project:\n// - pp: Used for the Reactive Core functionality.\n\n// Imports goes here --Start\nimport { createGlobalSingleton } from "./global-functions.js";\nimport { mergeTailwindClasses } from "./tailwind-merge.js";\n\ncreateGlobalSingleton("twMerge", mergeTailwindClasses);\n\n\n// Imports goes here --End\n\nconst pp = (globalThis as any).pp;\n\nif (document.readyState !== "loading") {\n\tpp?.mount?.();\n} else {\n\tdocument.addEventListener(\n\t\t"DOMContentLoaded",\n\t\t() => pp?.mount?.(),\n\t\t{ once: true },\n\t);\n}\n',{flag:"w"}))}function checkExcludeFiles(e){if(!updateAnswer?.isUpdate)return!1;const n=e.replace(/\\/g,"/");return!!updateAnswer?.excludeFilePath?.includes(n)||!!updateAnswer?.excludeFiles&&updateAnswer.excludeFiles.some(e=>{const s=e.replace(/\\/g,"/");return n.endsWith("/"+s)||n===s})}async function createDirectoryStructure(e,n){const s=[{src:"/main.py",dest:"/main.py"},{src:"/.prettierrc",dest:"/.prettierrc"},{src:"/pyproject.toml",dest:"/pyproject.toml"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"},{src:"/AGENTS.md",dest:"/AGENTS.md"},{src:"/CLAUDE.md",dest:"/CLAUDE.md"},{src:"/.python-version",dest:"/.python-version"}];n.tailwindcss&&s.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),n.typescript&&!n.backendOnly&&s.push({src:"/vite.config.ts",dest:"/vite.config.ts"});const t=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"},{src:"/public",dest:"/public"},{src:"/.github",dest:"/.github"},{src:"/.vscode",dest:"/.vscode"}];n.typescript&&!n.backendOnly&&t.push({src:"/ts",dest:"/ts"}),s.forEach(({src:n,dest:s})=>{const t=path.join(__dirname,n),i=path.join(e,s);if(checkExcludeFiles(i))return;if("/pyproject.toml"===n&&updateAnswer?.isUpdate&&fs.existsSync(i))return void console.log(chalk.gray("Preserving existing pyproject.toml during update."));const c=fs.readFileSync(t,"utf8");fs.writeFileSync(i,c,{flag:"w"})}),await executeCopy(e,t,n),n.tailwindcss&&!n.backendOnly&&(n.typescript?writeTailwindTypeScriptMain(e):(copyTailwindMergeBundle(e),writeTailwindMainJs(e))),await updatePackageJson(e,n),!n.tailwindcss&&n.backendOnly||modifyLayoutPHP(e,n);const i=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${generateAuthSecret()}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins allowed for CORS (comma-separated)\nCORS_ALLOWED_ORIGINS=""\n\n# Only trust Forwarded/X-Forwarded-* headers for RPC origin checks when the\n# app is deployed behind a trusted reverse proxy that strips client-supplied\n# forwarded headers before adding its own.\nTRUST_FORWARDED_HEADERS="false"\n\n# If you need cookies/Authorization across origins, keep this true\nCORS_ALLOW_CREDENTIALS="true"\n\n# Optional tuning\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\nCORS_EXPOSE_HEADERS=""\nCORS_MAX_AGE="86400"\n\n# Session & Security\nSESSION_LIFETIME_HOURS="7"\nMAX_CONTENT_LENGTH_MB="16"\n\n# Rate Limiting\nRATE_LIMIT_DEFAULT="200 per minute"\nRATE_LIMIT_RPC="60 per minute"\nRATE_LIMIT_AUTH="60 per minute"\n\n# Uvicorn workers are separate OS processes for the same FastAPI app. More\n# workers can increase throughput under concurrent load, but they do not make a\n# single request faster and they duplicate memory, connection pools, and any\n# in-process state. Keep this at 1 unless the app is designed for multi-process\n# coordination and testing shows a real concurrency bottleneck.\nUVICORN_WORKERS="1"`;if(n.prisma){const n=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${i}`;await createOrUpdateEnvFile(e,n)}else await createOrUpdateEnvFile(e,i)}async function getAnswer(e={},n=!1){if(n)return{projectName:e.projectName??"my-app",backendOnly:e.backendOnly??!1,tailwindcss:e.tailwindcss??!1,typescript:e.typescript??!1,mcp:e.mcp??!1,prisma:e.prisma??!1};if(e.starterKit){const n=e.starterKit;let s=null;if(STARTER_KITS[n]&&(s=STARTER_KITS[n]),s){const t={projectName:e.projectName??"my-app",starterKit:n,starterKitSource:e.starterKitSource,backendOnly:s.features.backendOnly??!1,tailwindcss:s.features.tailwindcss??!1,prisma:s.features.prisma??!1,mcp:s.features.mcp??!1,typescript:s.features.typescript??!1},i=process.argv.slice(2);return i.includes("--backend-only")&&(t.backendOnly=!0),i.includes("--tailwindcss")&&(t.tailwindcss=!0),i.includes("--mcp")&&(t.mcp=!0),i.includes("--prisma")&&(t.prisma=!0),i.includes("--typescript")&&(t.typescript=!0),t}if(e.starterKitSource){const s={projectName:e.projectName??"my-app",starterKit:n,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,prisma:!0,mcp:!1,typescript:!1},t=process.argv.slice(2);return t.includes("--backend-only")&&(s.backendOnly=!0),t.includes("--tailwindcss")&&(s.tailwindcss=!0),t.includes("--mcp")&&(s.mcp=!0),t.includes("--prisma")&&(s.prisma=!0),t.includes("--typescript")&&(s.typescript=!0),s}}const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{console.warn(chalk.red("Operation cancelled by the user.")),process.exit(0)},i=await prompts(s,{onCancel:t}),c=[];i.backendOnly??e.backendOnly??!1?(e.mcp||c.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.typescript||c.push({type:"toggle",name:"typescript",message:`Would you like to use ${chalk.blue("TypeScript")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||c.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"}));const a=await prompts(c,{onCancel:t});return{projectName:i.projectName?String(i.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:i.backendOnly??e.backendOnly??!1,tailwindcss:a.tailwindcss??e.tailwindcss??!1,typescript:a.typescript??e.typescript??!1,mcp:a.mcp??e.mcp??!1,prisma:a.prisma??e.prisma??!1}}async function uninstallNpmDependencies(e,n,s=!1){console.log("Uninstalling Node dependencies:"),n.forEach(e=>console.log(`- ${chalk.blue(e)}`));const t=buildManagedNpmCommand(["uninstall",s?"--save-dev":"--save",...n]);execSync(t,{stdio:"inherit",cwd:e})}function buildManagedNpmCommand(e){return`npm ${e.join(" ")} --ignore-scripts=false --min-release-age=0 --audit=false`}function fetchPackageVersion(e){return new Promise((n,s)=>{https.get(`https://registry.npmjs.org/${e}`,e=>{let t="";e.on("data",e=>t+=e),e.on("end",()=>{try{const e=JSON.parse(t);n(e["dist-tags"].latest)}catch(e){s(new Error("Failed to parse JSON response"))}})}).on("error",e=>s(e))})}const readJsonFile=e=>{const n=fs.readFileSync(e,"utf8");return JSON.parse(n)};function compareVersions(e,n){const s=e.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/),t=n.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);if(!s||!t)return e.localeCompare(n);const i=s.slice(1,4).map(Number),c=t.slice(1,4).map(Number);for(let e=0;e<i.length;e++){if(i[e]>c[e])return 1;if(i[e]<c[e])return-1}const a=s[4]??null,r=t[4]??null;return a&&!r?-1:!a&&r?1:a&&r?a.localeCompare(r):0}function getInstalledPackageInfo(e){try{const n=execSync(buildManagedNpmCommand(["list","-g",e,"--depth=0"])).toString(),s=n.match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?)`));return s?{version:s[1],isLinked:n.includes(`${e}@`)&&n.includes("->")}:(console.error(`Package ${e} is not installed`),{version:null,isLinked:!1})}catch(e){return console.error(e instanceof Error?e.message:String(e)),{version:null,isLinked:!1}}}function isRunningFromNpxCache(e){const n=path.resolve(e).toLowerCase(),s=`${path.sep}_npx${path.sep}`.toLowerCase();return n.includes(s)}async function installNpmDependencies(e,n,s=!1){fs.existsSync(path.join(e,"package.json"))?console.log("Updating existing Node.js project..."):console.log("Initializing new Node.js project..."),fs.existsSync(path.join(e,"package.json"))||execSync(buildManagedNpmCommand(["init","-y"]),{stdio:"inherit",cwd:e}),console.log((s?"Installing development dependencies":"Installing dependencies")+":"),n.forEach(e=>console.log(`- ${chalk.blue(e)}`));const t=buildManagedNpmCommand(["install",...s?["--save-dev"]:[],...n]);execSync(t,{stdio:"inherit",cwd:e})}const npmPinnedVersions={"@tailwindcss/postcss":"4.3.0","@types/browser-sync":"2.29.1","@types/node":"25.9.1","@types/prompts":"2.4.9","browser-sync":"3.0.4",chalk:"5.6.2","chokidar-cli":"3.0.0",cssnano:"8.0.1","http-proxy-middleware":"4.0.0","npm-run-all":"4.1.5",postcss:"8.5.15","postcss-cli":"11.0.1",prompts:"2.4.2",tailwindcss:"4.3.0",tsx:"4.22.3",typescript:"6.0.3",vite:"8.0.10","fast-glob":"3.3.3","@lezer/common":"1.5.2","@lezer/python":"1.1.18","caspian-utils":"0.1.x","tailwind-merge":"3.6.0"};function npmPkg(e){return npmPinnedVersions[e]?`${e}@${npmPinnedVersions[e]}`:e}function removeDirectorySafe(e){if(fs.existsSync(e))try{return void fs.rmSync(e,{recursive:!0,force:!0,maxRetries:5,retryDelay:250})}catch(n){const s=n;if("win32"===globalThis.process?.platform&&("EPERM"===s.code||"EACCES"===s.code)){try{spawnSync("cmd",["/c","attrib","-R","-H","-S","/S","/D",`${e}\\*`],{stdio:"ignore"})}catch{}return void spawnSync("cmd",["/c","rd","/s","/q",e],{stdio:"ignore"})}throw n}}async function setupStarterKit(e,n){if(!n.starterKit)return;let s=null;if(STARTER_KITS[n.starterKit]?s=STARTER_KITS[n.starterKit]:n.starterKitSource&&(s={id:n.starterKit,name:`Custom Starter Kit (${n.starterKit})`,description:"Custom starter kit from external source",features:{},requiredFiles:[],source:{type:"git",url:n.starterKitSource}}),s){if(console.log(chalk.green(`Setting up ${s.name}...`)),s.source)try{const t=s.source.branch?`git clone -b ${s.source.branch} --depth 1 ${s.source.url} "${e}"`:`git clone --depth 1 ${s.source.url} "${e}"`;execSync(t,{stdio:"inherit"});removeDirectorySafe(path.join(e,".git")),console.log(chalk.blue("Starter kit cloned successfully!"));const i=path.join(e,"caspian.config.json");if(fs.existsSync(i))try{const s=JSON.parse(fs.readFileSync(i,"utf8")),t=e,c=bsConfigUrls(t);s.projectName=n.projectName,s.projectRootPath=t,s.bsTarget=c.bsTarget,s.bsPathRewrite=c.bsPathRewrite;const a=await fetchPackageVersion("create-caspian-app");s.version=s.version||a,fs.writeFileSync(i,JSON.stringify(s,null,2)),console.log(chalk.green("Updated caspian.config.json with new project details"))}catch(e){console.warn(chalk.yellow("Failed to update caspian.config.json, will create new one"))}}catch(e){throw console.error(chalk.red(`Failed to setup starter kit: ${e}`)),e}s.customSetup&&await s.customSetup(e,n),console.log(chalk.green(`āœ“ ${s.name} setup complete!`))}else console.warn(chalk.yellow(`Starter kit '${n.starterKit}' not found. Skipping...`))}function showStarterKits(){console.log(chalk.blue("\nšŸš€ Available Starter Kits:\n")),Object.values(STARTER_KITS).forEach(e=>{const n=e.source?" (Custom)":" (Built-in)";console.log(chalk.green(` ${e.id}${chalk.gray(n)}`)),console.log(` ${e.name}`),console.log(chalk.gray(` ${e.description}`)),e.source&&console.log(chalk.cyan(` Source: ${e.source.url}`));const s=Object.entries(e.features).filter(([,e])=>!0===e).map(([e])=>e).join(", ");s&&console.log(chalk.magenta(` Features: ${s}`)),console.log()}),console.log(chalk.yellow("Usage:")),console.log(" npx create-caspian-app my-project --starter-kit=basic"),console.log(" npx create-caspian-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo"),console.log()}function runCmd(e,n,s){const t=spawnSync(e,n,{cwd:s,stdio:"inherit",shell:!1,encoding:"utf8"});if(t.error)throw t.error;if(0!==t.status)throw new Error(`Command failed (${e} ${n.join(" ")}), exit=${t.status}`)}function tryRunCmd(e,n,s){const t=spawnSync(e,n,{cwd:s,stdio:"ignore",shell:!1,encoding:"utf8"});return!t.error&&0===t.status}function tryInstallUv(e){console.log(chalk.blue("uv not found. Attempting to install uv..."));const n=[{cmd:"py",args:["-m","pip","install","--upgrade","uv"]},{cmd:"python",args:["-m","pip","install","--upgrade","uv"]},{cmd:"python3",args:["-m","pip","install","--upgrade","uv"]}];for(const s of n)if(tryRunCmd(s.cmd,s.args,e))return!0;return!1}function resolveUvCommand(e){const n=[{cmd:"uv",argsPrefix:[]},{cmd:"py",argsPrefix:["-m","uv"]},{cmd:"python",argsPrefix:["-m","uv"]},{cmd:"python3",argsPrefix:["-m","uv"]}];for(const s of n)if(tryRunCmd(s.cmd,[...s.argsPrefix,"--version"],e))return s;if(tryInstallUv(e))for(const s of n)if(tryRunCmd(s.cmd,[...s.argsPrefix,"--version"],e))return s;throw new Error("Could not find or install uv. Install uv and ensure `uv`, `py`, or `python` is available in PATH.")}function buildPythonDependencies(e){const n=["fastapi==0.136.1","uvicorn==0.47.0","python-dotenv==1.2.2","jinja2==3.1.6","beautifulsoup4==4.14.3","slowapi==0.1.9","python-multipart==0.0.29","starsessions==2.2.1","httpx==0.28.1","werkzeug==3.1.8","cuid2==2.0.1","nanoid==2.0.0","python-ulid==3.1.0","cuid==0.4","caspian-utils~=0.3"];return e.mcp&&n.push("fastmcp==3.2.4"),e.prisma&&(n.push("psycopg2-binary==2.9.12"),n.push("asyncpg==0.31.0"),n.push("aiosqlite==0.22.1"),n.push("aiomysql==0.3.2")),n}function getPythonRequirementName(e){const n=e.trim().match(/^([A-Za-z0-9._-]+)/);return n?.[1]??null}function getPyProjectDependencyNames(e){const n=path.join(e,"pyproject.toml");if(!fs.existsSync(n))return new Set;const s=fs.readFileSync(n,"utf8").replace(/\r\n/g,"\n").match(/^[ \t]*dependencies[ \t]*=[ \t]*\[([\s\S]*?)\]/m);if(!s)return new Set;const t=new Set,i=/"([^"]+)"/g;let c;for(;null!==(c=i.exec(s[1]));){const e=c[1].trim().match(/^([A-Za-z0-9._-]+)/)?.[1];e&&t.add(e.toLowerCase())}return t}function ensurePyProjectExists(e){const n=path.join(e,"pyproject.toml");if(!fs.existsSync(n))throw new Error(`pyproject.toml not found at: ${n}`);let s=fs.readFileSync(n,"utf8");s=s.replace(/\r\n/g,"\n"),s.includes("package = false")||(s=s.includes("[tool.uv]")?s.replace("[tool.uv]","[tool.uv]\npackage = false"):`${s.trimEnd()}\n\n[tool.uv]\npackage = false\n`),fs.writeFileSync(n,s,"utf8")}async function ensurePythonVenvAndDeps(e,n,s=[]){console.log(chalk.green("\n=========================")),console.log(chalk.green("Python setup: syncing dependencies with uv")),console.log(chalk.green("=========================\n")),console.log(chalk.blue("Preparing pyproject.toml...")),ensurePyProjectExists(e);const t=path.join(e,"requirements.txt");fs.existsSync(t)&&(fs.unlinkSync(t),console.log(chalk.gray("Removed legacy requirements.txt")));const i=resolveUvCommand(e),c=path.join(e,".venv");fs.existsSync(c)?console.log(chalk.blue("Existing .venv detected. Reusing it so uv sync can update dependencies without replacing the environment.")):(console.log(chalk.blue("Creating the virtual environment with uv...")),runCmd(i.cmd,[...i.argsPrefix,"venv",".venv"],e));const a=buildPythonDependencies(n),r=a.map(e=>getPythonRequirementName(e)).filter(e=>null!==e);s.length>0&&(console.log(chalk.blue("Removing obsolete Python dependencies via uv remove...")),runCmd(i.cmd,[...i.argsPrefix,"remove",...s],e));const o=r.flatMap(e=>["--upgrade-package",e]);console.log(chalk.blue("Adding Python dependencies via uv add...")),runCmd(i.cmd,[...i.argsPrefix,"add",...o,...a],e),console.log(chalk.blue("Syncing dependencies...")),runCmd(i.cmd,[...i.argsPrefix,"sync"],e),console.log(chalk.green("\nāœ“ uv environment ready and dependencies installed.\n"))}async function main(){try{const e=process.argv.slice(2),n=e.includes("-y");let s=e[0];const t=e.find(e=>e.startsWith("--starter-kit=")),i=t?.split("=")[1],c=e.find(e=>e.startsWith("--starter-kit-source=")),a=c?.split("=")[1];if(e.includes("--list-starter-kits"))return void showStarterKits();let r=null,o=!1;if(s){const t=process.cwd(),c=path.join(t,"caspian.config.json");if(i&&a){o=!0;const t={projectName:s,starterKit:i,starterKitSource:a,backendOnly:e.includes("--backend-only"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};r=await getAnswer(t,n)}else if(fs.existsSync(c)){const i=readJsonFile(c);let a=[];i.excludeFiles?.map(e=>{const n=path.join(t,e);fs.existsSync(n)&&a.push(n.replace(/\\/g,"/"))}),updateAnswer={projectName:s,backendOnly:i.backendOnly,tailwindcss:i.tailwindcss,mcp:i.mcp,prisma:i.prisma,typescript:i.typescript,isUpdate:!0,componentScanDirs:i.componentScanDirs??[],excludeFiles:i.excludeFiles??[],excludeFilePath:a??[],filePath:t};const o={projectName:s,backendOnly:e.includes("--backend-only")||i.backendOnly,tailwindcss:e.includes("--tailwindcss")||i.tailwindcss,typescript:e.includes("--typescript")||i.typescript,prisma:e.includes("--prisma")||i.prisma,mcp:e.includes("--mcp")||i.mcp};r=await getAnswer(o,n),null!==r&&(updateAnswer={projectName:s,backendOnly:r.backendOnly,tailwindcss:r.tailwindcss,mcp:r.mcp,prisma:r.prisma,typescript:r.typescript,isUpdate:!0,componentScanDirs:i.componentScanDirs??[],excludeFiles:i.excludeFiles??[],excludeFilePath:a??[],filePath:t})}else{const t={projectName:s,starterKit:i,starterKitSource:a,backendOnly:e.includes("--backend-only"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};r=await getAnswer(t,n)}if(null===r)return void console.log(chalk.red("Installation cancelled."))}else r=await getAnswer({},n);if(null===r)return void console.warn(chalk.red("Installation cancelled."));const l=await fetchPackageVersion("create-caspian-app"),p=getInstalledPackageInfo("create-caspian-app");isRunningFromNpxCache(__dirname)?console.log(chalk.gray("Skipping global create-caspian-app update because this command is running from an npx cache package.")):p.isLinked?console.log(chalk.gray("Skipping global create-caspian-app update because the global install is linked.")):p.version?-1===compareVersions(p.version,l)&&(execSync(buildManagedNpmCommand(["uninstall","-g","create-caspian-app"]),{stdio:"inherit"}),execSync(buildManagedNpmCommand(["install","-g","create-caspian-app"]),{stdio:"inherit"})):execSync(buildManagedNpmCommand(["install","-g","create-caspian-app"]),{stdio:"inherit"});const d=process.cwd();let u;if(s)if(o){const n=path.join(d,s);fs.existsSync(n)||fs.mkdirSync(n,{recursive:!0}),u=n,await setupStarterKit(u,r),process.chdir(u);const t=path.join(u,"caspian.config.json");if(fs.existsSync(t)){const n=JSON.parse(fs.readFileSync(t,"utf8"));e.includes("--backend-only")&&(n.backendOnly=!0),e.includes("--tailwindcss")&&(n.tailwindcss=!0),e.includes("--typescript")&&(n.typescript=!0),e.includes("--mcp")&&(n.mcp=!0),e.includes("--prisma")&&(n.prisma=!0),r={...r,backendOnly:n.backendOnly,tailwindcss:n.tailwindcss,typescript:n.typescript,mcp:n.mcp,prisma:n.prisma};let s=[];n.excludeFiles?.map(e=>{const n=path.join(u,e);fs.existsSync(n)&&s.push(n.replace(/\\/g,"/"))}),updateAnswer={...r,isUpdate:!0,componentScanDirs:n.componentScanDirs??[],excludeFiles:n.excludeFiles??[],excludeFilePath:s??[],filePath:u}}}else{const e=path.join(d,"caspian.config.json"),n=path.join(d,s),t=path.join(n,"caspian.config.json");fs.existsSync(e)?u=d:fs.existsSync(n)&&fs.existsSync(t)?(u=n,process.chdir(n)):(fs.existsSync(n)||fs.mkdirSync(n,{recursive:!0}),u=n,process.chdir(n))}else fs.mkdirSync(r.projectName,{recursive:!0}),u=path.join(d,r.projectName),process.chdir(r.projectName);let m=[npmPkg("typescript"),npmPkg("@types/node"),npmPkg("tsx"),npmPkg("http-proxy-middleware"),npmPkg("chalk"),npmPkg("npm-run-all"),npmPkg("browser-sync"),npmPkg("@types/browser-sync"),npmPkg("@lezer/common"),npmPkg("@lezer/python"),npmPkg("caspian-utils")];r.prisma&&m.push(npmPkg("prompts"),npmPkg("@types/prompts")),r.tailwindcss&&m.push(npmPkg("tailwindcss"),npmPkg("postcss"),npmPkg("postcss-cli"),npmPkg("@tailwindcss/postcss"),npmPkg("cssnano"),npmPkg("tailwind-merge")),r.prisma&&execSync(buildManagedNpmCommand(["install","-g","prisma-client-python@latest"]),{stdio:"inherit"}),r.typescript&&!r.backendOnly&&m.push(npmPkg("vite"),npmPkg("fast-glob")),r.starterKit&&!o&&await setupStarterKit(u,r),await installNpmDependencies(u,m,!0);let y=[];if(s||execSync("npx tsc --init",{stdio:"inherit"}),await createDirectoryStructure(u,r),r.prisma&&execSync("npx ppy init --caspian",{stdio:"inherit"}),updateAnswer?.isUpdate){const e=[],n=[],s=e=>{try{const n=path.join(u,"package.json");if(fs.existsSync(n)){const s=JSON.parse(fs.readFileSync(n,"utf8"));return!!(s.dependencies&&s.dependencies[e]||s.devDependencies&&s.devDependencies[e])}return!1}catch{return!1}};if(updateAnswer.backendOnly){nonBackendFiles.forEach(e=>{const n=path.join(u,"src","app",e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});["js","css"].forEach(e=>{const n=path.join(u,"src","app",e);fs.existsSync(n)&&(fs.rmSync(n,{recursive:!0,force:!0}),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.tailwindcss){["postcss.config.js"].forEach(e=>{const n=path.join(u,e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});const t=path.join(u,"public","js","tailwind-merge.mjs");fs.existsSync(t)&&(fs.unlinkSync(t),console.log(`${t} was deleted successfully.`));const i=path.join(u,"public","js","bundle-mjs.mjs.map");fs.existsSync(i)&&(fs.unlinkSync(i),console.log(`${i} was deleted successfully.`));const c=path.join(u,"ts","tailwind-merge.ts");fs.existsSync(c)&&(fs.unlinkSync(c),console.log(`${c} was deleted successfully.`));["tailwindcss","postcss","postcss-cli","@tailwindcss/postcss","cssnano","tailwind-merge"].forEach(n=>{s(n)&&e.push(n)}),n.push("tailwind-merge")}if(r.tailwindcss){const e=path.join(u,"public","css","index.css");if(fs.existsSync(e))try{fs.unlinkSync(e),console.log(`${e} was deleted successfully.`)}catch(n){console.warn(chalk.yellow(`Failed to delete ${e}: ${n}`))}}if(!updateAnswer.mcp){["restart-mcp.ts"].forEach(e=>{const n=path.join(u,"settings",e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});const e=path.join(u,"src","lib","mcp");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("MCP folder was deleted successfully.")),n.push("fastmcp")}if(!updateAnswer.prisma){["prisma","@prisma/client","@prisma/internals","better-sqlite3","@prisma/adapter-better-sqlite3","mariadb","@prisma/adapter-mariadb","pg","@prisma/adapter-pg","@types/pg"].forEach(n=>{s(n)&&e.push(n)}),n.push("psycopg2-binary","asyncpg","aiosqlite","aiomysql")}if(!updateAnswer.typescript||updateAnswer.backendOnly){["vite.config.ts",path.join("settings","run-vite-watch.ts")].forEach(e=>{const n=path.join(u,e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});const n=path.join(u,"ts");fs.existsSync(n)&&(fs.rmSync(n,{recursive:!0,force:!0}),console.log("ts folder was deleted successfully."));const t=path.join(u,"settings","vite-plugins");fs.existsSync(t)&&(fs.rmSync(t,{recursive:!0,force:!0}),console.log("settings/vite-plugins folder was deleted successfully."));["vite","fast-glob"].forEach(n=>{s(n)&&e.push(n)})}const t=e=>Array.from(new Set(e)),i=t(e);i.length>0&&(console.log(`Uninstalling npm packages: ${i.join(", ")}`),await uninstallNpmDependencies(u,i,!0));const c=t(n),a=getPyProjectDependencyNames(u);y=c.filter(e=>a.has(e.toLowerCase())),y.length>0&&console.log(chalk.gray(`Python dependencies will be removed via uv remove: ${y.join(", ")}`))}if(!o||!fs.existsSync(path.join(u,"caspian.config.json"))){const e=u.replace(/\\/g,"\\"),n=bsConfigUrls(e),s={projectName:r.projectName,projectRootPath:e,bsTarget:n.bsTarget,bsPathRewrite:n.bsPathRewrite,backendOnly:r.backendOnly,tailwindcss:r.tailwindcss,mcp:r.mcp,prisma:r.prisma,typescript:r.typescript,version:l,componentScanDirs:updateAnswer?.componentScanDirs??["src"],excludeFiles:updateAnswer?.excludeFiles??[]};fs.writeFileSync(path.join(u,"caspian.config.json"),JSON.stringify(s,null,2),{flag:"w"})}await ensurePythonVenvAndDeps(u,r,y),console.log("\n=========================\n"),console.log(`${chalk.green("Success!")} Caspian project successfully created in ${chalk.green(u.replace(/\\/g,"/"))}!`),console.log("\n=========================")}catch(e){console.error("Error while creating the project:",e),process.exit(1)}}main();
2
+ import{execSync,spawnSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.html","not-found.html","error.html"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,prisma:!1,mcp:!1},requiredFiles:["main.py",".prettierrc","pyproject.toml","src/app/layout.html","src/app/index.html"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,prisma:!0,mcp:!1},requiredFiles:["main.py",".prettierrc","pyproject.toml","postcss.config.js","src/app/layout.html","src/app/index.html","public/js/main.js","src/app/globals.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,prisma:!0,mcp:!1},requiredFiles:["main.py","pyproject.toml"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,prisma:!0,mcp:!0},requiredFiles:["main.py",".prettierrc","pyproject.toml","postcss.config.js","src/lib/mcp"]}};function bsConfigUrls(e){const n=e.indexOf("\\htdocs\\");if(-1===n)return console.error("Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"),{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,n+8).replace(/\\/g,"\\\\"),s=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let i=`http://localhost/${s}`;i=i.endsWith("/")?i.slice(0,-1):i;const c=i.replace(/(?<!:)(\/\/+)/g,"/"),a=s.replace(/\/\/+/g,"/");return{bsTarget:`${c}/`,bsPathRewrite:{"^/":`/${a.startsWith("/")?a.substring(1):a}/`}}}async function updatePackageJson(e,n){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const s=JSON.parse(fs.readFileSync(t,"utf8"));s.scripts={...s.scripts,projectName:"tsx settings/project-name.ts"};let i=[];n.tailwindcss&&(s.scripts={...s.scripts,tailwind:"tsx settings/run-postcss.ts watch","tailwind:build":"tsx settings/run-postcss.ts build"},i.push("tailwind")),n.typescript&&!n.backendOnly&&(s.scripts={...s.scripts,"ts:watch":"vite build --watch","ts:watch:dev":"tsx settings/run-vite-watch.ts","ts:build":"vite build"},i.push("ts:watch:dev")),n.mcp&&(s.scripts={...s.scripts,mcp:"tsx settings/restart-mcp.ts"},i.push("mcp"));let c={...s.scripts};c.browserSync="tsx settings/bs-config.ts",c.dev=`npm-run-all projectName -p browserSync ${i.join(" ")}`;let a=["projectName"];n.tailwindcss&&a.unshift("tailwind:build"),n.typescript&&!n.backendOnly&&a.unshift("ts:build"),c.build=`npm-run-all ${a.join(" ")}`,s.scripts=c,s.type="module",fs.writeFileSync(t,JSON.stringify(s,null,2))}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,n,t){const s=fs.existsSync(e),i=s&&fs.statSync(e);if(s&&i&&i.isDirectory()){const s=n.toLowerCase();if(!t.mcp&&s.includes("src\\lib\\mcp"))return;if((!t.typescript||t.backendOnly)&&(s.endsWith("\\ts")||s.includes("\\ts\\")))return;if((!t.typescript||t.backendOnly)&&(s.endsWith("\\vite-plugins")||s.includes("\\vite-plugins\\")||s.includes("\\vite-plugins")))return;if(t.backendOnly&&s.includes("public\\js")||t.backendOnly&&s.includes("public\\css")||t.backendOnly&&s.includes("public\\assets"))return;const i=n.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(i))return;fs.existsSync(n)||fs.mkdirSync(n,{recursive:!0}),fs.readdirSync(e).forEach(s=>{copyRecursiveSync(path.join(e,s),path.join(n,s),t)})}else{if(checkExcludeFiles(n))return;const s=n.replace(/\\/g,"/").toLowerCase();if(s.endsWith("/settings/run-vite-watch.ts")&&(!t.typescript||t.backendOnly))return;if(s.endsWith("/ts/tailwind-merge.ts")&&(!t.typescript||t.backendOnly||!t.tailwindcss))return;if(!t.tailwindcss&&(n.includes("globals.css")||n.includes("styles.css")))return;if(!t.mcp&&n.includes("restart-mcp.ts"))return;if(t.backendOnly&&nonBackendFiles.some(e=>n.includes(e)))return;if(t.backendOnly&&n.includes("layout.html"))return;if(t.tailwindcss&&n.includes("index.css"))return;if(!t.prisma&&n.includes("prisma-schema-config.json"))return;fs.copyFileSync(e,n,0)}}async function executeCopy(e,n,t){n.forEach(({src:n,dest:s})=>{copyRecursiveSync(path.join(__dirname,n),path.join(e,s),t)})}function modifyLayoutPHP(e,n){const t=path.join(e,"src","app","layout.html");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),s="";n.backendOnly||(n.tailwindcss||(s='\n <link href="/css/index.css" rel="stylesheet" />'),s+='\n <script type="module" src="/js/main.js"><\/script>');let i="";n.backendOnly||(i=n.tailwindcss?` <link href="/css/styles.css" rel="stylesheet" />${s}`:s),e=e.replace("</head>",`${i}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){console.error(chalk.red("Error modifying layout.html:"),e)}}async function createOrUpdateEnvFile(e,n){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,n,{flag:"w"})}function writeTailwindMainJs(e){const n=path.join(e,"public","js","main.js");checkExcludeFiles(n)||(fs.mkdirSync(path.dirname(n),{recursive:!0}),fs.writeFileSync(n,'import "/js/pp-reactive-v2.js";\nimport { twMerge } from "/js/tailwind-merge.mjs";\n\nconst pp = (globalThis).pp;\n\nglobalThis.twMerge = twMerge;\n\nif (document.readyState !== "loading") {\n pp?.mount?.();\n} else {\n document.addEventListener(\n "DOMContentLoaded",\n () => pp?.mount?.(),\n { once: true },\n );\n}\n',{flag:"w"}))}function copyTailwindMergeBundle(e){const n=path.join(e,"node_modules","tailwind-merge","dist","bundle-mjs.mjs"),t=path.join(e,"public","js","tailwind-merge.mjs"),s=path.join(e,"node_modules","tailwind-merge","dist","bundle-mjs.mjs.map"),i=path.join(e,"public","js","bundle-mjs.mjs.map");if(!checkExcludeFiles(t)){if(!fs.existsSync(n))throw new Error(`tailwind-merge bundle not found at ${n}`);fs.mkdirSync(path.dirname(t),{recursive:!0}),fs.copyFileSync(n,t),!checkExcludeFiles(i)&&fs.existsSync(s)&&fs.copyFileSync(s,i)}}function writeTailwindTypeScriptMain(e){const n=path.join(e,"ts","main.ts");checkExcludeFiles(n)||(fs.mkdirSync(path.dirname(n),{recursive:!0}),fs.writeFileSync(n,'import "/js/pp-reactive-v2.js";\n\n// The following global names have already been declared elsewhere in the project:\n// - pp: Used for the Reactive Core functionality.\n\n// Imports goes here --Start\nimport { createGlobalSingleton } from "./global-functions.js";\nimport { mergeTailwindClasses } from "./tailwind-merge.js";\n\ncreateGlobalSingleton("twMerge", mergeTailwindClasses);\n\n\n// Imports goes here --End\n\nconst pp = (globalThis as any).pp;\n\nif (document.readyState !== "loading") {\n\tpp?.mount?.();\n} else {\n\tdocument.addEventListener(\n\t\t"DOMContentLoaded",\n\t\t() => pp?.mount?.(),\n\t\t{ once: true },\n\t);\n}\n',{flag:"w"}))}function checkExcludeFiles(e){if(!updateAnswer?.isUpdate)return!1;const n=e.replace(/\\/g,"/");return!!updateAnswer?.excludeFilePath?.includes(n)||!!updateAnswer?.excludeFiles&&updateAnswer.excludeFiles.some(e=>{const t=e.replace(/\\/g,"/");return n.endsWith("/"+t)||n===t})}async function createDirectoryStructure(e,n){const t=[{src:"/main.py",dest:"/main.py"},{src:"/.prettierrc",dest:"/.prettierrc"},{src:"/pyproject.toml",dest:"/pyproject.toml"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"},{src:"/AGENTS.md",dest:"/AGENTS.md"},{src:"/CLAUDE.md",dest:"/CLAUDE.md"},{src:"/.python-version",dest:"/.python-version"}];n.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),n.typescript&&!n.backendOnly&&t.push({src:"/vite.config.ts",dest:"/vite.config.ts"});const s=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"},{src:"/public",dest:"/public"},{src:"/.github",dest:"/.github"},{src:"/.vscode",dest:"/.vscode"}];n.typescript&&!n.backendOnly&&s.push({src:"/ts",dest:"/ts"}),t.forEach(({src:n,dest:t})=>{const s=path.join(__dirname,n),i=path.join(e,t);if(checkExcludeFiles(i))return;if("/pyproject.toml"===n&&updateAnswer?.isUpdate&&fs.existsSync(i))return void console.log(chalk.gray("Preserving existing pyproject.toml during update."));const c=fs.readFileSync(s,"utf8");fs.writeFileSync(i,c,{flag:"w"})}),await executeCopy(e,s,n),n.tailwindcss&&!n.backendOnly&&(n.typescript?writeTailwindTypeScriptMain(e):(copyTailwindMergeBundle(e),writeTailwindMainJs(e))),await updatePackageJson(e,n),!n.tailwindcss&&n.backendOnly||modifyLayoutPHP(e,n);const i=`# -----------------------------------------------------------------------------\n# Application Runtime\n# -----------------------------------------------------------------------------\n\n# Application environment.\n# Use "development" for local work and "production" for deployed environments.\nAPP_ENV="development"\n\n# Application timezone used by server-side date/time helpers.\nAPP_TIMEZONE="UTC"\n\n# Show detailed errors in the browser.\n# Keep true for development and false in production.\nSHOW_ERRORS="true"\n\n\n# -----------------------------------------------------------------------------\n# Public URL, CORS, and Origin Validation\n# -----------------------------------------------------------------------------\n\n# Canonical public origin for this deployment.\n# Leave empty for local development when the browser URL and app runtime URL match.\n# Set in production when the browser-facing URL differs from the internal URL seen\n# by the application process, such as when running behind an ingress, reverse\n# proxy, load balancer, gateway, edge network, or TLS terminator.\n#\n# Example:\n# APP_BASE_URL="https://yourdomain.com"\nAPP_BASE_URL=""\n\n# Additional browser origins allowed to call protected endpoints such as\n# PulsePoint/Caspian RPC. Use when the same deployed app is reachable from more\n# than one public origin, for example a primary domain plus an alternate domain.\n#\n# Multiple values must be comma-separated with no spaces.\n# Example:\n# CORS_ALLOWED_ORIGINS="https://primary.example.com,https://alternate.example.com"\nCORS_ALLOWED_ORIGINS=""\n\n# Only trust Forwarded/X-Forwarded-* headers when every request reaches the app\n# through trusted infrastructure that removes client-supplied forwarded headers\n# before adding its own. Keep false by default.\n#\n# When false, RPC origin checks use APP_BASE_URL, CORS_ALLOWED_ORIGINS, and the\n# direct request URL instead of trusting spoofable forwarded headers.\nTRUST_FORWARDED_HEADERS="false"\n\n# Allow cookies/Authorization headers on cross-origin requests from allowed\n# origins. Keep true only when credentialed cross-origin requests are required.\nCORS_ALLOW_CREDENTIALS="true"\n\n# Allowed HTTP methods for CORS preflight responses.\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\n\n# Allowed request headers for CORS preflight responses.\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\n\n# Response headers exposed to browser JavaScript.\nCORS_EXPOSE_HEADERS=""\n\n# Browser preflight cache duration in seconds.\nCORS_MAX_AGE="86400"\n\n\n# -----------------------------------------------------------------------------\n# Authentication and Sessions\n# -----------------------------------------------------------------------------\n\n# Authentication secret key for JWT/session signing or encryption.\n# Generate a unique strong value per app and per environment.\nAUTH_SECRET="${generateAuthSecret()}"\n\n# Authentication cookie name.\n# Use a unique value when multiple apps may share the same parent domain.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# Session lifetime in hours.\nSESSION_LIFETIME_HOURS="7"\n\n\n# -----------------------------------------------------------------------------\n# RPC and Request Security\n# -----------------------------------------------------------------------------\n\n# Secret key for encrypting or signing function calls.\n# Generate a unique strong value per app and per environment.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Maximum accepted request body size in megabytes.\nMAX_CONTENT_LENGTH_MB="16"\n\n\n# -----------------------------------------------------------------------------\n# Cache\n# -----------------------------------------------------------------------------\n\n# Enable or disable application cache.\nCACHE_ENABLED="false"\n\n# Cache time-to-live in seconds.\nCACHE_TTL="600"\n\n\n# -----------------------------------------------------------------------------\n# Rate Limiting\n# -----------------------------------------------------------------------------\n\n# Default request rate limit.\nRATE_LIMIT_DEFAULT="200 per minute"\n\n# RPC request rate limit.\nRATE_LIMIT_RPC="60 per minute"\n\n# Authentication-related request rate limit.\nRATE_LIMIT_AUTH="60 per minute"\n\n\n# -----------------------------------------------------------------------------\n# Server Process\n# -----------------------------------------------------------------------------\n\n# Uvicorn workers are separate OS processes for the same FastAPI app. More\n# workers can increase throughput under concurrent load, but they do not make a\n# single request faster and they duplicate memory, connection pools, and any\n# in-process state. Keep this at 1 unless the app is designed for multi-process\n# coordination and testing shows a real concurrency bottleneck.\nUVICORN_WORKERS="1"`;if(n.prisma){const n=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${i}`;await createOrUpdateEnvFile(e,n)}else await createOrUpdateEnvFile(e,i)}async function getAnswer(e={},n=!1){if(n)return{projectName:e.projectName??"my-app",backendOnly:e.backendOnly??!1,tailwindcss:e.tailwindcss??!1,typescript:e.typescript??!1,mcp:e.mcp??!1,prisma:e.prisma??!1};if(e.starterKit){const n=e.starterKit;let t=null;if(STARTER_KITS[n]&&(t=STARTER_KITS[n]),t){const s={projectName:e.projectName??"my-app",starterKit:n,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,prisma:t.features.prisma??!1,mcp:t.features.mcp??!1,typescript:t.features.typescript??!1},i=process.argv.slice(2);return i.includes("--backend-only")&&(s.backendOnly=!0),i.includes("--tailwindcss")&&(s.tailwindcss=!0),i.includes("--mcp")&&(s.mcp=!0),i.includes("--prisma")&&(s.prisma=!0),i.includes("--typescript")&&(s.typescript=!0),s}if(e.starterKitSource){const t={projectName:e.projectName??"my-app",starterKit:n,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,prisma:!0,mcp:!1,typescript:!1},s=process.argv.slice(2);return s.includes("--backend-only")&&(t.backendOnly=!0),s.includes("--tailwindcss")&&(t.tailwindcss=!0),s.includes("--mcp")&&(t.mcp=!0),s.includes("--prisma")&&(t.prisma=!0),s.includes("--typescript")&&(t.typescript=!0),t}}const t=[];e.projectName||t.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||t.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const s=()=>{console.warn(chalk.red("Operation cancelled by the user.")),process.exit(0)},i=await prompts(t,{onCancel:s}),c=[];i.backendOnly??e.backendOnly??!1?(e.mcp||c.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.typescript||c.push({type:"toggle",name:"typescript",message:`Would you like to use ${chalk.blue("TypeScript")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||c.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"}));const a=await prompts(c,{onCancel:s});return{projectName:i.projectName?String(i.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:i.backendOnly??e.backendOnly??!1,tailwindcss:a.tailwindcss??e.tailwindcss??!1,typescript:a.typescript??e.typescript??!1,mcp:a.mcp??e.mcp??!1,prisma:a.prisma??e.prisma??!1}}async function uninstallNpmDependencies(e,n,t=!1){console.log("Uninstalling Node dependencies:"),n.forEach(e=>console.log(`- ${chalk.blue(e)}`));const s=buildManagedNpmCommand(["uninstall",t?"--save-dev":"--save",...n]);execSync(s,{stdio:"inherit",cwd:e})}function buildManagedNpmCommand(e){return`npm ${e.join(" ")} --ignore-scripts=false --min-release-age=0 --audit=false`}function fetchPackageVersion(e){return new Promise((n,t)=>{https.get(`https://registry.npmjs.org/${e}`,e=>{let s="";e.on("data",e=>s+=e),e.on("end",()=>{try{const e=JSON.parse(s);n(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}})}).on("error",e=>t(e))})}const readJsonFile=e=>{const n=fs.readFileSync(e,"utf8");return JSON.parse(n)};function compareVersions(e,n){const t=e.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/),s=n.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);if(!t||!s)return e.localeCompare(n);const i=t.slice(1,4).map(Number),c=s.slice(1,4).map(Number);for(let e=0;e<i.length;e++){if(i[e]>c[e])return 1;if(i[e]<c[e])return-1}const a=t[4]??null,r=s[4]??null;return a&&!r?-1:!a&&r?1:a&&r?a.localeCompare(r):0}function getInstalledPackageInfo(e){try{const n=execSync(buildManagedNpmCommand(["list","-g",e,"--depth=0"])).toString(),t=n.match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?)`));return t?{version:t[1],isLinked:n.includes(`${e}@`)&&n.includes("->")}:(console.error(`Package ${e} is not installed`),{version:null,isLinked:!1})}catch(e){return console.error(e instanceof Error?e.message:String(e)),{version:null,isLinked:!1}}}function isRunningFromNpxCache(e){const n=path.resolve(e).toLowerCase(),t=`${path.sep}_npx${path.sep}`.toLowerCase();return n.includes(t)}async function installNpmDependencies(e,n,t=!1){fs.existsSync(path.join(e,"package.json"))?console.log("Updating existing Node.js project..."):console.log("Initializing new Node.js project..."),fs.existsSync(path.join(e,"package.json"))||execSync(buildManagedNpmCommand(["init","-y"]),{stdio:"inherit",cwd:e}),console.log((t?"Installing development dependencies":"Installing dependencies")+":"),n.forEach(e=>console.log(`- ${chalk.blue(e)}`));const s=buildManagedNpmCommand(["install",...t?["--save-dev"]:[],...n]);execSync(s,{stdio:"inherit",cwd:e})}const npmPinnedVersions={"@tailwindcss/postcss":"4.3.0","@types/browser-sync":"2.29.1","@types/node":"25.9.1","@types/prompts":"2.4.9","browser-sync":"3.0.4",chalk:"5.6.2","chokidar-cli":"3.0.0",cssnano:"8.0.1","http-proxy-middleware":"4.0.0","npm-run-all":"4.1.5",postcss:"8.5.15","postcss-cli":"11.0.1",prompts:"2.4.2",tailwindcss:"4.3.0",tsx:"4.22.3",typescript:"6.0.3",vite:"8.0.10","fast-glob":"3.3.3","@lezer/common":"1.5.2","@lezer/python":"1.1.18","caspian-utils":"0.1.x","tailwind-merge":"3.6.0"};function npmPkg(e){return npmPinnedVersions[e]?`${e}@${npmPinnedVersions[e]}`:e}function removeDirectorySafe(e){if(fs.existsSync(e))try{return void fs.rmSync(e,{recursive:!0,force:!0,maxRetries:5,retryDelay:250})}catch(n){const t=n;if("win32"===globalThis.process?.platform&&("EPERM"===t.code||"EACCES"===t.code)){try{spawnSync("cmd",["/c","attrib","-R","-H","-S","/S","/D",`${e}\\*`],{stdio:"ignore"})}catch{}return void spawnSync("cmd",["/c","rd","/s","/q",e],{stdio:"ignore"})}throw n}}async function setupStarterKit(e,n){if(!n.starterKit)return;let t=null;if(STARTER_KITS[n.starterKit]?t=STARTER_KITS[n.starterKit]:n.starterKitSource&&(t={id:n.starterKit,name:`Custom Starter Kit (${n.starterKit})`,description:"Custom starter kit from external source",features:{},requiredFiles:[],source:{type:"git",url:n.starterKitSource}}),t){if(console.log(chalk.green(`Setting up ${t.name}...`)),t.source)try{const s=t.source.branch?`git clone -b ${t.source.branch} --depth 1 ${t.source.url} "${e}"`:`git clone --depth 1 ${t.source.url} "${e}"`;execSync(s,{stdio:"inherit"});removeDirectorySafe(path.join(e,".git")),console.log(chalk.blue("Starter kit cloned successfully!"));const i=path.join(e,"caspian.config.json");if(fs.existsSync(i))try{const t=JSON.parse(fs.readFileSync(i,"utf8")),s=e,c=bsConfigUrls(s);t.projectName=n.projectName,t.projectRootPath=s,t.bsTarget=c.bsTarget,t.bsPathRewrite=c.bsPathRewrite;const a=await fetchPackageVersion("create-caspian-app");t.version=t.version||a,fs.writeFileSync(i,JSON.stringify(t,null,2)),console.log(chalk.green("Updated caspian.config.json with new project details"))}catch(e){console.warn(chalk.yellow("Failed to update caspian.config.json, will create new one"))}}catch(e){throw console.error(chalk.red(`Failed to setup starter kit: ${e}`)),e}t.customSetup&&await t.customSetup(e,n),console.log(chalk.green(`āœ“ ${t.name} setup complete!`))}else console.warn(chalk.yellow(`Starter kit '${n.starterKit}' not found. Skipping...`))}function showStarterKits(){console.log(chalk.blue("\nšŸš€ Available Starter Kits:\n")),Object.values(STARTER_KITS).forEach(e=>{const n=e.source?" (Custom)":" (Built-in)";console.log(chalk.green(` ${e.id}${chalk.gray(n)}`)),console.log(` ${e.name}`),console.log(chalk.gray(` ${e.description}`)),e.source&&console.log(chalk.cyan(` Source: ${e.source.url}`));const t=Object.entries(e.features).filter(([,e])=>!0===e).map(([e])=>e).join(", ");t&&console.log(chalk.magenta(` Features: ${t}`)),console.log()}),console.log(chalk.yellow("Usage:")),console.log(" npx create-caspian-app my-project --starter-kit=basic"),console.log(" npx create-caspian-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo"),console.log()}function runCmd(e,n,t){const s=spawnSync(e,n,{cwd:t,stdio:"inherit",shell:!1,encoding:"utf8"});if(s.error)throw s.error;if(0!==s.status)throw new Error(`Command failed (${e} ${n.join(" ")}), exit=${s.status}`)}function tryRunCmd(e,n,t){const s=spawnSync(e,n,{cwd:t,stdio:"ignore",shell:!1,encoding:"utf8"});return!s.error&&0===s.status}function tryInstallUv(e){console.log(chalk.blue("uv not found. Attempting to install uv..."));const n=[{cmd:"py",args:["-m","pip","install","--upgrade","uv"]},{cmd:"python",args:["-m","pip","install","--upgrade","uv"]},{cmd:"python3",args:["-m","pip","install","--upgrade","uv"]}];for(const t of n)if(tryRunCmd(t.cmd,t.args,e))return!0;return!1}function resolveUvCommand(e){const n=[{cmd:"uv",argsPrefix:[]},{cmd:"py",argsPrefix:["-m","uv"]},{cmd:"python",argsPrefix:["-m","uv"]},{cmd:"python3",argsPrefix:["-m","uv"]}];for(const t of n)if(tryRunCmd(t.cmd,[...t.argsPrefix,"--version"],e))return t;if(tryInstallUv(e))for(const t of n)if(tryRunCmd(t.cmd,[...t.argsPrefix,"--version"],e))return t;throw new Error("Could not find or install uv. Install uv and ensure `uv`, `py`, or `python` is available in PATH.")}function buildPythonDependencies(e){const n=["fastapi==0.136.1","uvicorn==0.47.0","python-dotenv==1.2.2","jinja2==3.1.6","beautifulsoup4==4.14.3","slowapi==0.1.9","python-multipart==0.0.29","starsessions==2.2.1","httpx==0.28.1","werkzeug==3.1.8","cuid2==2.0.1","nanoid==2.0.0","python-ulid==3.1.0","cuid==0.4","caspian-utils~=0.3"];return e.mcp&&n.push("fastmcp==3.2.4"),e.prisma&&(n.push("psycopg2-binary==2.9.12"),n.push("asyncpg==0.31.0"),n.push("aiosqlite==0.22.1"),n.push("aiomysql==0.3.2")),n}function getPythonRequirementName(e){const n=e.trim().match(/^([A-Za-z0-9._-]+)/);return n?.[1]??null}function getPyProjectDependencyNames(e){const n=path.join(e,"pyproject.toml");if(!fs.existsSync(n))return new Set;const t=fs.readFileSync(n,"utf8").replace(/\r\n/g,"\n").match(/^[ \t]*dependencies[ \t]*=[ \t]*\[([\s\S]*?)\]/m);if(!t)return new Set;const s=new Set,i=/"([^"]+)"/g;let c;for(;null!==(c=i.exec(t[1]));){const e=c[1].trim().match(/^([A-Za-z0-9._-]+)/)?.[1];e&&s.add(e.toLowerCase())}return s}function ensurePyProjectExists(e){const n=path.join(e,"pyproject.toml");if(!fs.existsSync(n))throw new Error(`pyproject.toml not found at: ${n}`);let t=fs.readFileSync(n,"utf8");t=t.replace(/\r\n/g,"\n"),t.includes("package = false")||(t=t.includes("[tool.uv]")?t.replace("[tool.uv]","[tool.uv]\npackage = false"):`${t.trimEnd()}\n\n[tool.uv]\npackage = false\n`),fs.writeFileSync(n,t,"utf8")}async function ensurePythonVenvAndDeps(e,n,t=[]){console.log(chalk.green("\n=========================")),console.log(chalk.green("Python setup: syncing dependencies with uv")),console.log(chalk.green("=========================\n")),console.log(chalk.blue("Preparing pyproject.toml...")),ensurePyProjectExists(e);const s=path.join(e,"requirements.txt");fs.existsSync(s)&&(fs.unlinkSync(s),console.log(chalk.gray("Removed legacy requirements.txt")));const i=resolveUvCommand(e),c=path.join(e,".venv");fs.existsSync(c)?console.log(chalk.blue("Existing .venv detected. Reusing it so uv sync can update dependencies without replacing the environment.")):(console.log(chalk.blue("Creating the virtual environment with uv...")),runCmd(i.cmd,[...i.argsPrefix,"venv",".venv"],e));const a=buildPythonDependencies(n),r=a.map(e=>getPythonRequirementName(e)).filter(e=>null!==e);t.length>0&&(console.log(chalk.blue("Removing obsolete Python dependencies via uv remove...")),runCmd(i.cmd,[...i.argsPrefix,"remove",...t],e));const o=r.flatMap(e=>["--upgrade-package",e]);console.log(chalk.blue("Adding Python dependencies via uv add...")),runCmd(i.cmd,[...i.argsPrefix,"add",...o,...a],e),console.log(chalk.blue("Syncing dependencies...")),runCmd(i.cmd,[...i.argsPrefix,"sync"],e),console.log(chalk.green("\nāœ“ uv environment ready and dependencies installed.\n"))}async function main(){try{const e=process.argv.slice(2),n=e.includes("-y");let t=e[0];const s=e.find(e=>e.startsWith("--starter-kit=")),i=s?.split("=")[1],c=e.find(e=>e.startsWith("--starter-kit-source=")),a=c?.split("=")[1];if(e.includes("--list-starter-kits"))return void showStarterKits();let r=null,o=!1;if(t){const s=process.cwd(),c=path.join(s,"caspian.config.json");if(i&&a){o=!0;const s={projectName:t,starterKit:i,starterKitSource:a,backendOnly:e.includes("--backend-only"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};r=await getAnswer(s,n)}else if(fs.existsSync(c)){const i=readJsonFile(c);let a=[];i.excludeFiles?.map(e=>{const n=path.join(s,e);fs.existsSync(n)&&a.push(n.replace(/\\/g,"/"))}),updateAnswer={projectName:t,backendOnly:i.backendOnly,tailwindcss:i.tailwindcss,mcp:i.mcp,prisma:i.prisma,typescript:i.typescript,isUpdate:!0,componentScanDirs:i.componentScanDirs??[],excludeFiles:i.excludeFiles??[],excludeFilePath:a??[],filePath:s};const o={projectName:t,backendOnly:e.includes("--backend-only")||i.backendOnly,tailwindcss:e.includes("--tailwindcss")||i.tailwindcss,typescript:e.includes("--typescript")||i.typescript,prisma:e.includes("--prisma")||i.prisma,mcp:e.includes("--mcp")||i.mcp};r=await getAnswer(o,n),null!==r&&(updateAnswer={projectName:t,backendOnly:r.backendOnly,tailwindcss:r.tailwindcss,mcp:r.mcp,prisma:r.prisma,typescript:r.typescript,isUpdate:!0,componentScanDirs:i.componentScanDirs??[],excludeFiles:i.excludeFiles??[],excludeFilePath:a??[],filePath:s})}else{const s={projectName:t,starterKit:i,starterKitSource:a,backendOnly:e.includes("--backend-only"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};r=await getAnswer(s,n)}if(null===r)return void console.log(chalk.red("Installation cancelled."))}else r=await getAnswer({},n);if(null===r)return void console.warn(chalk.red("Installation cancelled."));const l=await fetchPackageVersion("create-caspian-app"),p=getInstalledPackageInfo("create-caspian-app");isRunningFromNpxCache(__dirname)?console.log(chalk.gray("Skipping global create-caspian-app update because this command is running from an npx cache package.")):p.isLinked?console.log(chalk.gray("Skipping global create-caspian-app update because the global install is linked.")):p.version?-1===compareVersions(p.version,l)&&(execSync(buildManagedNpmCommand(["uninstall","-g","create-caspian-app"]),{stdio:"inherit"}),execSync(buildManagedNpmCommand(["install","-g","create-caspian-app"]),{stdio:"inherit"})):execSync(buildManagedNpmCommand(["install","-g","create-caspian-app"]),{stdio:"inherit"});const d=process.cwd();let u;if(t)if(o){const n=path.join(d,t);fs.existsSync(n)||fs.mkdirSync(n,{recursive:!0}),u=n,await setupStarterKit(u,r),process.chdir(u);const s=path.join(u,"caspian.config.json");if(fs.existsSync(s)){const n=JSON.parse(fs.readFileSync(s,"utf8"));e.includes("--backend-only")&&(n.backendOnly=!0),e.includes("--tailwindcss")&&(n.tailwindcss=!0),e.includes("--typescript")&&(n.typescript=!0),e.includes("--mcp")&&(n.mcp=!0),e.includes("--prisma")&&(n.prisma=!0),r={...r,backendOnly:n.backendOnly,tailwindcss:n.tailwindcss,typescript:n.typescript,mcp:n.mcp,prisma:n.prisma};let t=[];n.excludeFiles?.map(e=>{const n=path.join(u,e);fs.existsSync(n)&&t.push(n.replace(/\\/g,"/"))}),updateAnswer={...r,isUpdate:!0,componentScanDirs:n.componentScanDirs??[],excludeFiles:n.excludeFiles??[],excludeFilePath:t??[],filePath:u}}}else{const e=path.join(d,"caspian.config.json"),n=path.join(d,t),s=path.join(n,"caspian.config.json");fs.existsSync(e)?u=d:fs.existsSync(n)&&fs.existsSync(s)?(u=n,process.chdir(n)):(fs.existsSync(n)||fs.mkdirSync(n,{recursive:!0}),u=n,process.chdir(n))}else fs.mkdirSync(r.projectName,{recursive:!0}),u=path.join(d,r.projectName),process.chdir(r.projectName);let m=[npmPkg("typescript"),npmPkg("@types/node"),npmPkg("tsx"),npmPkg("http-proxy-middleware"),npmPkg("chalk"),npmPkg("npm-run-all"),npmPkg("browser-sync"),npmPkg("@types/browser-sync"),npmPkg("@lezer/common"),npmPkg("@lezer/python"),npmPkg("caspian-utils")];r.prisma&&m.push(npmPkg("prompts"),npmPkg("@types/prompts")),r.tailwindcss&&m.push(npmPkg("tailwindcss"),npmPkg("postcss"),npmPkg("postcss-cli"),npmPkg("@tailwindcss/postcss"),npmPkg("cssnano"),npmPkg("tailwind-merge")),r.prisma&&execSync(buildManagedNpmCommand(["install","-g","prisma-client-python@latest"]),{stdio:"inherit"}),r.typescript&&!r.backendOnly&&m.push(npmPkg("vite"),npmPkg("fast-glob")),r.starterKit&&!o&&await setupStarterKit(u,r),await installNpmDependencies(u,m,!0);let y=[];if(t||execSync("npx tsc --init",{stdio:"inherit"}),await createDirectoryStructure(u,r),r.prisma&&execSync("npx ppy init --caspian",{stdio:"inherit"}),updateAnswer?.isUpdate){const e=[],n=[],t=e=>{try{const n=path.join(u,"package.json");if(fs.existsSync(n)){const t=JSON.parse(fs.readFileSync(n,"utf8"));return!!(t.dependencies&&t.dependencies[e]||t.devDependencies&&t.devDependencies[e])}return!1}catch{return!1}};if(updateAnswer.backendOnly){nonBackendFiles.forEach(e=>{const n=path.join(u,"src","app",e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});["js","css"].forEach(e=>{const n=path.join(u,"src","app",e);fs.existsSync(n)&&(fs.rmSync(n,{recursive:!0,force:!0}),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.tailwindcss){["postcss.config.js"].forEach(e=>{const n=path.join(u,e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});const s=path.join(u,"public","js","tailwind-merge.mjs");fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${s} was deleted successfully.`));const i=path.join(u,"public","js","bundle-mjs.mjs.map");fs.existsSync(i)&&(fs.unlinkSync(i),console.log(`${i} was deleted successfully.`));const c=path.join(u,"ts","tailwind-merge.ts");fs.existsSync(c)&&(fs.unlinkSync(c),console.log(`${c} was deleted successfully.`));["tailwindcss","postcss","postcss-cli","@tailwindcss/postcss","cssnano","tailwind-merge"].forEach(n=>{t(n)&&e.push(n)}),n.push("tailwind-merge")}if(r.tailwindcss){const e=path.join(u,"public","css","index.css");if(fs.existsSync(e))try{fs.unlinkSync(e),console.log(`${e} was deleted successfully.`)}catch(n){console.warn(chalk.yellow(`Failed to delete ${e}: ${n}`))}}if(!updateAnswer.mcp){["restart-mcp.ts"].forEach(e=>{const n=path.join(u,"settings",e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});const e=path.join(u,"src","lib","mcp");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("MCP folder was deleted successfully.")),n.push("fastmcp")}if(!updateAnswer.prisma){["prisma","@prisma/client","@prisma/internals","better-sqlite3","@prisma/adapter-better-sqlite3","mariadb","@prisma/adapter-mariadb","pg","@prisma/adapter-pg","@types/pg"].forEach(n=>{t(n)&&e.push(n)}),n.push("psycopg2-binary","asyncpg","aiosqlite","aiomysql")}if(!updateAnswer.typescript||updateAnswer.backendOnly){["vite.config.ts",path.join("settings","run-vite-watch.ts")].forEach(e=>{const n=path.join(u,e);fs.existsSync(n)&&(fs.unlinkSync(n),console.log(`${e} was deleted successfully.`))});const n=path.join(u,"ts");fs.existsSync(n)&&(fs.rmSync(n,{recursive:!0,force:!0}),console.log("ts folder was deleted successfully."));const s=path.join(u,"settings","vite-plugins");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("settings/vite-plugins folder was deleted successfully."));["vite","fast-glob"].forEach(n=>{t(n)&&e.push(n)})}const s=e=>Array.from(new Set(e)),i=s(e);i.length>0&&(console.log(`Uninstalling npm packages: ${i.join(", ")}`),await uninstallNpmDependencies(u,i,!0));const c=s(n),a=getPyProjectDependencyNames(u);y=c.filter(e=>a.has(e.toLowerCase())),y.length>0&&console.log(chalk.gray(`Python dependencies will be removed via uv remove: ${y.join(", ")}`))}if(!o||!fs.existsSync(path.join(u,"caspian.config.json"))){const e=u.replace(/\\/g,"\\"),n=bsConfigUrls(e),t={projectName:r.projectName,projectRootPath:e,bsTarget:n.bsTarget,bsPathRewrite:n.bsPathRewrite,backendOnly:r.backendOnly,tailwindcss:r.tailwindcss,mcp:r.mcp,prisma:r.prisma,typescript:r.typescript,version:l,componentScanDirs:updateAnswer?.componentScanDirs??["src"],excludeFiles:updateAnswer?.excludeFiles??[]};fs.writeFileSync(path.join(u,"caspian.config.json"),JSON.stringify(t,null,2),{flag:"w"})}await ensurePythonVenvAndDeps(u,r,y),console.log("\n=========================\n"),console.log(`${chalk.green("Success!")} Caspian project successfully created in ${chalk.green(u.replace(/\\/g,"/"))}!`),console.log("\n=========================")}catch(e){console.error("Error while creating the project:",e),process.exit(1)}}main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "0.3.0-rc.7",
3
+ "version": "0.3.0-rc.8",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",