create-prisma-php-app 4.0.0-alpha.2 → 4.0.0-alpha.21

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 (59) hide show
  1. package/dist/.htaccess +54 -41
  2. package/dist/bootstrap.php +143 -98
  3. package/dist/index.js +264 -99
  4. package/dist/settings/auto-swagger-docs.ts +196 -95
  5. package/dist/settings/bs-config.ts +56 -58
  6. package/dist/settings/files-list.json +1 -1
  7. package/dist/settings/restart-mcp.ts +58 -0
  8. package/dist/settings/restart-websocket.ts +51 -45
  9. package/dist/settings/utils.ts +240 -0
  10. package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
  11. package/dist/src/Lib/Auth/Auth.php +544 -0
  12. package/dist/src/Lib/Auth/AuthConfig.php +89 -0
  13. package/dist/src/Lib/CacheHandler.php +121 -0
  14. package/dist/src/Lib/ErrorHandler.php +322 -0
  15. package/dist/src/Lib/FileManager/UploadFile.php +383 -0
  16. package/dist/src/Lib/Headers/Boom.php +192 -0
  17. package/dist/src/Lib/IncludeTracker.php +59 -0
  18. package/dist/src/Lib/MCP/WeatherTools.php +104 -0
  19. package/dist/src/Lib/MCP/mcp-server.php +80 -0
  20. package/dist/src/Lib/MainLayout.php +230 -0
  21. package/dist/src/Lib/Middleware/AuthMiddleware.php +154 -0
  22. package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
  23. package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
  24. package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
  25. package/dist/src/Lib/PHPX/Fragment.php +32 -0
  26. package/dist/src/Lib/PHPX/IPHPX.php +22 -0
  27. package/dist/src/Lib/PHPX/PHPX.php +287 -0
  28. package/dist/src/Lib/PHPX/TemplateCompiler.php +641 -0
  29. package/dist/src/Lib/PHPX/TwMerge.php +346 -0
  30. package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
  31. package/dist/src/Lib/PartialRenderer.php +40 -0
  32. package/dist/src/Lib/PrismaPHPSettings.php +181 -0
  33. package/dist/src/Lib/Request.php +479 -0
  34. package/dist/src/Lib/Security/RateLimiter.php +33 -0
  35. package/dist/src/Lib/Set.php +102 -0
  36. package/dist/src/Lib/StateManager.php +127 -0
  37. package/dist/src/Lib/Validator.php +752 -0
  38. package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
  39. package/dist/src/Lib/Websocket/websocket-server.php +118 -0
  40. package/dist/src/app/error.php +1 -1
  41. package/dist/src/app/index.php +24 -5
  42. package/dist/src/app/js/index.js +1 -1
  43. package/dist/src/app/layout.php +2 -2
  44. package/package.json +1 -1
  45. package/dist/settings/restart-websocket.bat +0 -28
  46. package/dist/src/app/assets/images/prisma-php-black.svg +0 -6
  47. package/dist/websocket-server.php +0 -22
  48. package/vendor/autoload.php +0 -25
  49. package/vendor/composer/ClassLoader.php +0 -579
  50. package/vendor/composer/InstalledVersions.php +0 -359
  51. package/vendor/composer/LICENSE +0 -21
  52. package/vendor/composer/autoload_classmap.php +0 -10
  53. package/vendor/composer/autoload_namespaces.php +0 -9
  54. package/vendor/composer/autoload_psr4.php +0 -10
  55. package/vendor/composer/autoload_real.php +0 -38
  56. package/vendor/composer/autoload_static.php +0 -25
  57. package/vendor/composer/installed.json +0 -825
  58. package/vendor/composer/installed.php +0 -132
  59. package/vendor/composer/platform_check.php +0 -26
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{execSync}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.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const o=c.replace(/(?<!:)(\/\/+)/g,"/"),i=n.replace(/\/\/+/g,"/");return{bsTarget:`${o}/`,bsPathRewrite:{"^/":`/${i.startsWith("/")?i.substring(1):i}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch","tailwind:build":"postcss src/app/css/tailwind.css -o src/app/css/styles.css"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let o={...n.scripts};o.browserSync="tsx settings/bs-config.ts",o["browserSync:build"]="tsx settings/build.ts",o.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,o.build=`npm-run-all${s.tailwindcss?" tailwind:build":""} browserSync:build`,n.scripts=o,n.type="module",fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e){const s=path.join(e,"composer.json");if(checkExcludeFiles(s))return;let t;if(fs.existsSync(s)){{const e=fs.readFileSync(s,"utf8");t=JSON.parse(e)}t.autoload={"psr-4":{"":"src/"}},t.version="1.0.0",fs.writeFileSync(s,JSON.stringify(t,null,2))}}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\websocket"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&(s.includes("restart-websocket.ts")||s.includes("restart-websocket.bat")||s.includes("websocket-server.php")))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n cssnano: {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />'),n+='\n <script src="<?= Request::baseUrl; ?>/js/morphdom-umd.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${n}`:n),e=e.replace("</head>",`${c}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),s.websocket&&t.push({src:"/websocket-server.php",dest:"/websocket-server.php"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.docker&&n.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach((({src:s,dest:t})=>{const n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const o=fs.readFileSync(n,"utf8");fs.writeFileSync(c,o,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c=generateAuthSecret(),o=generateHexEncodedKey(),i=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${c}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# PHPMailer SMTP configuration (uncomment and set as needed)\n# SMTP_HOST="smtp.gmail.com" # Your SMTP host\n# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username\n# SMTP_PASSWORD="123456" # Your SMTP password\n# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port\n# SMTP_ENCRYPTION="ssl" # ssl or tls\n# MAIL_FROM="john.doe@gmail.com" # Sender email address\n# MAIL_FROM_NAME="John Doe" # Sender name\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# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${o}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"`;if(s.prisma){const s=`${'# 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,s)}else await createOrUpdateEnvFile(e,i)}async function getAnswer(e={}){const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||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=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly||e.backendOnly?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,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.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const o=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:o.swaggerDocs??e.swaggerDocs??!1,tailwindcss:o.tailwindcss??e.tailwindcss??!1,websocket:o.websocket??e.websocket??!1,prisma:o.prisma??e.prisma??!1,docker:o.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){s.forEach((e=>{}));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}}))})).on("error",(e=>t(e)))}))}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:null}catch(e){return null}}
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.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const o=c.replace(/(?<!:)(\/\/+)/g,"/"),i=n.replace(/\/\/+/g,"/");return{bsTarget:`${o}/`,bsPathRewrite:{"^/":`/${i.startsWith("/")?i.substring(1):i}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch","tailwind:build":"postcss src/app/css/tailwind.css -o src/app/css/styles.css"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let o={...n.scripts};o.browserSync="tsx settings/bs-config.ts",o["browserSync:build"]="tsx settings/build.ts",o.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,o.build=`npm-run-all${s.tailwindcss?" tailwind:build":""} browserSync:build`,n.scripts=o,n.type="module",fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e){checkExcludeFiles(path.join(e,"composer.json"))}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&s.includes("restart-websocket.ts"))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n cssnano: {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />'),n+='\n <script src="<?= Request::baseUrl; ?>/js/morphdom-umd.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${n}`:n),e=e.replace("</head>",`${c}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.docker&&n.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach((({src:s,dest:t})=>{const n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const o=fs.readFileSync(n,"utf8");fs.writeFileSync(c,o,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c=generateAuthSecret(),o=generateHexEncodedKey(),i=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${c}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# PHPMailer SMTP configuration (uncomment and set as needed)\n# SMTP_HOST="smtp.gmail.com" # Your SMTP host\n# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username\n# SMTP_PASSWORD="123456" # Your SMTP password\n# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port\n# SMTP_ENCRYPTION="ssl" # ssl or tls\n# MAIL_FROM="john.doe@gmail.com" # Sender email address\n# MAIL_FROM_NAME="John Doe" # Sender name\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# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${o}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins (CSV or JSON array)\nCORS_ALLOWED_ORIGINS=[]\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"`;if(s.prisma){const s=`${'# 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,s)}else await createOrUpdateEnvFile(e,i)}async function getAnswer(e={}){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=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly??e.backendOnly??!1?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,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.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const o=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:o.swaggerDocs??e.swaggerDocs??!1,tailwindcss:o.tailwindcss??e.tailwindcss??!1,websocket:o.websocket??e.websocket??!1,prisma:o.prisma??e.prisma??!1,docker:o.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){s.forEach((e=>{}));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}}))})).on("error",(e=>t(e)))}))}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:null}catch(e){return null}}
3
3
  /**
4
4
  * Install dependencies in the specified directory.
5
5
  * @param {string} baseDir - The base directory where to install the dependencies.
@@ -7,7 +7,11 @@ import{execSync}from"child_process";import fs from"fs";import{fileURLToPath}from
7
7
  * @param {boolean} [isDev=false] - Whether to install the dependencies as devDependencies.
8
8
  */
9
9
  async function installNpmDependencies(baseDir, dependencies, isDev = false) {
10
- console.log("Initializing new Node.js project...");
10
+ if (!fs.existsSync(path.join(baseDir, "package.json"))) {
11
+ console.log("Initializing new Node.js project...");
12
+ } else {
13
+ console.log("Updating existing Node.js project...");
14
+ }
11
15
  // Initialize a package.json if it doesn't exist
12
16
  if (!fs.existsSync(path.join(baseDir, "package.json"))) {
13
17
  execSync("npm init -y", {
@@ -32,27 +36,99 @@ async function installNpmDependencies(baseDir, dependencies, isDev = false) {
32
36
  cwd: baseDir,
33
37
  });
34
38
  }
35
- async function installComposerDependencies(baseDir, dependencies) {
36
- console.log("Initializing new Composer project...");
37
- // Initialize a composer.json if it doesn't exist
38
- if (!fs.existsSync(path.join(baseDir, "composer.json"))) {
39
+ function getComposerCmd() {
40
+ try {
41
+ execSync("composer --version", { stdio: "ignore" });
42
+ return { cmd: "composer", baseArgs: [] };
43
+ } catch {
44
+ return {
45
+ cmd: "C:\\xampp\\php\\php.exe",
46
+ baseArgs: ["C:\\ProgramData\\ComposerSetup\\bin\\composer.phar"],
47
+ };
48
+ }
49
+ }
50
+ export async function installComposerDependencies(baseDir, dependencies) {
51
+ const { cmd, baseArgs } = getComposerCmd();
52
+ const composerJsonPath = path.join(baseDir, "composer.json");
53
+ const existsAlready = fs.existsSync(composerJsonPath);
54
+ console.log(
55
+ chalk.green(
56
+ `Composer project initialization: ${
57
+ existsAlready ? "Updating existing project…" : "Setting up new project…"
58
+ }`
59
+ )
60
+ );
61
+ /* ------------------------------------------------------------------ */
62
+ /* 1. Try composer init (quietly fall back if it fails) */
63
+ /* ------------------------------------------------------------------ */
64
+ if (!existsAlready) {
65
+ const initArgs = [
66
+ ...baseArgs,
67
+ "init",
68
+ "--no-interaction",
69
+ "--name",
70
+ "tsnc/prisma-php-app",
71
+ "--require",
72
+ "php:^8.2",
73
+ "--type",
74
+ "project",
75
+ "--version",
76
+ "1.0.0",
77
+ ];
78
+ const res = spawnSync(cmd, initArgs, { cwd: baseDir });
79
+ if (res.status !== 0) {
80
+ // Silent fallback: no logs, just write a minimal composer.json
81
+ fs.writeFileSync(
82
+ composerJsonPath,
83
+ JSON.stringify(
84
+ {
85
+ name: "tsnc/prisma-php-app",
86
+ type: "project",
87
+ version: "1.0.0",
88
+ require: { php: "^8.2" },
89
+ autoload: { "psr-4": { "": "src/" } },
90
+ },
91
+ null,
92
+ 2
93
+ )
94
+ );
95
+ }
96
+ }
97
+ /* 2. Ensure PSR-4 autoload entry ---------------------------------- */
98
+ const json = JSON.parse(fs.readFileSync(composerJsonPath, "utf8"));
99
+ json.autoload ??= {};
100
+ json.autoload["psr-4"] ??= {};
101
+ json.autoload["psr-4"][""] ??= "src/";
102
+ fs.writeFileSync(composerJsonPath, JSON.stringify(json, null, 2));
103
+ /* 3. Install dependencies ----------------------------------------- */
104
+ if (dependencies.length) {
105
+ console.log("Installing Composer dependencies:");
106
+ dependencies.forEach((d) => console.log(`- ${chalk.blue(d)}`));
39
107
  execSync(
40
- `composer init -n --name="tsnc/prisma-php-app" --require="php:^8.2"`,
41
- {
42
- stdio: "inherit",
43
- cwd: baseDir,
44
- }
108
+ `${cmd} ${[
109
+ ...baseArgs,
110
+ "require",
111
+ "--no-interaction",
112
+ ...dependencies,
113
+ ].join(" ")}`,
114
+ { stdio: "inherit", cwd: baseDir }
45
115
  );
46
116
  }
47
- // Log the dependencies being installed
48
- console.log("Installing Composer dependencies:");
49
- dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
50
- // Prepare the composer require command
51
- const composerRequireCommand = `C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar require ${dependencies.join(
52
- " "
53
- )}`;
54
- // Execute the composer require command
55
- execSync(composerRequireCommand, {
117
+ /* 4. Refresh lock when updating ----------------------------------- */
118
+ if (existsAlready) {
119
+ execSync(
120
+ `${cmd} ${[
121
+ ...baseArgs,
122
+ "update",
123
+ "--lock",
124
+ "--no-install",
125
+ "--no-interaction",
126
+ ].join(" ")}`,
127
+ { stdio: "inherit", cwd: baseDir }
128
+ );
129
+ }
130
+ /* 5. Regenerate autoloader ---------------------------------------- */
131
+ execSync(`${cmd} ${[...baseArgs, "dump-autoload", "--quiet"].join(" ")}`, {
56
132
  stdio: "inherit",
57
133
  cwd: baseDir,
58
134
  });
@@ -60,21 +136,21 @@ async function installComposerDependencies(baseDir, dependencies) {
60
136
  const npmPinnedVersions = {
61
137
  "@tailwindcss/postcss": "^4.1.11",
62
138
  "@types/browser-sync": "^2.29.0",
63
- "@types/node": "^24.0.7",
139
+ "@types/node": "^24.2.1",
64
140
  "@types/prompts": "^2.4.9",
65
141
  "browser-sync": "^3.0.4",
66
- chalk: "^5.4.1",
142
+ chalk: "^5.5.0",
67
143
  "chokidar-cli": "^3.0.0",
68
- cssnano: "^7.0.7",
144
+ cssnano: "^7.1.0",
69
145
  "http-proxy-middleware": "^3.0.5",
70
146
  "npm-run-all": "^4.1.5",
71
- "php-parser": "^3.2.4",
147
+ "php-parser": "^3.2.5",
72
148
  postcss: "^8.5.6",
73
149
  "postcss-cli": "^11.0.1",
74
150
  prompts: "^2.4.2",
75
151
  tailwindcss: "^4.1.11",
76
152
  tsx: "^4.20.3",
77
- typescript: "^5.8.3",
153
+ typescript: "^5.9.2",
78
154
  };
79
155
  function npmPkg(name) {
80
156
  return npmPinnedVersions[name] ? `${name}@${npmPinnedVersions[name]}` : name;
@@ -88,7 +164,7 @@ const composerPinnedVersions = {
88
164
  "symfony/uid": "^7.2.0",
89
165
  "brick/math": "^0.13.1",
90
166
  "cboden/ratchet": "^0.4.4",
91
- "tsnc/prisma-php": "^1.0.1",
167
+ "tsnc/prisma-php": "^1.0.0",
92
168
  };
93
169
  function composerPkg(name) {
94
170
  return composerPinnedVersions[name]
@@ -101,29 +177,11 @@ async function main() {
101
177
  let projectName = args[0];
102
178
  let answer = null;
103
179
  if (projectName) {
104
- let useBackendOnly = args.includes("--backend-only");
105
- let useSwaggerDocs = args.includes("--swagger-docs");
106
- let useTailwind = args.includes("--tailwindcss");
107
- let useWebsocket = args.includes("--websocket");
108
- let usePrisma = args.includes("--prisma");
109
- let useDocker = args.includes("--docker");
110
- const predefinedAnswers = {
111
- projectName,
112
- backendOnly: useBackendOnly,
113
- swaggerDocs: useSwaggerDocs,
114
- tailwindcss: useTailwind,
115
- websocket: useWebsocket,
116
- prisma: usePrisma,
117
- docker: useDocker,
118
- };
119
- answer = await getAnswer(predefinedAnswers);
120
- if (answer === null) {
121
- console.log(chalk.red("Installation cancelled."));
122
- return;
123
- }
180
+ // Check if it's an update FIRST
124
181
  const currentDir = process.cwd();
125
182
  const configPath = path.join(currentDir, "prisma-php.json");
126
183
  if (fs.existsSync(configPath)) {
184
+ // It's an update - read existing settings
127
185
  const localSettings = readJsonFile(configPath);
128
186
  let excludeFiles = [];
129
187
  localSettings.excludeFiles?.map((file) => {
@@ -131,21 +189,75 @@ async function main() {
131
189
  if (fs.existsSync(filePath))
132
190
  excludeFiles.push(filePath.replace(/\\/g, "/"));
133
191
  });
192
+ // Set updateAnswer with OLD settings initially (for checkExcludeFiles function)
134
193
  updateAnswer = {
135
194
  projectName,
136
- backendOnly: answer?.backendOnly ?? false,
137
- swaggerDocs: answer?.swaggerDocs ?? false,
138
- tailwindcss: answer?.tailwindcss ?? false,
139
- websocket: answer?.websocket ?? false,
140
- prisma: answer?.prisma ?? false,
141
- docker: answer?.docker ?? false,
195
+ backendOnly: localSettings.backendOnly,
196
+ swaggerDocs: localSettings.swaggerDocs,
197
+ tailwindcss: localSettings.tailwindcss,
198
+ websocket: localSettings.websocket,
199
+ prisma: localSettings.prisma,
200
+ docker: localSettings.docker,
142
201
  isUpdate: true,
143
202
  excludeFiles: localSettings.excludeFiles ?? [],
144
203
  excludeFilePath: excludeFiles ?? [],
145
204
  filePath: currentDir,
146
205
  };
206
+ // For updates, use existing settings but allow CLI overrides
207
+ const predefinedAnswers = {
208
+ projectName,
209
+ backendOnly:
210
+ args.includes("--backend-only") || localSettings.backendOnly,
211
+ swaggerDocs:
212
+ args.includes("--swagger-docs") || localSettings.swaggerDocs,
213
+ tailwindcss:
214
+ args.includes("--tailwindcss") || localSettings.tailwindcss,
215
+ websocket: args.includes("--websocket") || localSettings.websocket,
216
+ prisma: args.includes("--prisma") || localSettings.prisma,
217
+ docker: args.includes("--docker") || localSettings.docker,
218
+ };
219
+ answer = await getAnswer(predefinedAnswers);
220
+ // IMPORTANT: Update updateAnswer with the NEW answer after getting user input
221
+ if (answer !== null) {
222
+ updateAnswer = {
223
+ projectName,
224
+ backendOnly: answer.backendOnly,
225
+ swaggerDocs: answer.swaggerDocs,
226
+ tailwindcss: answer.tailwindcss,
227
+ websocket: answer.websocket,
228
+ prisma: answer.prisma,
229
+ docker: answer.docker,
230
+ isUpdate: true,
231
+ excludeFiles: localSettings.excludeFiles ?? [],
232
+ excludeFilePath: excludeFiles ?? [],
233
+ filePath: currentDir,
234
+ };
235
+ }
236
+ } else {
237
+ // It's a new project - use CLI arguments
238
+ let useBackendOnly = args.includes("--backend-only");
239
+ let useSwaggerDocs = args.includes("--swagger-docs");
240
+ let useTailwind = args.includes("--tailwindcss");
241
+ let useWebsocket = args.includes("--websocket");
242
+ let usePrisma = args.includes("--prisma");
243
+ let useDocker = args.includes("--docker");
244
+ const predefinedAnswers = {
245
+ projectName,
246
+ backendOnly: useBackendOnly,
247
+ swaggerDocs: useSwaggerDocs,
248
+ tailwindcss: useTailwind,
249
+ websocket: useWebsocket,
250
+ prisma: usePrisma,
251
+ docker: useDocker,
252
+ };
253
+ answer = await getAnswer(predefinedAnswers);
254
+ }
255
+ if (answer === null) {
256
+ console.log(chalk.red("Installation cancelled."));
257
+ return;
147
258
  }
148
259
  } else {
260
+ // No project name provided - interactive mode
149
261
  answer = await getAnswer();
150
262
  }
151
263
  if (answer === null) {
@@ -201,7 +313,7 @@ async function main() {
201
313
  composerPkg("ezyang/htmlpurifier"),
202
314
  composerPkg("symfony/uid"),
203
315
  composerPkg("brick/math"),
204
- composerPkg("tsnc/prisma-php"),
316
+ // composerPkg("tsnc/prisma-php"),
205
317
  ];
206
318
  if (answer.swaggerDocs) {
207
319
  npmDependencies.push(
@@ -266,24 +378,57 @@ async function main() {
266
378
  if (updateAnswer?.isUpdate) {
267
379
  const updateUninstallNpmDependencies = [];
268
380
  const updateUninstallComposerDependencies = [];
381
+ // Helper function to check if a composer package is installed
382
+ const isComposerPackageInstalled = (packageName) => {
383
+ try {
384
+ const composerJsonPath = path.join(projectPath, "composer.json");
385
+ if (fs.existsSync(composerJsonPath)) {
386
+ const composerJson = JSON.parse(
387
+ fs.readFileSync(composerJsonPath, "utf8")
388
+ );
389
+ return !!(
390
+ composerJson.require && composerJson.require[packageName]
391
+ );
392
+ }
393
+ return false;
394
+ } catch {
395
+ return false;
396
+ }
397
+ };
398
+ // Helper function to check if an npm package is installed
399
+ const isNpmPackageInstalled = (packageName) => {
400
+ try {
401
+ const packageJsonPath = path.join(projectPath, "package.json");
402
+ if (fs.existsSync(packageJsonPath)) {
403
+ const packageJson = JSON.parse(
404
+ fs.readFileSync(packageJsonPath, "utf8")
405
+ );
406
+ return !!(
407
+ (packageJson.dependencies &&
408
+ packageJson.dependencies[packageName]) ||
409
+ (packageJson.devDependencies &&
410
+ packageJson.devDependencies[packageName])
411
+ );
412
+ }
413
+ return false;
414
+ } catch {
415
+ return false;
416
+ }
417
+ };
269
418
  if (updateAnswer.backendOnly) {
270
419
  nonBackendFiles.forEach((file) => {
271
420
  const filePath = path.join(projectPath, "src", "app", file);
272
421
  if (fs.existsSync(filePath)) {
273
- fs.unlinkSync(filePath); // Delete each file if it exists
422
+ fs.unlinkSync(filePath);
274
423
  console.log(`${file} was deleted successfully.`);
275
- } else {
276
- console.log(`${file} does not exist.`);
277
424
  }
278
425
  });
279
426
  const backendOnlyFolders = ["js", "css"];
280
427
  backendOnlyFolders.forEach((folder) => {
281
428
  const folderPath = path.join(projectPath, "src", "app", folder);
282
429
  if (fs.existsSync(folderPath)) {
283
- fs.rmSync(folderPath, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
430
+ fs.rmSync(folderPath, { recursive: true, force: true });
284
431
  console.log(`${folder} was deleted successfully.`);
285
- } else {
286
- console.log(`${folder} does not exist.`);
287
432
  }
288
433
  });
289
434
  }
@@ -295,81 +440,92 @@ async function main() {
295
440
  "swagger-docs"
296
441
  );
297
442
  if (fs.existsSync(swaggerDocsFolder)) {
298
- fs.rmSync(swaggerDocsFolder, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
443
+ fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
299
444
  console.log(`swagger-docs was deleted successfully.`);
300
445
  }
301
446
  const swaggerFiles = ["swagger-config.ts"];
302
447
  swaggerFiles.forEach((file) => {
303
448
  const filePath = path.join(projectPath, "settings", file);
304
449
  if (fs.existsSync(filePath)) {
305
- fs.unlinkSync(filePath); // Delete each file if it exists
450
+ fs.unlinkSync(filePath);
306
451
  console.log(`${file} was deleted successfully.`);
307
- } else {
308
- console.log(`${file} does not exist.`);
309
452
  }
310
453
  });
311
- updateUninstallNpmDependencies.push(
312
- "swagger-jsdoc",
313
- "@types/swagger-jsdoc",
314
- "prompts",
315
- "@types/prompts"
316
- );
454
+ // Only add to uninstall list if packages are actually installed
455
+ if (isNpmPackageInstalled("swagger-jsdoc")) {
456
+ updateUninstallNpmDependencies.push("swagger-jsdoc");
457
+ }
458
+ if (isNpmPackageInstalled("@types/swagger-jsdoc")) {
459
+ updateUninstallNpmDependencies.push("@types/swagger-jsdoc");
460
+ }
461
+ if (isNpmPackageInstalled("prompts")) {
462
+ updateUninstallNpmDependencies.push("prompts");
463
+ }
464
+ if (isNpmPackageInstalled("@types/prompts")) {
465
+ updateUninstallNpmDependencies.push("@types/prompts");
466
+ }
317
467
  }
318
468
  if (!updateAnswer.tailwindcss) {
319
469
  const tailwindFiles = ["postcss.config.js"];
320
470
  tailwindFiles.forEach((file) => {
321
471
  const filePath = path.join(projectPath, file);
322
472
  if (fs.existsSync(filePath)) {
323
- fs.unlinkSync(filePath); // Delete each file if it exists
473
+ fs.unlinkSync(filePath);
324
474
  console.log(`${file} was deleted successfully.`);
325
- } else {
326
- console.log(`${file} does not exist.`);
327
475
  }
328
476
  });
329
- updateUninstallNpmDependencies.push(
477
+ // Only add to uninstall list if packages are actually installed
478
+ const tailwindPackages = [
330
479
  "tailwindcss",
331
480
  "postcss",
332
481
  "postcss-cli",
333
482
  "@tailwindcss/postcss",
334
- "cssnano"
335
- );
483
+ "cssnano",
484
+ ];
485
+ tailwindPackages.forEach((pkg) => {
486
+ if (isNpmPackageInstalled(pkg)) {
487
+ updateUninstallNpmDependencies.push(pkg);
488
+ }
489
+ });
336
490
  }
337
491
  if (!updateAnswer.websocket) {
338
- const websocketFiles = [
339
- "restart-websocket.ts",
340
- "restart-websocket.bat",
341
- ];
492
+ const websocketFiles = ["restart-websocket.ts"];
342
493
  websocketFiles.forEach((file) => {
343
494
  const filePath = path.join(projectPath, "settings", file);
344
495
  if (fs.existsSync(filePath)) {
345
- fs.unlinkSync(filePath); // Delete each file if it exists
496
+ fs.unlinkSync(filePath);
346
497
  console.log(`${file} was deleted successfully.`);
347
- } else {
348
- console.log(`${file} does not exist.`);
349
498
  }
350
499
  });
351
- const websocketFolder = path.join(projectPath, "src", "Websocket");
500
+ const websocketFolder = path.join(
501
+ projectPath,
502
+ "src",
503
+ "Lib",
504
+ "Websocket"
505
+ );
352
506
  if (fs.existsSync(websocketFolder)) {
353
- fs.rmSync(websocketFolder, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
507
+ fs.rmSync(websocketFolder, { recursive: true, force: true });
354
508
  console.log(`Websocket folder was deleted successfully.`);
355
509
  }
356
- const websocketServerFile = path.join(
357
- projectPath,
358
- "websocket-server.php"
359
- );
360
- if (fs.existsSync(websocketServerFile)) {
361
- fs.unlinkSync(websocketServerFile); // Delete the file if it exists
362
- console.log(`websocket-server.php was deleted successfully.`);
510
+ // Only add to uninstall list if packages are actually installed
511
+ if (isNpmPackageInstalled("chokidar-cli")) {
512
+ updateUninstallNpmDependencies.push("chokidar-cli");
513
+ }
514
+ if (isComposerPackageInstalled("cboden/ratchet")) {
515
+ updateUninstallComposerDependencies.push("cboden/ratchet");
363
516
  }
364
- updateUninstallNpmDependencies.push("chokidar-cli");
365
- updateUninstallComposerDependencies.push("cboden/ratchet");
366
517
  }
367
518
  if (!updateAnswer.prisma) {
368
- updateUninstallNpmDependencies.push(
519
+ const prismaPackages = [
369
520
  "prisma",
370
521
  "@prisma/client",
371
- "@prisma/internals"
372
- );
522
+ "@prisma/internals",
523
+ ];
524
+ prismaPackages.forEach((pkg) => {
525
+ if (isNpmPackageInstalled(pkg)) {
526
+ updateUninstallNpmDependencies.push(pkg);
527
+ }
528
+ });
373
529
  }
374
530
  if (!updateAnswer.docker) {
375
531
  const dockerFiles = [
@@ -381,14 +537,18 @@ async function main() {
381
537
  dockerFiles.forEach((file) => {
382
538
  const filePath = path.join(projectPath, file);
383
539
  if (fs.existsSync(filePath)) {
384
- fs.unlinkSync(filePath); // Delete each file if it exists
540
+ fs.unlinkSync(filePath);
385
541
  console.log(`${file} was deleted successfully.`);
386
- } else {
387
- console.log(`${file} does not exist.`);
388
542
  }
389
543
  });
390
544
  }
545
+ // Only uninstall if there are packages to uninstall
391
546
  if (updateUninstallNpmDependencies.length > 0) {
547
+ console.log(
548
+ `Uninstalling npm packages: ${updateUninstallNpmDependencies.join(
549
+ ", "
550
+ )}`
551
+ );
392
552
  await uninstallNpmDependencies(
393
553
  projectPath,
394
554
  updateUninstallNpmDependencies,
@@ -396,6 +556,11 @@ async function main() {
396
556
  );
397
557
  }
398
558
  if (updateUninstallComposerDependencies.length > 0) {
559
+ console.log(
560
+ `Uninstalling composer packages: ${updateUninstallComposerDependencies.join(
561
+ ", "
562
+ )}`
563
+ );
399
564
  await uninstallComposerDependencies(
400
565
  projectPath,
401
566
  updateUninstallComposerDependencies