create-prisma-php-app 4.0.0-alpha.30 ā 4.0.0-alpha.31
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 +189 -18
- package/package.json +1 -1
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.mcp&&(n.scripts={...n.scripts,mcp:"tsx settings/restart-mcp.ts"},c.push("mcp")),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.mcp&&n.includes("src\\lib\\mcp"))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.mcp&&s.includes("restart-mcp.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.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 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.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 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,mcp:o.mcp??e.mcp??!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"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!1,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","src/app/layout.php","src/app/index.php"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/app/layout.php","src/app/index.php","src/app/js/index.js","src/app/css/tailwind.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","docker-compose.yml","Dockerfile"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,websocket:!0,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!0},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/lib/websocket","src/lib/mcp"]},ecommerce:{id:"ecommerce",name:"E-commerce Starter",description:"Full e-commerce application with cart, payments, and admin",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!0,swaggerDocs:!0,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-ecommerce-starter",branch:"main"}},blog:{id:"blog",name:"Blog CMS",description:"Blog content management system",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!1,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-blog-starter"}}};function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),c=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let n=`http://localhost/${c}`;n=n.endsWith("/")?n.slice(0,-1):n;const i=n.replace(/(?<!:)(\/\/+)/g,"/"),o=c.replace(/\/\/+/g,"/");return{bsTarget:`${i}/`,bsPathRewrite:{"^/":`/${o.startsWith("/")?o.substring(1):o}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const c=JSON.parse(fs.readFileSync(t,"utf8"));c.scripts={...c.scripts,projectName:"tsx settings/project-name.ts"};let n=[];if(s.tailwindcss&&(c.scripts={...c.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"},n.push("tailwind")),s.websocket&&(c.scripts={...c.scripts,websocket:"tsx settings/restart-websocket.ts"},n.push("websocket")),s.mcp&&(c.scripts={...c.scripts,mcp:"tsx settings/restart-mcp.ts"},n.push("mcp")),s.docker&&(c.scripts={...c.scripts,docker:"docker-compose up"},n.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";c.scripts={...c.scripts,"create-swagger-docs":e}}let i={...c.scripts};i.browserSync="tsx settings/bs-config.ts",i["browserSync:build"]="tsx settings/build.ts",i.dev=`npm-run-all projectName -p browserSync ${n.join(" ")}`,i.build=`npm-run-all${s.tailwindcss?" tailwind:build":""} browserSync:build`,c.scripts=i,c.type="module",fs.writeFileSync(t,JSON.stringify(c,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 c=fs.readFileSync(t,"utf8");c+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,c,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const c=fs.existsSync(e),n=c&&fs.statSync(e);if(c&&n&&n.isDirectory()){const c=s.toLowerCase();if(!t.websocket&&c.includes("src\\lib\\websocket"))return;if(!t.mcp&&c.includes("src\\lib\\mcp"))return;if(t.backendOnly&&c.includes("src\\app\\js")||t.backendOnly&&c.includes("src\\app\\css")||t.backendOnly&&c.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&c.includes("src\\app\\swagger-docs"))return;const n=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(n))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((c=>{copyRecursiveSync(path.join(e,c),path.join(s,c),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.mcp&&s.includes("restart-mcp.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:c})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,c),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"),c="";s.backendOnly||(s.tailwindcss||(c='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />'),c+='\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 n="";s.backendOnly||(n=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${c}`:c),e=e.replace("</head>",`${n}\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 c=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.docker&&c.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 c=path.join(__dirname,s),n=path.join(e,t);if(checkExcludeFiles(n))return;const i=fs.readFileSync(c,"utf8");fs.writeFileSync(n,i,{flag:"w"})})),await executeCopy(e,c,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 n=generateAuthSecret(),i=generateHexEncodedKey(),o=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${n}"\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="${i}"\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${o}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,o)}async function getAnswer(e={}){if(e.starterKit){const s=e.starterKit;let t=null;if(STARTER_KITS[s]&&(t=STARTER_KITS[s]),t){const c={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,websocket:t.features.websocket??!1,prisma:t.features.prisma??!1,docker:t.features.docker??!1,swaggerDocs:t.features.swaggerDocs??!1,mcp:t.features.mcp??!1},n=process.argv.slice(2);return n.includes("--backend-only")&&(c.backendOnly=!0),n.includes("--swagger-docs")&&(c.swaggerDocs=!0),n.includes("--tailwindcss")&&(c.tailwindcss=!0),n.includes("--websocket")&&(c.websocket=!0),n.includes("--mcp")&&(c.mcp=!0),n.includes("--prisma")&&(c.prisma=!0),n.includes("--docker")&&(c.docker=!0),c}if(e.starterKitSource)return{projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,docker:!1,swaggerDocs:!0,mcp:!1}}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)},c=await prompts(s,{onCancel:t}),n=[];c.backendOnly??e.backendOnly??!1?(e.swaggerDocs||n.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||n.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||n.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||n.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||n.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||n.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||n.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||n.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||n.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||n.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||n.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const i=await prompts(n,{onCancel:t});return{projectName:c.projectName?String(c.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:c.backendOnly??e.backendOnly??!1,swaggerDocs:i.swaggerDocs??e.swaggerDocs??!1,tailwindcss:i.tailwindcss??e.tailwindcss??!1,websocket:i.websocket??e.websocket??!1,mcp:i.mcp??e.mcp??!1,prisma:i.prisma??e.prisma??!1,docker:i.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){s.forEach((e=>{}));const c=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(c,{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 c="";e.on("data",(e=>c+=e)),e.on("end",(()=>{try{const e=JSON.parse(c);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),c=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>c[e])return 1;if(t[e]<c[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.
|
|
@@ -171,13 +171,185 @@ function composerPkg(name) {
|
|
|
171
171
|
? `${name}:${composerPinnedVersions[name]}`
|
|
172
172
|
: name;
|
|
173
173
|
}
|
|
174
|
+
async function downloadStarterKit(starterKit, tempDir) {
|
|
175
|
+
if (!starterKit.source) {
|
|
176
|
+
throw new Error("No source defined for starter kit");
|
|
177
|
+
}
|
|
178
|
+
const { type, url, branch = "main", subfolder } = starterKit.source;
|
|
179
|
+
switch (type) {
|
|
180
|
+
case "git":
|
|
181
|
+
console.log(chalk.blue(`Cloning ${starterKit.name} from ${url}...`));
|
|
182
|
+
const cloneCommand = branch
|
|
183
|
+
? `git clone -b ${branch} --depth 1 ${url} ${tempDir}`
|
|
184
|
+
: `git clone --depth 1 ${url} ${tempDir}`;
|
|
185
|
+
execSync(cloneCommand, { stdio: "inherit" });
|
|
186
|
+
// Remove .git directory
|
|
187
|
+
const gitDir = path.join(tempDir, ".git");
|
|
188
|
+
if (fs.existsSync(gitDir)) {
|
|
189
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
// Return the subfolder if specified
|
|
192
|
+
return subfolder ? path.join(tempDir, subfolder) : tempDir;
|
|
193
|
+
case "npm":
|
|
194
|
+
console.log(chalk.blue(`Downloading ${starterKit.name} from npm...`));
|
|
195
|
+
execSync(`npm pack ${url}`, { cwd: tempDir, stdio: "inherit" });
|
|
196
|
+
// Extract the tarball
|
|
197
|
+
const tarball = fs.readdirSync(tempDir).find((f) => f.endsWith(".tgz"));
|
|
198
|
+
if (tarball) {
|
|
199
|
+
execSync(`tar -xzf ${tarball}`, { cwd: tempDir });
|
|
200
|
+
fs.unlinkSync(path.join(tempDir, tarball));
|
|
201
|
+
return path.join(tempDir, "package");
|
|
202
|
+
}
|
|
203
|
+
throw new Error("Failed to extract npm package");
|
|
204
|
+
case "url":
|
|
205
|
+
throw new Error("URL download not implemented yet");
|
|
206
|
+
default:
|
|
207
|
+
throw new Error(`Unsupported source type: ${type}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function mergeStarterKitFiles(starterKitPath, projectPath, answer) {
|
|
211
|
+
console.log(chalk.blue("Merging starter kit files..."));
|
|
212
|
+
// Copy all files from starter kit, but don't overwrite base files
|
|
213
|
+
copyRecursiveSync(starterKitPath, projectPath, answer);
|
|
214
|
+
// Look for starter kit specific configuration
|
|
215
|
+
const starterKitConfig = path.join(starterKitPath, "starter-kit.json");
|
|
216
|
+
if (fs.existsSync(starterKitConfig)) {
|
|
217
|
+
const config = JSON.parse(fs.readFileSync(starterKitConfig, "utf8"));
|
|
218
|
+
// Handle post-install scripts
|
|
219
|
+
if (config.postInstall) {
|
|
220
|
+
console.log(chalk.blue("Running post-install scripts..."));
|
|
221
|
+
for (const script of config.postInstall) {
|
|
222
|
+
console.log(chalk.gray(`Running: ${script}`));
|
|
223
|
+
execSync(script, { cwd: projectPath, stdio: "inherit" });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Handle additional dependencies
|
|
227
|
+
if (config.additionalNpmDependencies) {
|
|
228
|
+
await installNpmDependencies(
|
|
229
|
+
projectPath,
|
|
230
|
+
config.additionalNpmDependencies.map(npmPkg),
|
|
231
|
+
true
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (config.additionalComposerDependencies) {
|
|
235
|
+
await installComposerDependencies(
|
|
236
|
+
projectPath,
|
|
237
|
+
config.additionalComposerDependencies.map(composerPkg)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function setupStarterKit(baseDir, answer) {
|
|
243
|
+
if (!answer.starterKit) return;
|
|
244
|
+
let starterKit = null;
|
|
245
|
+
// Check if it's a built-in starter kit
|
|
246
|
+
if (STARTER_KITS[answer.starterKit]) {
|
|
247
|
+
starterKit = STARTER_KITS[answer.starterKit];
|
|
248
|
+
}
|
|
249
|
+
// Handle custom starter kit URL
|
|
250
|
+
else if (answer.starterKitSource) {
|
|
251
|
+
starterKit = {
|
|
252
|
+
id: answer.starterKit,
|
|
253
|
+
name: `Custom Starter Kit (${answer.starterKit})`,
|
|
254
|
+
description: "Custom starter kit from external source",
|
|
255
|
+
features: {}, // Will be determined from the downloaded kit
|
|
256
|
+
requiredFiles: [],
|
|
257
|
+
source: {
|
|
258
|
+
type: "git", // Assume git for now, could be enhanced
|
|
259
|
+
url: answer.starterKitSource,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (!starterKit) {
|
|
264
|
+
console.warn(
|
|
265
|
+
chalk.yellow(`Starter kit '${answer.starterKit}' not found. Skipping...`)
|
|
266
|
+
);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
console.log(chalk.green(`Setting up ${starterKit.name}...`));
|
|
270
|
+
// If it's a custom starter kit with source, download it
|
|
271
|
+
if (starterKit.source) {
|
|
272
|
+
const tempDir = path.join(baseDir, ".temp-starter-kit");
|
|
273
|
+
try {
|
|
274
|
+
// Create temp directory
|
|
275
|
+
if (fs.existsSync(tempDir)) {
|
|
276
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
279
|
+
// Download the starter kit
|
|
280
|
+
const kitPath = await downloadStarterKit(starterKit, tempDir);
|
|
281
|
+
// Merge files from starter kit
|
|
282
|
+
await mergeStarterKitFiles(kitPath, baseDir, answer);
|
|
283
|
+
// Check if starter kit has its own configuration
|
|
284
|
+
const kitConfigPath = path.join(kitPath, "prisma-php-starter.json");
|
|
285
|
+
if (fs.existsSync(kitConfigPath)) {
|
|
286
|
+
const kitConfig = JSON.parse(fs.readFileSync(kitConfigPath, "utf8"));
|
|
287
|
+
// Override features with starter kit configuration
|
|
288
|
+
Object.assign(answer, kitConfig.features || {});
|
|
289
|
+
console.log(chalk.green(`Applied starter kit configuration`));
|
|
290
|
+
}
|
|
291
|
+
// Clean up temp directory
|
|
292
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error(chalk.red(`Failed to setup starter kit: ${error}`));
|
|
295
|
+
// Clean up temp directory on error
|
|
296
|
+
if (fs.existsSync(tempDir)) {
|
|
297
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Run custom setup if defined
|
|
303
|
+
if (starterKit.customSetup) {
|
|
304
|
+
await starterKit.customSetup(baseDir, answer);
|
|
305
|
+
}
|
|
306
|
+
console.log(chalk.green(`ā ${starterKit.name} setup complete!`));
|
|
307
|
+
}
|
|
308
|
+
function showStarterKits() {
|
|
309
|
+
console.log(chalk.blue("\nš Available Starter Kits:\n"));
|
|
310
|
+
Object.values(STARTER_KITS).forEach((kit) => {
|
|
311
|
+
const isCustom = kit.source ? " (Custom)" : " (Built-in)";
|
|
312
|
+
console.log(chalk.green(` ${kit.id}${chalk.gray(isCustom)}`));
|
|
313
|
+
console.log(` ${kit.name}`);
|
|
314
|
+
console.log(chalk.gray(` ${kit.description}`));
|
|
315
|
+
if (kit.source) {
|
|
316
|
+
console.log(chalk.cyan(` Source: ${kit.source.url}`));
|
|
317
|
+
}
|
|
318
|
+
const features = Object.entries(kit.features)
|
|
319
|
+
.filter(([, value]) => value === true)
|
|
320
|
+
.map(([key]) => key)
|
|
321
|
+
.join(", ");
|
|
322
|
+
if (features) {
|
|
323
|
+
console.log(chalk.magenta(` Features: ${features}`));
|
|
324
|
+
}
|
|
325
|
+
console.log();
|
|
326
|
+
});
|
|
327
|
+
console.log(chalk.yellow("Usage:"));
|
|
328
|
+
console.log(` npx create-prisma-php-app my-project --starter-kit=basic`);
|
|
329
|
+
console.log(
|
|
330
|
+
` npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo`
|
|
331
|
+
);
|
|
332
|
+
console.log();
|
|
333
|
+
}
|
|
174
334
|
async function main() {
|
|
175
335
|
try {
|
|
176
336
|
const args = process.argv.slice(2);
|
|
177
337
|
let projectName = args[0];
|
|
338
|
+
// Parse starter kit arguments
|
|
339
|
+
const starterKitArg = args.find((arg) => arg.startsWith("--starter-kit="));
|
|
340
|
+
const starterKitFromArgs = starterKitArg?.split("=")[1];
|
|
341
|
+
// Parse custom starter kit source
|
|
342
|
+
const starterKitSourceArg = args.find((arg) =>
|
|
343
|
+
arg.startsWith("--starter-kit-source=")
|
|
344
|
+
);
|
|
345
|
+
const starterKitSource = starterKitSourceArg?.split("=")[1];
|
|
346
|
+
// Show help
|
|
347
|
+
if (args.includes("--list-starter-kits")) {
|
|
348
|
+
showStarterKits();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
178
351
|
let answer = null;
|
|
179
352
|
if (projectName) {
|
|
180
|
-
// Check if it's an update FIRST
|
|
181
353
|
const currentDir = process.cwd();
|
|
182
354
|
const configPath = path.join(currentDir, "prisma-php.json");
|
|
183
355
|
if (fs.existsSync(configPath)) {
|
|
@@ -237,23 +409,18 @@ async function main() {
|
|
|
237
409
|
};
|
|
238
410
|
}
|
|
239
411
|
} else {
|
|
240
|
-
//
|
|
241
|
-
let useBackendOnly = args.includes("--backend-only");
|
|
242
|
-
let useSwaggerDocs = args.includes("--swagger-docs");
|
|
243
|
-
let useTailwind = args.includes("--tailwindcss");
|
|
244
|
-
let useWebsocket = args.includes("--websocket");
|
|
245
|
-
let useMcp = args.includes("--mcp");
|
|
246
|
-
let usePrisma = args.includes("--prisma");
|
|
247
|
-
let useDocker = args.includes("--docker");
|
|
412
|
+
// New project
|
|
248
413
|
const predefinedAnswers = {
|
|
249
414
|
projectName,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
415
|
+
starterKit: starterKitFromArgs,
|
|
416
|
+
starterKitSource: starterKitSource,
|
|
417
|
+
backendOnly: args.includes("--backend-only"),
|
|
418
|
+
swaggerDocs: args.includes("--swagger-docs"),
|
|
419
|
+
tailwindcss: args.includes("--tailwindcss"),
|
|
420
|
+
websocket: args.includes("--websocket"),
|
|
421
|
+
mcp: args.includes("--mcp"),
|
|
422
|
+
prisma: args.includes("--prisma"),
|
|
423
|
+
docker: args.includes("--docker"),
|
|
257
424
|
};
|
|
258
425
|
answer = await getAnswer(predefinedAnswers);
|
|
259
426
|
}
|
|
@@ -262,7 +429,7 @@ async function main() {
|
|
|
262
429
|
return;
|
|
263
430
|
}
|
|
264
431
|
} else {
|
|
265
|
-
//
|
|
432
|
+
// Interactive mode
|
|
266
433
|
answer = await getAnswer();
|
|
267
434
|
}
|
|
268
435
|
if (answer === null) {
|
|
@@ -347,6 +514,10 @@ async function main() {
|
|
|
347
514
|
if (answer.prisma) {
|
|
348
515
|
execSync("npm install -g prisma-client-php", { stdio: "inherit" });
|
|
349
516
|
}
|
|
517
|
+
// Add starter kit setup before npm/composer installation
|
|
518
|
+
if (answer.starterKit) {
|
|
519
|
+
await setupStarterKit(projectPath, answer);
|
|
520
|
+
}
|
|
350
521
|
await installNpmDependencies(projectPath, npmDependencies, true);
|
|
351
522
|
await installComposerDependencies(projectPath, composerDependencies);
|
|
352
523
|
if (!projectName) {
|