create-prisma-php-app 4.0.0-alpha.19 → 4.0.0-alpha.20

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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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.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")||s.includes("restart-websocket.bat")))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)}"`;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:!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)}"`;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,spawnSync}from"child_process";import fs from"fs";import{fileURLT
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", {
@@ -132,12 +136,12 @@ export async function installComposerDependencies(baseDir, dependencies) {
132
136
  const npmPinnedVersions = {
133
137
  "@tailwindcss/postcss": "^4.1.11",
134
138
  "@types/browser-sync": "^2.29.0",
135
- "@types/node": "^24.0.13",
139
+ "@types/node": "^24.2.1",
136
140
  "@types/prompts": "^2.4.9",
137
141
  "browser-sync": "^3.0.4",
138
- chalk: "^5.4.1",
142
+ chalk: "^5.5.0",
139
143
  "chokidar-cli": "^3.0.0",
140
- cssnano: "^7.0.7",
144
+ cssnano: "^7.1.0",
141
145
  "http-proxy-middleware": "^3.0.5",
142
146
  "npm-run-all": "^4.1.5",
143
147
  "php-parser": "^3.2.5",
@@ -146,7 +150,7 @@ const npmPinnedVersions = {
146
150
  prompts: "^2.4.2",
147
151
  tailwindcss: "^4.1.11",
148
152
  tsx: "^4.20.3",
149
- typescript: "^5.8.3",
153
+ typescript: "^5.9.2",
150
154
  };
151
155
  function npmPkg(name) {
152
156
  return npmPinnedVersions[name] ? `${name}@${npmPinnedVersions[name]}` : name;
@@ -173,29 +177,11 @@ async function main() {
173
177
  let projectName = args[0];
174
178
  let answer = null;
175
179
  if (projectName) {
176
- let useBackendOnly = args.includes("--backend-only");
177
- let useSwaggerDocs = args.includes("--swagger-docs");
178
- let useTailwind = args.includes("--tailwindcss");
179
- let useWebsocket = args.includes("--websocket");
180
- let usePrisma = args.includes("--prisma");
181
- let useDocker = args.includes("--docker");
182
- const predefinedAnswers = {
183
- projectName,
184
- backendOnly: useBackendOnly,
185
- swaggerDocs: useSwaggerDocs,
186
- tailwindcss: useTailwind,
187
- websocket: useWebsocket,
188
- prisma: usePrisma,
189
- docker: useDocker,
190
- };
191
- answer = await getAnswer(predefinedAnswers);
192
- if (answer === null) {
193
- console.log(chalk.red("Installation cancelled."));
194
- return;
195
- }
180
+ // Check if it's an update FIRST
196
181
  const currentDir = process.cwd();
197
182
  const configPath = path.join(currentDir, "prisma-php.json");
198
183
  if (fs.existsSync(configPath)) {
184
+ // It's an update - read existing settings
199
185
  const localSettings = readJsonFile(configPath);
200
186
  let excludeFiles = [];
201
187
  localSettings.excludeFiles?.map((file) => {
@@ -203,21 +189,75 @@ async function main() {
203
189
  if (fs.existsSync(filePath))
204
190
  excludeFiles.push(filePath.replace(/\\/g, "/"));
205
191
  });
192
+ // Set updateAnswer with OLD settings initially (for checkExcludeFiles function)
206
193
  updateAnswer = {
207
194
  projectName,
208
- backendOnly: answer?.backendOnly ?? false,
209
- swaggerDocs: answer?.swaggerDocs ?? false,
210
- tailwindcss: answer?.tailwindcss ?? false,
211
- websocket: answer?.websocket ?? false,
212
- prisma: answer?.prisma ?? false,
213
- 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,
214
201
  isUpdate: true,
215
202
  excludeFiles: localSettings.excludeFiles ?? [],
216
203
  excludeFilePath: excludeFiles ?? [],
217
204
  filePath: currentDir,
218
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;
219
258
  }
220
259
  } else {
260
+ // No project name provided - interactive mode
221
261
  answer = await getAnswer();
222
262
  }
223
263
  if (answer === null) {
@@ -273,7 +313,7 @@ async function main() {
273
313
  composerPkg("ezyang/htmlpurifier"),
274
314
  composerPkg("symfony/uid"),
275
315
  composerPkg("brick/math"),
276
- composerPkg("tsnc/prisma-php"),
316
+ // composerPkg("tsnc/prisma-php"),
277
317
  ];
278
318
  if (answer.swaggerDocs) {
279
319
  npmDependencies.push(
@@ -338,24 +378,57 @@ async function main() {
338
378
  if (updateAnswer?.isUpdate) {
339
379
  const updateUninstallNpmDependencies = [];
340
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
+ };
341
418
  if (updateAnswer.backendOnly) {
342
419
  nonBackendFiles.forEach((file) => {
343
420
  const filePath = path.join(projectPath, "src", "app", file);
344
421
  if (fs.existsSync(filePath)) {
345
- fs.unlinkSync(filePath); // Delete each file if it exists
422
+ fs.unlinkSync(filePath);
346
423
  console.log(`${file} was deleted successfully.`);
347
- } else {
348
- console.log(`${file} does not exist.`);
349
424
  }
350
425
  });
351
426
  const backendOnlyFolders = ["js", "css"];
352
427
  backendOnlyFolders.forEach((folder) => {
353
428
  const folderPath = path.join(projectPath, "src", "app", folder);
354
429
  if (fs.existsSync(folderPath)) {
355
- fs.rmSync(folderPath, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
430
+ fs.rmSync(folderPath, { recursive: true, force: true });
356
431
  console.log(`${folder} was deleted successfully.`);
357
- } else {
358
- console.log(`${folder} does not exist.`);
359
432
  }
360
433
  });
361
434
  }
@@ -367,57 +440,61 @@ async function main() {
367
440
  "swagger-docs"
368
441
  );
369
442
  if (fs.existsSync(swaggerDocsFolder)) {
370
- fs.rmSync(swaggerDocsFolder, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
443
+ fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
371
444
  console.log(`swagger-docs was deleted successfully.`);
372
445
  }
373
446
  const swaggerFiles = ["swagger-config.ts"];
374
447
  swaggerFiles.forEach((file) => {
375
448
  const filePath = path.join(projectPath, "settings", file);
376
449
  if (fs.existsSync(filePath)) {
377
- fs.unlinkSync(filePath); // Delete each file if it exists
450
+ fs.unlinkSync(filePath);
378
451
  console.log(`${file} was deleted successfully.`);
379
- } else {
380
- console.log(`${file} does not exist.`);
381
452
  }
382
453
  });
383
- updateUninstallNpmDependencies.push(
384
- "swagger-jsdoc",
385
- "@types/swagger-jsdoc",
386
- "prompts",
387
- "@types/prompts"
388
- );
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
+ }
389
467
  }
390
468
  if (!updateAnswer.tailwindcss) {
391
469
  const tailwindFiles = ["postcss.config.js"];
392
470
  tailwindFiles.forEach((file) => {
393
471
  const filePath = path.join(projectPath, file);
394
472
  if (fs.existsSync(filePath)) {
395
- fs.unlinkSync(filePath); // Delete each file if it exists
473
+ fs.unlinkSync(filePath);
396
474
  console.log(`${file} was deleted successfully.`);
397
- } else {
398
- console.log(`${file} does not exist.`);
399
475
  }
400
476
  });
401
- updateUninstallNpmDependencies.push(
477
+ // Only add to uninstall list if packages are actually installed
478
+ const tailwindPackages = [
402
479
  "tailwindcss",
403
480
  "postcss",
404
481
  "postcss-cli",
405
482
  "@tailwindcss/postcss",
406
- "cssnano"
407
- );
483
+ "cssnano",
484
+ ];
485
+ tailwindPackages.forEach((pkg) => {
486
+ if (isNpmPackageInstalled(pkg)) {
487
+ updateUninstallNpmDependencies.push(pkg);
488
+ }
489
+ });
408
490
  }
409
491
  if (!updateAnswer.websocket) {
410
- const websocketFiles = [
411
- "restart-websocket.ts",
412
- "restart-websocket.bat",
413
- ];
492
+ const websocketFiles = ["restart-websocket.ts"];
414
493
  websocketFiles.forEach((file) => {
415
494
  const filePath = path.join(projectPath, "settings", file);
416
495
  if (fs.existsSync(filePath)) {
417
- fs.unlinkSync(filePath); // Delete each file if it exists
496
+ fs.unlinkSync(filePath);
418
497
  console.log(`${file} was deleted successfully.`);
419
- } else {
420
- console.log(`${file} does not exist.`);
421
498
  }
422
499
  });
423
500
  const websocketFolder = path.join(
@@ -427,18 +504,28 @@ async function main() {
427
504
  "Websocket"
428
505
  );
429
506
  if (fs.existsSync(websocketFolder)) {
430
- fs.rmSync(websocketFolder, { recursive: true, force: true }); // Use fs.rmSync instead of fs.rmdirSync
507
+ fs.rmSync(websocketFolder, { recursive: true, force: true });
431
508
  console.log(`Websocket folder was deleted successfully.`);
432
509
  }
433
- updateUninstallNpmDependencies.push("chokidar-cli");
434
- updateUninstallComposerDependencies.push("cboden/ratchet");
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");
516
+ }
435
517
  }
436
518
  if (!updateAnswer.prisma) {
437
- updateUninstallNpmDependencies.push(
519
+ const prismaPackages = [
438
520
  "prisma",
439
521
  "@prisma/client",
440
- "@prisma/internals"
441
- );
522
+ "@prisma/internals",
523
+ ];
524
+ prismaPackages.forEach((pkg) => {
525
+ if (isNpmPackageInstalled(pkg)) {
526
+ updateUninstallNpmDependencies.push(pkg);
527
+ }
528
+ });
442
529
  }
443
530
  if (!updateAnswer.docker) {
444
531
  const dockerFiles = [
@@ -450,14 +537,18 @@ async function main() {
450
537
  dockerFiles.forEach((file) => {
451
538
  const filePath = path.join(projectPath, file);
452
539
  if (fs.existsSync(filePath)) {
453
- fs.unlinkSync(filePath); // Delete each file if it exists
540
+ fs.unlinkSync(filePath);
454
541
  console.log(`${file} was deleted successfully.`);
455
- } else {
456
- console.log(`${file} does not exist.`);
457
542
  }
458
543
  });
459
544
  }
545
+ // Only uninstall if there are packages to uninstall
460
546
  if (updateUninstallNpmDependencies.length > 0) {
547
+ console.log(
548
+ `Uninstalling npm packages: ${updateUninstallNpmDependencies.join(
549
+ ", "
550
+ )}`
551
+ );
461
552
  await uninstallNpmDependencies(
462
553
  projectPath,
463
554
  updateUninstallNpmDependencies,
@@ -465,6 +556,11 @@ async function main() {
465
556
  );
466
557
  }
467
558
  if (updateUninstallComposerDependencies.length > 0) {
559
+ console.log(
560
+ `Uninstalling composer packages: ${updateUninstallComposerDependencies.join(
561
+ ", "
562
+ )}`
563
+ );
468
564
  await uninstallComposerDependencies(
469
565
  projectPath,
470
566
  updateUninstallComposerDependencies
@@ -478,7 +574,6 @@ async function main() {
478
574
  projectRootPath: projectPathModified,
479
575
  phpEnvironment: "XAMPP",
480
576
  phpRootPathExe: "C:\\xampp\\php\\php.exe",
481
- phpGenerateClassPath: "src/Lib/Prisma/Classes",
482
577
  bsTarget: bsConfig.bsTarget,
483
578
  bsPathRewrite: bsConfig.bsPathRewrite,
484
579
  backendOnly: answer.backendOnly,