create-prisma-php-app 4.0.0-alpha.4 → 4.0.0-alpha.41
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/.htaccess +54 -41
- package/dist/bootstrap.php +143 -98
- package/dist/index.js +1628 -142
- package/dist/settings/auto-swagger-docs.ts +196 -95
- package/dist/settings/bs-config.ts +53 -58
- package/dist/settings/files-list.json +1 -1
- package/dist/settings/project-name.ts +2 -0
- package/dist/settings/restart-mcp.ts +58 -0
- package/dist/settings/restart-websocket.ts +51 -45
- package/dist/settings/utils.ts +240 -0
- package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
- package/dist/src/Lib/Auth/Auth.php +544 -0
- package/dist/src/Lib/Auth/AuthConfig.php +89 -0
- package/dist/src/Lib/CacheHandler.php +121 -0
- package/dist/src/Lib/ErrorHandler.php +322 -0
- package/dist/src/Lib/FileManager/UploadFile.php +383 -0
- package/dist/src/Lib/Headers/Boom.php +192 -0
- package/dist/src/Lib/IncludeTracker.php +59 -0
- package/dist/src/Lib/MCP/WeatherTools.php +104 -0
- package/dist/src/Lib/MCP/mcp-server.php +80 -0
- package/dist/src/Lib/MainLayout.php +230 -0
- package/dist/src/Lib/Middleware/AuthMiddleware.php +157 -0
- package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
- package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
- package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
- package/dist/src/Lib/PHPX/Fragment.php +32 -0
- package/dist/src/Lib/PHPX/IPHPX.php +22 -0
- package/dist/src/Lib/PHPX/PHPX.php +287 -0
- package/dist/src/Lib/PHPX/TemplateCompiler.php +641 -0
- package/dist/src/Lib/PHPX/TwMerge.php +346 -0
- package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
- package/dist/src/Lib/PartialRenderer.php +40 -0
- package/dist/src/Lib/PrismaPHPSettings.php +181 -0
- package/dist/src/Lib/Request.php +479 -0
- package/dist/src/Lib/Security/RateLimiter.php +33 -0
- package/dist/src/Lib/Set.php +102 -0
- package/dist/src/Lib/StateManager.php +127 -0
- package/dist/src/Lib/Validator.php +752 -0
- package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
- package/dist/src/Lib/Websocket/websocket-server.php +118 -0
- package/dist/src/app/error.php +1 -1
- package/dist/src/app/index.php +22 -5
- package/dist/src/app/js/index.js +1 -1
- package/dist/src/app/layout.php +2 -2
- package/package.json +1 -1
- package/dist/settings/restart-websocket.bat +0 -28
- package/dist/src/app/assets/images/prisma-php-black.svg +0 -6
- package/dist/websocket-server.php +0 -22
- package/vendor/autoload.php +0 -25
- package/vendor/composer/ClassLoader.php +0 -579
- package/vendor/composer/InstalledVersions.php +0 -359
- package/vendor/composer/LICENSE +0 -21
- package/vendor/composer/autoload_classmap.php +0 -10
- package/vendor/composer/autoload_namespaces.php +0 -9
- package/vendor/composer/autoload_psr4.php +0 -10
- package/vendor/composer/autoload_real.php +0 -38
- package/vendor/composer/autoload_static.php +0 -25
- package/vendor/composer/installed.json +0 -825
- package/vendor/composer/installed.php +0 -132
- package/vendor/composer/platform_check.php +0 -26
package/dist/index.js
CHANGED
|
@@ -1,5 +1,857 @@
|
|
|
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";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import https from "https";
|
|
9
|
+
import { randomBytes } from "crypto";
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
let updateAnswer = null;
|
|
13
|
+
const nonBackendFiles = [
|
|
14
|
+
"favicon.ico",
|
|
15
|
+
"\\src\\app\\index.php",
|
|
16
|
+
"metadata.php",
|
|
17
|
+
"not-found.php",
|
|
18
|
+
"error.php",
|
|
19
|
+
];
|
|
20
|
+
const dockerFiles = [
|
|
21
|
+
".dockerignore",
|
|
22
|
+
"docker-compose.yml",
|
|
23
|
+
"Dockerfile",
|
|
24
|
+
"apache.conf",
|
|
25
|
+
];
|
|
26
|
+
const STARTER_KITS = {
|
|
27
|
+
basic: {
|
|
28
|
+
id: "basic",
|
|
29
|
+
name: "Basic PHP Application",
|
|
30
|
+
description: "Simple PHP backend with minimal dependencies",
|
|
31
|
+
features: {
|
|
32
|
+
backendOnly: true,
|
|
33
|
+
tailwindcss: false,
|
|
34
|
+
websocket: false,
|
|
35
|
+
prisma: false,
|
|
36
|
+
docker: false,
|
|
37
|
+
swaggerDocs: false,
|
|
38
|
+
mcp: false,
|
|
39
|
+
},
|
|
40
|
+
requiredFiles: [
|
|
41
|
+
"bootstrap.php",
|
|
42
|
+
".htaccess",
|
|
43
|
+
"src/app/layout.php",
|
|
44
|
+
"src/app/index.php",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
fullstack: {
|
|
48
|
+
id: "fullstack",
|
|
49
|
+
name: "Full-Stack Application",
|
|
50
|
+
description: "Complete web application with frontend and backend",
|
|
51
|
+
features: {
|
|
52
|
+
backendOnly: false,
|
|
53
|
+
tailwindcss: true,
|
|
54
|
+
websocket: false,
|
|
55
|
+
prisma: true,
|
|
56
|
+
docker: false,
|
|
57
|
+
swaggerDocs: true,
|
|
58
|
+
mcp: false,
|
|
59
|
+
},
|
|
60
|
+
requiredFiles: [
|
|
61
|
+
"bootstrap.php",
|
|
62
|
+
".htaccess",
|
|
63
|
+
"postcss.config.js",
|
|
64
|
+
"src/app/layout.php",
|
|
65
|
+
"src/app/index.php",
|
|
66
|
+
"src/app/js/index.js",
|
|
67
|
+
"src/app/css/tailwind.css",
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
api: {
|
|
71
|
+
id: "api",
|
|
72
|
+
name: "REST API",
|
|
73
|
+
description: "Backend API with database and documentation",
|
|
74
|
+
features: {
|
|
75
|
+
backendOnly: true,
|
|
76
|
+
tailwindcss: false,
|
|
77
|
+
websocket: false,
|
|
78
|
+
prisma: true,
|
|
79
|
+
docker: true,
|
|
80
|
+
swaggerDocs: true,
|
|
81
|
+
mcp: false,
|
|
82
|
+
},
|
|
83
|
+
requiredFiles: [
|
|
84
|
+
"bootstrap.php",
|
|
85
|
+
".htaccess",
|
|
86
|
+
"docker-compose.yml",
|
|
87
|
+
"Dockerfile",
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
realtime: {
|
|
91
|
+
id: "realtime",
|
|
92
|
+
name: "Real-time Application",
|
|
93
|
+
description: "Application with WebSocket support and MCP",
|
|
94
|
+
features: {
|
|
95
|
+
backendOnly: false,
|
|
96
|
+
tailwindcss: true,
|
|
97
|
+
websocket: true,
|
|
98
|
+
prisma: true,
|
|
99
|
+
docker: false,
|
|
100
|
+
swaggerDocs: true,
|
|
101
|
+
mcp: true,
|
|
102
|
+
},
|
|
103
|
+
requiredFiles: [
|
|
104
|
+
"bootstrap.php",
|
|
105
|
+
".htaccess",
|
|
106
|
+
"postcss.config.js",
|
|
107
|
+
"src/lib/websocket",
|
|
108
|
+
"src/lib/mcp",
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
// Custom starter kit examples
|
|
112
|
+
ecommerce: {
|
|
113
|
+
id: "ecommerce",
|
|
114
|
+
name: "E-commerce Starter",
|
|
115
|
+
description: "Full e-commerce application with cart, payments, and admin",
|
|
116
|
+
features: {
|
|
117
|
+
backendOnly: false,
|
|
118
|
+
tailwindcss: true,
|
|
119
|
+
websocket: false,
|
|
120
|
+
prisma: true,
|
|
121
|
+
docker: true,
|
|
122
|
+
swaggerDocs: true,
|
|
123
|
+
mcp: false,
|
|
124
|
+
},
|
|
125
|
+
requiredFiles: [],
|
|
126
|
+
source: {
|
|
127
|
+
type: "git",
|
|
128
|
+
url: "https://github.com/your-org/prisma-php-ecommerce-starter",
|
|
129
|
+
branch: "main",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
blog: {
|
|
133
|
+
id: "blog",
|
|
134
|
+
name: "Blog CMS",
|
|
135
|
+
description: "Blog content management system",
|
|
136
|
+
features: {
|
|
137
|
+
backendOnly: false,
|
|
138
|
+
tailwindcss: true,
|
|
139
|
+
websocket: false,
|
|
140
|
+
prisma: true,
|
|
141
|
+
docker: false,
|
|
142
|
+
swaggerDocs: false,
|
|
143
|
+
mcp: false,
|
|
144
|
+
},
|
|
145
|
+
requiredFiles: [],
|
|
146
|
+
source: {
|
|
147
|
+
type: "git",
|
|
148
|
+
url: "https://github.com/your-org/prisma-php-blog-starter",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
function bsConfigUrls(projectRootPath) {
|
|
153
|
+
// Identify the base path dynamically up to and including 'htdocs'
|
|
154
|
+
const htdocsIndex = projectRootPath.indexOf("\\htdocs\\");
|
|
155
|
+
if (htdocsIndex === -1) {
|
|
156
|
+
console.error(
|
|
157
|
+
"Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"
|
|
158
|
+
);
|
|
159
|
+
return {
|
|
160
|
+
bsTarget: "",
|
|
161
|
+
bsPathRewrite: {},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Extract the path up to and including 'htdocs\\'
|
|
165
|
+
const basePathToRemove = projectRootPath.substring(
|
|
166
|
+
0,
|
|
167
|
+
htdocsIndex + "\\htdocs\\".length
|
|
168
|
+
);
|
|
169
|
+
// Escape backslashes for the regex pattern
|
|
170
|
+
const escapedBasePathToRemove = basePathToRemove.replace(/\\/g, "\\\\");
|
|
171
|
+
// Remove the base path and replace backslashes with forward slashes for URL compatibility
|
|
172
|
+
const relativeWebPath = projectRootPath
|
|
173
|
+
.replace(new RegExp(`^${escapedBasePathToRemove}`), "")
|
|
174
|
+
.replace(/\\/g, "/");
|
|
175
|
+
// Construct the Browser Sync command with the correct proxy URL, being careful not to affect the protocol part
|
|
176
|
+
let proxyUrl = `http://localhost/${relativeWebPath}`;
|
|
177
|
+
// Ensure the proxy URL does not end with a slash before appending '/public'
|
|
178
|
+
proxyUrl = proxyUrl.endsWith("/") ? proxyUrl.slice(0, -1) : proxyUrl;
|
|
179
|
+
// Clean the URL by replacing "//" with "/" but not affecting "http://"
|
|
180
|
+
// We replace instances of "//" that are not preceded by ":"
|
|
181
|
+
const cleanUrl = proxyUrl.replace(/(?<!:)(\/\/+)/g, "/");
|
|
182
|
+
const cleanRelativeWebPath = relativeWebPath.replace(/\/\/+/g, "/");
|
|
183
|
+
// Correct the relativeWebPath to ensure it does not start with a "/"
|
|
184
|
+
const adjustedRelativeWebPath = cleanRelativeWebPath.startsWith("/")
|
|
185
|
+
? cleanRelativeWebPath.substring(1)
|
|
186
|
+
: cleanRelativeWebPath;
|
|
187
|
+
return {
|
|
188
|
+
bsTarget: `${cleanUrl}/`,
|
|
189
|
+
bsPathRewrite: {
|
|
190
|
+
"^/": `/${adjustedRelativeWebPath}/`,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function updatePackageJson(baseDir, answer) {
|
|
195
|
+
const packageJsonPath = path.join(baseDir, "package.json");
|
|
196
|
+
if (checkExcludeFiles(packageJsonPath)) return;
|
|
197
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
198
|
+
packageJson.scripts = {
|
|
199
|
+
...packageJson.scripts,
|
|
200
|
+
projectName: "tsx settings/project-name.ts",
|
|
201
|
+
};
|
|
202
|
+
let answersToInclude = [];
|
|
203
|
+
if (answer.tailwindcss) {
|
|
204
|
+
packageJson.scripts = {
|
|
205
|
+
...packageJson.scripts,
|
|
206
|
+
tailwind:
|
|
207
|
+
"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch",
|
|
208
|
+
"tailwind:build":
|
|
209
|
+
"postcss src/app/css/tailwind.css -o src/app/css/styles.css",
|
|
210
|
+
};
|
|
211
|
+
answersToInclude.push("tailwind");
|
|
212
|
+
}
|
|
213
|
+
if (answer.websocket) {
|
|
214
|
+
packageJson.scripts = {
|
|
215
|
+
...packageJson.scripts,
|
|
216
|
+
websocket: "tsx settings/restart-websocket.ts",
|
|
217
|
+
};
|
|
218
|
+
answersToInclude.push("websocket");
|
|
219
|
+
}
|
|
220
|
+
if (answer.mcp) {
|
|
221
|
+
packageJson.scripts = {
|
|
222
|
+
...packageJson.scripts,
|
|
223
|
+
mcp: "tsx settings/restart-mcp.ts",
|
|
224
|
+
};
|
|
225
|
+
answersToInclude.push("mcp");
|
|
226
|
+
}
|
|
227
|
+
if (answer.docker) {
|
|
228
|
+
packageJson.scripts = {
|
|
229
|
+
...packageJson.scripts,
|
|
230
|
+
docker: "docker-compose up",
|
|
231
|
+
};
|
|
232
|
+
answersToInclude.push("docker");
|
|
233
|
+
}
|
|
234
|
+
if (answer.swaggerDocs) {
|
|
235
|
+
const swaggerDocsExecuteScript = answer.prisma
|
|
236
|
+
? "tsx settings/auto-swagger-docs.ts"
|
|
237
|
+
: "tsx settings/swagger-config.ts";
|
|
238
|
+
packageJson.scripts = {
|
|
239
|
+
...packageJson.scripts,
|
|
240
|
+
"create-swagger-docs": swaggerDocsExecuteScript,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// Initialize with existing scripts
|
|
244
|
+
let updatedScripts = {
|
|
245
|
+
...packageJson.scripts,
|
|
246
|
+
};
|
|
247
|
+
updatedScripts.browserSync = "tsx settings/bs-config.ts";
|
|
248
|
+
updatedScripts["browserSync:build"] = "tsx settings/build.ts";
|
|
249
|
+
updatedScripts.dev = `npm-run-all projectName -p browserSync ${answersToInclude.join(
|
|
250
|
+
" "
|
|
251
|
+
)}`;
|
|
252
|
+
updatedScripts.build = `npm-run-all${
|
|
253
|
+
answer.tailwindcss ? " tailwind:build" : ""
|
|
254
|
+
} browserSync:build`;
|
|
255
|
+
// Finally, assign the updated scripts back to packageJson
|
|
256
|
+
packageJson.scripts = updatedScripts;
|
|
257
|
+
packageJson.type = "module";
|
|
258
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
259
|
+
}
|
|
260
|
+
async function updateComposerJson(baseDir) {
|
|
261
|
+
const composerJsonPath = path.join(baseDir, "composer.json");
|
|
262
|
+
if (checkExcludeFiles(composerJsonPath)) return;
|
|
263
|
+
}
|
|
264
|
+
async function updateIndexJsForWebSocket(baseDir, answer) {
|
|
265
|
+
if (!answer.websocket) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const indexPath = path.join(baseDir, "src", "app", "js", "index.js");
|
|
269
|
+
if (checkExcludeFiles(indexPath)) return;
|
|
270
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
271
|
+
// WebSocket initialization code to be appended
|
|
272
|
+
const webSocketCode = `
|
|
273
|
+
// WebSocket initialization
|
|
274
|
+
var ws = new WebSocket("ws://localhost:8080");
|
|
275
|
+
`;
|
|
276
|
+
// Append WebSocket code if user chose to use WebSocket
|
|
277
|
+
indexContent += webSocketCode;
|
|
278
|
+
fs.writeFileSync(indexPath, indexContent, "utf8");
|
|
279
|
+
console.log("WebSocket code added to index.js successfully.");
|
|
280
|
+
}
|
|
281
|
+
function generateAuthSecret() {
|
|
282
|
+
// Generate 33 random bytes and encode them as a base64 string
|
|
283
|
+
return randomBytes(33).toString("base64");
|
|
284
|
+
}
|
|
285
|
+
function generateHexEncodedKey(size = 16) {
|
|
286
|
+
return randomBytes(size).toString("hex"); // Hex encoding ensures safe session keys
|
|
287
|
+
}
|
|
288
|
+
// Recursive copy function
|
|
289
|
+
function copyRecursiveSync(src, dest, answer) {
|
|
290
|
+
const exists = fs.existsSync(src);
|
|
291
|
+
const stats = exists && fs.statSync(src);
|
|
292
|
+
const isDirectory = exists && stats && stats.isDirectory();
|
|
293
|
+
if (isDirectory) {
|
|
294
|
+
const destLower = dest.toLowerCase();
|
|
295
|
+
if (!answer.websocket && destLower.includes("src\\lib\\websocket")) return;
|
|
296
|
+
if (!answer.mcp && destLower.includes("src\\lib\\mcp")) return;
|
|
297
|
+
if (
|
|
298
|
+
(answer.backendOnly && destLower.includes("src\\app\\js")) ||
|
|
299
|
+
(answer.backendOnly && destLower.includes("src\\app\\css")) ||
|
|
300
|
+
(answer.backendOnly && destLower.includes("src\\app\\assets"))
|
|
301
|
+
)
|
|
302
|
+
return;
|
|
303
|
+
if (!answer.swaggerDocs && destLower.includes("src\\app\\swagger-docs"))
|
|
304
|
+
return;
|
|
305
|
+
const destModified = dest.replace(/\\/g, "/");
|
|
306
|
+
if (updateAnswer?.excludeFilePath?.includes(destModified)) return;
|
|
307
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
308
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
309
|
+
copyRecursiveSync(
|
|
310
|
+
path.join(src, childItemName),
|
|
311
|
+
path.join(dest, childItemName),
|
|
312
|
+
answer
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
} else {
|
|
316
|
+
if (checkExcludeFiles(dest)) return;
|
|
317
|
+
if (
|
|
318
|
+
!answer.tailwindcss &&
|
|
319
|
+
(dest.includes("tailwind.css") || dest.includes("styles.css"))
|
|
320
|
+
)
|
|
321
|
+
return;
|
|
322
|
+
if (!answer.websocket && dest.includes("restart-websocket.ts")) return;
|
|
323
|
+
if (!answer.mcp && dest.includes("restart-mcp.ts")) return;
|
|
324
|
+
if (!answer.docker && dockerFiles.some((file) => dest.includes(file)))
|
|
325
|
+
return;
|
|
326
|
+
if (
|
|
327
|
+
answer.backendOnly &&
|
|
328
|
+
nonBackendFiles.some((file) => dest.includes(file))
|
|
329
|
+
)
|
|
330
|
+
return;
|
|
331
|
+
if (!answer.backendOnly && dest.includes("route.php")) return;
|
|
332
|
+
if (
|
|
333
|
+
answer.backendOnly &&
|
|
334
|
+
!answer.swaggerDocs &&
|
|
335
|
+
dest.includes("layout.php")
|
|
336
|
+
)
|
|
337
|
+
return;
|
|
338
|
+
if (!answer.swaggerDocs && dest.includes("swagger-config.ts")) return;
|
|
339
|
+
if (answer.tailwindcss && dest.includes("index.css")) return;
|
|
340
|
+
if (
|
|
341
|
+
(!answer.swaggerDocs || !answer.prisma) &&
|
|
342
|
+
(dest.includes("auto-swagger-docs.ts") ||
|
|
343
|
+
dest.includes("prisma-schema-config.json"))
|
|
344
|
+
)
|
|
345
|
+
return;
|
|
346
|
+
fs.copyFileSync(src, dest, 0);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Function to execute the recursive copy for entire directories
|
|
350
|
+
async function executeCopy(baseDir, directoriesToCopy, answer) {
|
|
351
|
+
directoriesToCopy.forEach(({ src: srcDir, dest: destDir }) => {
|
|
352
|
+
const sourcePath = path.join(__dirname, srcDir);
|
|
353
|
+
const destPath = path.join(baseDir, destDir);
|
|
354
|
+
copyRecursiveSync(sourcePath, destPath, answer);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function modifyPostcssConfig(baseDir) {
|
|
358
|
+
const filePath = path.join(baseDir, "postcss.config.js");
|
|
359
|
+
if (checkExcludeFiles(filePath)) return;
|
|
360
|
+
const newContent = `export default {
|
|
361
|
+
plugins: {
|
|
362
|
+
"@tailwindcss/postcss": {},
|
|
363
|
+
cssnano: {},
|
|
364
|
+
},
|
|
365
|
+
};`;
|
|
366
|
+
fs.writeFileSync(filePath, newContent, { flag: "w" });
|
|
367
|
+
console.log(chalk.green("postcss.config.js updated successfully."));
|
|
368
|
+
}
|
|
369
|
+
function modifyLayoutPHP(baseDir, answer) {
|
|
370
|
+
const layoutPath = path.join(baseDir, "src", "app", "layout.php");
|
|
371
|
+
if (checkExcludeFiles(layoutPath)) return;
|
|
372
|
+
try {
|
|
373
|
+
let indexContent = fs.readFileSync(layoutPath, "utf8");
|
|
374
|
+
let stylesAndLinks = "";
|
|
375
|
+
if (!answer.backendOnly) {
|
|
376
|
+
if (!answer.tailwindcss) {
|
|
377
|
+
stylesAndLinks = `\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />`;
|
|
378
|
+
}
|
|
379
|
+
stylesAndLinks += `\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>`;
|
|
380
|
+
}
|
|
381
|
+
// Tailwind CSS link or CDN script
|
|
382
|
+
let tailwindLink = "";
|
|
383
|
+
if (!answer.backendOnly) {
|
|
384
|
+
tailwindLink = answer.tailwindcss
|
|
385
|
+
? ` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${stylesAndLinks}`
|
|
386
|
+
: stylesAndLinks;
|
|
387
|
+
}
|
|
388
|
+
// Insert before the closing </head> tag
|
|
389
|
+
indexContent = indexContent.replace(
|
|
390
|
+
"</head>",
|
|
391
|
+
`${tailwindLink}
|
|
392
|
+
</head>`
|
|
393
|
+
);
|
|
394
|
+
fs.writeFileSync(layoutPath, indexContent, { flag: "w" });
|
|
395
|
+
console.log(
|
|
396
|
+
chalk.green(
|
|
397
|
+
`layout.php modified successfully for ${
|
|
398
|
+
answer.tailwindcss ? "local Tailwind CSS" : "Tailwind CSS CDN"
|
|
399
|
+
}.`
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error(chalk.red("Error modifying layout.php:"), error);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// This function updates or creates the .env file
|
|
407
|
+
async function createOrUpdateEnvFile(baseDir, content) {
|
|
408
|
+
const envPath = path.join(baseDir, ".env");
|
|
409
|
+
if (checkExcludeFiles(envPath)) return;
|
|
410
|
+
console.log("🚀 ~ content:", content);
|
|
411
|
+
fs.writeFileSync(envPath, content, { flag: "w" });
|
|
412
|
+
}
|
|
413
|
+
function checkExcludeFiles(destPath) {
|
|
414
|
+
if (!updateAnswer) return false;
|
|
415
|
+
return (
|
|
416
|
+
updateAnswer?.excludeFilePath?.includes(destPath.replace(/\\/g, "/")) ??
|
|
417
|
+
false
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
async function createDirectoryStructure(baseDir, answer) {
|
|
421
|
+
console.log("🚀 ~ baseDir:", baseDir);
|
|
422
|
+
console.log("🚀 ~ answer:", answer);
|
|
423
|
+
const filesToCopy = [
|
|
424
|
+
{ src: "/bootstrap.php", dest: "/bootstrap.php" },
|
|
425
|
+
{ src: "/.htaccess", dest: "/.htaccess" },
|
|
426
|
+
{ src: "/tsconfig.json", dest: "/tsconfig.json" },
|
|
427
|
+
{ src: "/app-gitignore", dest: "/.gitignore" },
|
|
428
|
+
];
|
|
429
|
+
if (answer.tailwindcss) {
|
|
430
|
+
filesToCopy.push({ src: "/postcss.config.js", dest: "/postcss.config.js" });
|
|
431
|
+
}
|
|
432
|
+
const directoriesToCopy = [
|
|
433
|
+
{
|
|
434
|
+
src: "/settings",
|
|
435
|
+
dest: "/settings",
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
src: "/src",
|
|
439
|
+
dest: "/src",
|
|
440
|
+
},
|
|
441
|
+
];
|
|
442
|
+
if (answer.docker) {
|
|
443
|
+
directoriesToCopy.push(
|
|
444
|
+
{ src: "/.dockerignore", dest: "/.dockerignore" },
|
|
445
|
+
{ src: "/docker-compose.yml", dest: "/docker-compose.yml" },
|
|
446
|
+
{ src: "/Dockerfile", dest: "/Dockerfile" },
|
|
447
|
+
{ src: "/apache.conf", dest: "/apache.conf" }
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
console.log("🚀 ~ directoriesToCopy:", directoriesToCopy);
|
|
451
|
+
filesToCopy.forEach(({ src, dest }) => {
|
|
452
|
+
const sourcePath = path.join(__dirname, src);
|
|
453
|
+
const destPath = path.join(baseDir, dest);
|
|
454
|
+
if (checkExcludeFiles(destPath)) return;
|
|
455
|
+
const code = fs.readFileSync(sourcePath, "utf8");
|
|
456
|
+
fs.writeFileSync(destPath, code, { flag: "w" });
|
|
457
|
+
});
|
|
458
|
+
await executeCopy(baseDir, directoriesToCopy, answer);
|
|
459
|
+
await updatePackageJson(baseDir, answer);
|
|
460
|
+
await updateComposerJson(baseDir);
|
|
461
|
+
if (!answer.backendOnly) {
|
|
462
|
+
await updateIndexJsForWebSocket(baseDir, answer);
|
|
463
|
+
}
|
|
464
|
+
if (answer.tailwindcss) {
|
|
465
|
+
modifyPostcssConfig(baseDir);
|
|
466
|
+
}
|
|
467
|
+
if (answer.tailwindcss || !answer.backendOnly || answer.swaggerDocs) {
|
|
468
|
+
modifyLayoutPHP(baseDir, answer);
|
|
469
|
+
}
|
|
470
|
+
const authSecret = generateAuthSecret();
|
|
471
|
+
const localStoreKey = generateHexEncodedKey();
|
|
472
|
+
const authCookieName = generateHexEncodedKey(8);
|
|
473
|
+
const functionCallSecret = generateHexEncodedKey(32);
|
|
474
|
+
const prismaPHPEnvContent = `# Authentication secret key for JWT or session encryption.
|
|
475
|
+
AUTH_SECRET="${authSecret}"
|
|
476
|
+
# Name of the authentication cookie.
|
|
477
|
+
AUTH_COOKIE_NAME="${authCookieName}"
|
|
478
|
+
|
|
479
|
+
# PHPMailer SMTP configuration (uncomment and set as needed)
|
|
480
|
+
# SMTP_HOST="smtp.gmail.com" # Your SMTP host
|
|
481
|
+
# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username
|
|
482
|
+
# SMTP_PASSWORD="123456" # Your SMTP password
|
|
483
|
+
# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port
|
|
484
|
+
# SMTP_ENCRYPTION="ssl" # ssl or tls
|
|
485
|
+
# MAIL_FROM="john.doe@gmail.com" # Sender email address
|
|
486
|
+
# MAIL_FROM_NAME="John Doe" # Sender name
|
|
487
|
+
|
|
488
|
+
# Show errors in the browser (development only). Set to false in production.
|
|
489
|
+
SHOW_ERRORS="true"
|
|
490
|
+
|
|
491
|
+
# Application timezone (default: UTC)
|
|
492
|
+
APP_TIMEZONE="UTC"
|
|
493
|
+
|
|
494
|
+
# Application environment (development or production)
|
|
495
|
+
APP_ENV="development"
|
|
496
|
+
|
|
497
|
+
# Enable or disable application cache (default: false)
|
|
498
|
+
CACHE_ENABLED="false"
|
|
499
|
+
# Cache time-to-live in seconds (default: 600)
|
|
500
|
+
CACHE_TTL="600"
|
|
501
|
+
|
|
502
|
+
# Local storage key for browser storage (auto-generated if not set).
|
|
503
|
+
# Spaces will be replaced with underscores and converted to lowercase.
|
|
504
|
+
LOCALSTORE_KEY="${localStoreKey}"
|
|
505
|
+
|
|
506
|
+
# Secret key for encrypting function calls.
|
|
507
|
+
FUNCTION_CALL_SECRET="${functionCallSecret}"
|
|
508
|
+
|
|
509
|
+
# Single or multiple origins (CSV or JSON array)
|
|
510
|
+
CORS_ALLOWED_ORIGINS=[]
|
|
511
|
+
|
|
512
|
+
# If you need cookies/Authorization across origins, keep this true
|
|
513
|
+
CORS_ALLOW_CREDENTIALS="true"
|
|
514
|
+
|
|
515
|
+
# Optional tuning
|
|
516
|
+
CORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"
|
|
517
|
+
CORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"
|
|
518
|
+
CORS_EXPOSE_HEADERS=""
|
|
519
|
+
CORS_MAX_AGE="86400"`;
|
|
520
|
+
if (answer.prisma) {
|
|
521
|
+
const prismaEnvContent = `# Environment variables declared in this file are automatically made available to Prisma.
|
|
522
|
+
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
|
523
|
+
|
|
524
|
+
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
|
525
|
+
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
|
526
|
+
|
|
527
|
+
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"`;
|
|
528
|
+
const envContent = `${prismaEnvContent}\n\n${prismaPHPEnvContent}`;
|
|
529
|
+
await createOrUpdateEnvFile(baseDir, envContent);
|
|
530
|
+
} else {
|
|
531
|
+
await createOrUpdateEnvFile(baseDir, prismaPHPEnvContent);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function getAnswer(predefinedAnswers = {}) {
|
|
535
|
+
console.log("🚀 ~ predefinedAnswers:", predefinedAnswers);
|
|
536
|
+
// If starter kit is specified, use non-interactive mode
|
|
537
|
+
if (predefinedAnswers.starterKit) {
|
|
538
|
+
const selectedKit = predefinedAnswers.starterKit;
|
|
539
|
+
let starterKit = null;
|
|
540
|
+
// Check built-in starter kits
|
|
541
|
+
if (STARTER_KITS[selectedKit]) {
|
|
542
|
+
starterKit = STARTER_KITS[selectedKit];
|
|
543
|
+
}
|
|
544
|
+
if (starterKit) {
|
|
545
|
+
console.log(chalk.blue(`Using starter kit: ${starterKit.name}`));
|
|
546
|
+
console.log(chalk.gray(starterKit.description));
|
|
547
|
+
const answer = {
|
|
548
|
+
projectName: predefinedAnswers.projectName ?? "my-app",
|
|
549
|
+
starterKit: selectedKit,
|
|
550
|
+
starterKitSource: predefinedAnswers.starterKitSource,
|
|
551
|
+
backendOnly: starterKit.features.backendOnly ?? false,
|
|
552
|
+
tailwindcss: starterKit.features.tailwindcss ?? false,
|
|
553
|
+
websocket: starterKit.features.websocket ?? false,
|
|
554
|
+
prisma: starterKit.features.prisma ?? false,
|
|
555
|
+
docker: starterKit.features.docker ?? false,
|
|
556
|
+
swaggerDocs: starterKit.features.swaggerDocs ?? false,
|
|
557
|
+
mcp: starterKit.features.mcp ?? false,
|
|
558
|
+
};
|
|
559
|
+
// Allow CLI overrides
|
|
560
|
+
const args = process.argv.slice(2);
|
|
561
|
+
if (args.includes("--backend-only")) answer.backendOnly = true;
|
|
562
|
+
if (args.includes("--swagger-docs")) answer.swaggerDocs = true;
|
|
563
|
+
if (args.includes("--tailwindcss")) answer.tailwindcss = true;
|
|
564
|
+
if (args.includes("--websocket")) answer.websocket = true;
|
|
565
|
+
if (args.includes("--mcp")) answer.mcp = true;
|
|
566
|
+
if (args.includes("--prisma")) answer.prisma = true;
|
|
567
|
+
if (args.includes("--docker")) answer.docker = true;
|
|
568
|
+
return answer; // ✅ Return immediately - no interactive prompts
|
|
569
|
+
}
|
|
570
|
+
// Handle custom starter kit
|
|
571
|
+
else if (predefinedAnswers.starterKitSource) {
|
|
572
|
+
console.log(
|
|
573
|
+
chalk.blue(
|
|
574
|
+
`Using custom starter kit from: ${predefinedAnswers.starterKitSource}`
|
|
575
|
+
)
|
|
576
|
+
);
|
|
577
|
+
const answer = {
|
|
578
|
+
projectName: predefinedAnswers.projectName ?? "my-app",
|
|
579
|
+
starterKit: selectedKit,
|
|
580
|
+
starterKitSource: predefinedAnswers.starterKitSource,
|
|
581
|
+
// Default features - will be overridden by starter kit config
|
|
582
|
+
backendOnly: false,
|
|
583
|
+
tailwindcss: true,
|
|
584
|
+
websocket: false,
|
|
585
|
+
prisma: true,
|
|
586
|
+
docker: false,
|
|
587
|
+
swaggerDocs: true,
|
|
588
|
+
mcp: false,
|
|
589
|
+
};
|
|
590
|
+
// Allow CLI overrides
|
|
591
|
+
const args = process.argv.slice(2);
|
|
592
|
+
if (args.includes("--backend-only")) answer.backendOnly = true;
|
|
593
|
+
if (args.includes("--swagger-docs")) answer.swaggerDocs = true;
|
|
594
|
+
if (args.includes("--tailwindcss")) answer.tailwindcss = true;
|
|
595
|
+
if (args.includes("--websocket")) answer.websocket = true;
|
|
596
|
+
if (args.includes("--mcp")) answer.mcp = true;
|
|
597
|
+
if (args.includes("--prisma")) answer.prisma = true;
|
|
598
|
+
if (args.includes("--docker")) answer.docker = true;
|
|
599
|
+
return answer; // ✅ Return immediately - no interactive prompts
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const questionsArray = [];
|
|
603
|
+
// Ask for project name if not provided
|
|
604
|
+
if (!predefinedAnswers.projectName) {
|
|
605
|
+
questionsArray.push({
|
|
606
|
+
type: "text",
|
|
607
|
+
name: "projectName",
|
|
608
|
+
message: "What is your project named?",
|
|
609
|
+
initial: "my-app",
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
// IMPORTANT: skip asking backendOnly if updateAnswer.isUpdate is true
|
|
613
|
+
if (!predefinedAnswers.backendOnly && !updateAnswer?.isUpdate) {
|
|
614
|
+
questionsArray.push({
|
|
615
|
+
type: "toggle",
|
|
616
|
+
name: "backendOnly",
|
|
617
|
+
message: `Would you like to create a ${chalk.blue(
|
|
618
|
+
"backend-only project"
|
|
619
|
+
)}?`,
|
|
620
|
+
initial: false,
|
|
621
|
+
active: "Yes",
|
|
622
|
+
inactive: "No",
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
const onCancel = () => {
|
|
626
|
+
console.log(chalk.red("Operation cancelled by the user."));
|
|
627
|
+
process.exit(0);
|
|
628
|
+
};
|
|
629
|
+
const initialResponse = await prompts(questionsArray, { onCancel });
|
|
630
|
+
console.log("🚀 ~ initialResponse:", initialResponse);
|
|
631
|
+
const nonBackendOnlyQuestionsArray = [];
|
|
632
|
+
const isBackendOnly =
|
|
633
|
+
initialResponse.backendOnly ?? predefinedAnswers.backendOnly ?? false;
|
|
634
|
+
if (isBackendOnly) {
|
|
635
|
+
// For backend-only project (skip Tailwind), but still ask other features
|
|
636
|
+
if (!predefinedAnswers.swaggerDocs) {
|
|
637
|
+
nonBackendOnlyQuestionsArray.push({
|
|
638
|
+
type: "toggle",
|
|
639
|
+
name: "swaggerDocs",
|
|
640
|
+
message: `Would you like to use ${chalk.blue("Swagger Docs")}?`,
|
|
641
|
+
initial: false,
|
|
642
|
+
active: "Yes",
|
|
643
|
+
inactive: "No",
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (!predefinedAnswers.websocket) {
|
|
647
|
+
nonBackendOnlyQuestionsArray.push({
|
|
648
|
+
type: "toggle",
|
|
649
|
+
name: "websocket",
|
|
650
|
+
message: `Would you like to use ${chalk.blue("Websocket")}?`,
|
|
651
|
+
initial: false,
|
|
652
|
+
active: "Yes",
|
|
653
|
+
inactive: "No",
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
if (!predefinedAnswers.mcp) {
|
|
657
|
+
nonBackendOnlyQuestionsArray.push({
|
|
658
|
+
type: "toggle",
|
|
659
|
+
name: "mcp",
|
|
660
|
+
message: `Would you like to use ${chalk.blue(
|
|
661
|
+
"MCP (Model Context Protocol)"
|
|
662
|
+
)}?`,
|
|
663
|
+
initial: false,
|
|
664
|
+
active: "Yes",
|
|
665
|
+
inactive: "No",
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (!predefinedAnswers.prisma) {
|
|
669
|
+
nonBackendOnlyQuestionsArray.push({
|
|
670
|
+
type: "toggle",
|
|
671
|
+
name: "prisma",
|
|
672
|
+
message: `Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,
|
|
673
|
+
initial: false,
|
|
674
|
+
active: "Yes",
|
|
675
|
+
inactive: "No",
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
if (!predefinedAnswers.docker) {
|
|
679
|
+
nonBackendOnlyQuestionsArray.push({
|
|
680
|
+
type: "toggle",
|
|
681
|
+
name: "docker",
|
|
682
|
+
message: `Would you like to use ${chalk.blue("Docker")}?`,
|
|
683
|
+
initial: false,
|
|
684
|
+
active: "Yes",
|
|
685
|
+
inactive: "No",
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
// For full-stack project, include Tailwind
|
|
690
|
+
if (!predefinedAnswers.swaggerDocs) {
|
|
691
|
+
nonBackendOnlyQuestionsArray.push({
|
|
692
|
+
type: "toggle",
|
|
693
|
+
name: "swaggerDocs",
|
|
694
|
+
message: `Would you like to use ${chalk.blue("Swagger Docs")}?`,
|
|
695
|
+
initial: false,
|
|
696
|
+
active: "Yes",
|
|
697
|
+
inactive: "No",
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (!predefinedAnswers.tailwindcss) {
|
|
701
|
+
nonBackendOnlyQuestionsArray.push({
|
|
702
|
+
type: "toggle",
|
|
703
|
+
name: "tailwindcss",
|
|
704
|
+
message: `Would you like to use ${chalk.blue("Tailwind CSS")}?`,
|
|
705
|
+
initial: false,
|
|
706
|
+
active: "Yes",
|
|
707
|
+
inactive: "No",
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
if (!predefinedAnswers.websocket) {
|
|
711
|
+
nonBackendOnlyQuestionsArray.push({
|
|
712
|
+
type: "toggle",
|
|
713
|
+
name: "websocket",
|
|
714
|
+
message: `Would you like to use ${chalk.blue("Websocket")}?`,
|
|
715
|
+
initial: false,
|
|
716
|
+
active: "Yes",
|
|
717
|
+
inactive: "No",
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (!predefinedAnswers.mcp) {
|
|
721
|
+
nonBackendOnlyQuestionsArray.push({
|
|
722
|
+
type: "toggle",
|
|
723
|
+
name: "mcp",
|
|
724
|
+
message: `Would you like to use ${chalk.blue(
|
|
725
|
+
"MCP (Model Context Protocol)"
|
|
726
|
+
)}?`,
|
|
727
|
+
initial: false,
|
|
728
|
+
active: "Yes",
|
|
729
|
+
inactive: "No",
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
if (!predefinedAnswers.prisma) {
|
|
733
|
+
nonBackendOnlyQuestionsArray.push({
|
|
734
|
+
type: "toggle",
|
|
735
|
+
name: "prisma",
|
|
736
|
+
message: `Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,
|
|
737
|
+
initial: false,
|
|
738
|
+
active: "Yes",
|
|
739
|
+
inactive: "No",
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
if (!predefinedAnswers.docker) {
|
|
743
|
+
nonBackendOnlyQuestionsArray.push({
|
|
744
|
+
type: "toggle",
|
|
745
|
+
name: "docker",
|
|
746
|
+
message: `Would you like to use ${chalk.blue("Docker")}?`,
|
|
747
|
+
initial: false,
|
|
748
|
+
active: "Yes",
|
|
749
|
+
inactive: "No",
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const nonBackendOnlyResponse = await prompts(nonBackendOnlyQuestionsArray, {
|
|
754
|
+
onCancel,
|
|
755
|
+
});
|
|
756
|
+
console.log("🚀 ~ nonBackendOnlyResponse:", nonBackendOnlyResponse);
|
|
757
|
+
return {
|
|
758
|
+
projectName: initialResponse.projectName
|
|
759
|
+
? String(initialResponse.projectName).trim().replace(/ /g, "-")
|
|
760
|
+
: predefinedAnswers.projectName ?? "my-app",
|
|
761
|
+
backendOnly:
|
|
762
|
+
initialResponse.backendOnly ?? predefinedAnswers.backendOnly ?? false,
|
|
763
|
+
swaggerDocs:
|
|
764
|
+
nonBackendOnlyResponse.swaggerDocs ??
|
|
765
|
+
predefinedAnswers.swaggerDocs ??
|
|
766
|
+
false,
|
|
767
|
+
tailwindcss:
|
|
768
|
+
nonBackendOnlyResponse.tailwindcss ??
|
|
769
|
+
predefinedAnswers.tailwindcss ??
|
|
770
|
+
false,
|
|
771
|
+
websocket:
|
|
772
|
+
nonBackendOnlyResponse.websocket ?? predefinedAnswers.websocket ?? false,
|
|
773
|
+
mcp: nonBackendOnlyResponse.mcp ?? predefinedAnswers.mcp ?? false,
|
|
774
|
+
prisma: nonBackendOnlyResponse.prisma ?? predefinedAnswers.prisma ?? false,
|
|
775
|
+
docker: nonBackendOnlyResponse.docker ?? predefinedAnswers.docker ?? false,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
async function uninstallNpmDependencies(baseDir, dependencies, isDev = false) {
|
|
779
|
+
console.log("Uninstalling dependencies:");
|
|
780
|
+
dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
|
|
781
|
+
// Prepare the npm uninstall command with the appropriate flag for dev dependencies
|
|
782
|
+
const npmUninstallCommand = `npm uninstall ${
|
|
783
|
+
isDev ? "--save-dev" : "--save"
|
|
784
|
+
} ${dependencies.join(" ")}`;
|
|
785
|
+
// Execute the npm uninstall command
|
|
786
|
+
execSync(npmUninstallCommand, {
|
|
787
|
+
stdio: "inherit",
|
|
788
|
+
cwd: baseDir,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async function uninstallComposerDependencies(baseDir, dependencies) {
|
|
792
|
+
console.log("Uninstalling Composer dependencies:");
|
|
793
|
+
dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
|
|
794
|
+
// Prepare the composer remove command
|
|
795
|
+
const composerRemoveCommand = `C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${dependencies.join(
|
|
796
|
+
" "
|
|
797
|
+
)}`;
|
|
798
|
+
// Execute the composer remove command
|
|
799
|
+
execSync(composerRemoveCommand, {
|
|
800
|
+
stdio: "inherit",
|
|
801
|
+
cwd: baseDir,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function fetchPackageVersion(packageName) {
|
|
805
|
+
return new Promise((resolve, reject) => {
|
|
806
|
+
https
|
|
807
|
+
.get(`https://registry.npmjs.org/${packageName}`, (res) => {
|
|
808
|
+
let data = "";
|
|
809
|
+
res.on("data", (chunk) => (data += chunk));
|
|
810
|
+
res.on("end", () => {
|
|
811
|
+
try {
|
|
812
|
+
const parsed = JSON.parse(data);
|
|
813
|
+
resolve(parsed["dist-tags"].latest);
|
|
814
|
+
} catch (error) {
|
|
815
|
+
reject(new Error("Failed to parse JSON response"));
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
})
|
|
819
|
+
.on("error", (err) => reject(err));
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
const readJsonFile = (filePath) => {
|
|
823
|
+
const jsonData = fs.readFileSync(filePath, "utf8");
|
|
824
|
+
return JSON.parse(jsonData);
|
|
825
|
+
};
|
|
826
|
+
function compareVersions(installedVersion, currentVersion) {
|
|
827
|
+
const installedVersionArray = installedVersion.split(".").map(Number);
|
|
828
|
+
const currentVersionArray = currentVersion.split(".").map(Number);
|
|
829
|
+
for (let i = 0; i < installedVersionArray.length; i++) {
|
|
830
|
+
if (installedVersionArray[i] > currentVersionArray[i]) {
|
|
831
|
+
return 1;
|
|
832
|
+
} else if (installedVersionArray[i] < currentVersionArray[i]) {
|
|
833
|
+
return -1;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
838
|
+
function getInstalledPackageVersion(packageName) {
|
|
839
|
+
try {
|
|
840
|
+
const output = execSync(`npm list -g ${packageName} --depth=0`).toString();
|
|
841
|
+
const versionMatch = output.match(
|
|
842
|
+
new RegExp(`${packageName}@(\\d+\\.\\d+\\.\\d+)`)
|
|
843
|
+
);
|
|
844
|
+
if (versionMatch) {
|
|
845
|
+
return versionMatch[1];
|
|
846
|
+
} else {
|
|
847
|
+
console.error(`Package ${packageName} is not installed`);
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
} catch (error) {
|
|
851
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
3
855
|
/**
|
|
4
856
|
* Install dependencies in the specified directory.
|
|
5
857
|
* @param {string} baseDir - The base directory where to install the dependencies.
|
|
@@ -7,7 +859,11 @@ import{execSync}from"child_process";import fs from"fs";import{fileURLToPath}from
|
|
|
7
859
|
* @param {boolean} [isDev=false] - Whether to install the dependencies as devDependencies.
|
|
8
860
|
*/
|
|
9
861
|
async function installNpmDependencies(baseDir, dependencies, isDev = false) {
|
|
10
|
-
|
|
862
|
+
if (!fs.existsSync(path.join(baseDir, "package.json"))) {
|
|
863
|
+
console.log("Initializing new Node.js project...");
|
|
864
|
+
} else {
|
|
865
|
+
console.log("Updating existing Node.js project...");
|
|
866
|
+
}
|
|
11
867
|
// Initialize a package.json if it doesn't exist
|
|
12
868
|
if (!fs.existsSync(path.join(baseDir, "package.json"))) {
|
|
13
869
|
execSync("npm init -y", {
|
|
@@ -32,27 +888,99 @@ async function installNpmDependencies(baseDir, dependencies, isDev = false) {
|
|
|
32
888
|
cwd: baseDir,
|
|
33
889
|
});
|
|
34
890
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
891
|
+
function getComposerCmd() {
|
|
892
|
+
try {
|
|
893
|
+
execSync("composer --version", { stdio: "ignore" });
|
|
894
|
+
return { cmd: "composer", baseArgs: [] };
|
|
895
|
+
} catch {
|
|
896
|
+
return {
|
|
897
|
+
cmd: "C:\\xampp\\php\\php.exe",
|
|
898
|
+
baseArgs: ["C:\\ProgramData\\ComposerSetup\\bin\\composer.phar"],
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
export async function installComposerDependencies(baseDir, dependencies) {
|
|
903
|
+
const { cmd, baseArgs } = getComposerCmd();
|
|
904
|
+
const composerJsonPath = path.join(baseDir, "composer.json");
|
|
905
|
+
const existsAlready = fs.existsSync(composerJsonPath);
|
|
906
|
+
console.log(
|
|
907
|
+
chalk.green(
|
|
908
|
+
`Composer project initialization: ${
|
|
909
|
+
existsAlready ? "Updating existing project…" : "Setting up new project…"
|
|
910
|
+
}`
|
|
911
|
+
)
|
|
912
|
+
);
|
|
913
|
+
/* ------------------------------------------------------------------ */
|
|
914
|
+
/* 1. Try composer init (quietly fall back if it fails) */
|
|
915
|
+
/* ------------------------------------------------------------------ */
|
|
916
|
+
if (!existsAlready) {
|
|
917
|
+
const initArgs = [
|
|
918
|
+
...baseArgs,
|
|
919
|
+
"init",
|
|
920
|
+
"--no-interaction",
|
|
921
|
+
"--name",
|
|
922
|
+
"tsnc/prisma-php-app",
|
|
923
|
+
"--require",
|
|
924
|
+
"php:^8.2",
|
|
925
|
+
"--type",
|
|
926
|
+
"project",
|
|
927
|
+
"--version",
|
|
928
|
+
"1.0.0",
|
|
929
|
+
];
|
|
930
|
+
const res = spawnSync(cmd, initArgs, { cwd: baseDir });
|
|
931
|
+
if (res.status !== 0) {
|
|
932
|
+
// Silent fallback: no logs, just write a minimal composer.json
|
|
933
|
+
fs.writeFileSync(
|
|
934
|
+
composerJsonPath,
|
|
935
|
+
JSON.stringify(
|
|
936
|
+
{
|
|
937
|
+
name: "tsnc/prisma-php-app",
|
|
938
|
+
type: "project",
|
|
939
|
+
version: "1.0.0",
|
|
940
|
+
require: { php: "^8.2" },
|
|
941
|
+
autoload: { "psr-4": { "": "src/" } },
|
|
942
|
+
},
|
|
943
|
+
null,
|
|
944
|
+
2
|
|
945
|
+
)
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/* 2. Ensure PSR-4 autoload entry ---------------------------------- */
|
|
950
|
+
const json = JSON.parse(fs.readFileSync(composerJsonPath, "utf8"));
|
|
951
|
+
json.autoload ??= {};
|
|
952
|
+
json.autoload["psr-4"] ??= {};
|
|
953
|
+
json.autoload["psr-4"][""] ??= "src/";
|
|
954
|
+
fs.writeFileSync(composerJsonPath, JSON.stringify(json, null, 2));
|
|
955
|
+
/* 3. Install dependencies ----------------------------------------- */
|
|
956
|
+
if (dependencies.length) {
|
|
957
|
+
console.log("Installing Composer dependencies:");
|
|
958
|
+
dependencies.forEach((d) => console.log(`- ${chalk.blue(d)}`));
|
|
39
959
|
execSync(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
960
|
+
`${cmd} ${[
|
|
961
|
+
...baseArgs,
|
|
962
|
+
"require",
|
|
963
|
+
"--no-interaction",
|
|
964
|
+
...dependencies,
|
|
965
|
+
].join(" ")}`,
|
|
966
|
+
{ stdio: "inherit", cwd: baseDir }
|
|
45
967
|
);
|
|
46
968
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
969
|
+
/* 4. Refresh lock when updating ----------------------------------- */
|
|
970
|
+
if (existsAlready) {
|
|
971
|
+
execSync(
|
|
972
|
+
`${cmd} ${[
|
|
973
|
+
...baseArgs,
|
|
974
|
+
"update",
|
|
975
|
+
"--lock",
|
|
976
|
+
"--no-install",
|
|
977
|
+
"--no-interaction",
|
|
978
|
+
].join(" ")}`,
|
|
979
|
+
{ stdio: "inherit", cwd: baseDir }
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
/* 5. Regenerate autoloader ---------------------------------------- */
|
|
983
|
+
execSync(`${cmd} ${[...baseArgs, "dump-autoload", "--quiet"].join(" ")}`, {
|
|
56
984
|
stdio: "inherit",
|
|
57
985
|
cwd: baseDir,
|
|
58
986
|
});
|
|
@@ -60,21 +988,20 @@ async function installComposerDependencies(baseDir, dependencies) {
|
|
|
60
988
|
const npmPinnedVersions = {
|
|
61
989
|
"@tailwindcss/postcss": "^4.1.11",
|
|
62
990
|
"@types/browser-sync": "^2.29.0",
|
|
63
|
-
"@types/node": "^24.
|
|
991
|
+
"@types/node": "^24.2.1",
|
|
64
992
|
"@types/prompts": "^2.4.9",
|
|
65
993
|
"browser-sync": "^3.0.4",
|
|
66
|
-
chalk: "^5.
|
|
67
|
-
|
|
68
|
-
cssnano: "^7.0.7",
|
|
994
|
+
chalk: "^5.5.0",
|
|
995
|
+
cssnano: "^7.1.0",
|
|
69
996
|
"http-proxy-middleware": "^3.0.5",
|
|
70
997
|
"npm-run-all": "^4.1.5",
|
|
71
|
-
"php-parser": "^3.2.
|
|
998
|
+
"php-parser": "^3.2.5",
|
|
72
999
|
postcss: "^8.5.6",
|
|
73
1000
|
"postcss-cli": "^11.0.1",
|
|
74
1001
|
prompts: "^2.4.2",
|
|
75
1002
|
tailwindcss: "^4.1.11",
|
|
76
1003
|
tsx: "^4.20.3",
|
|
77
|
-
typescript: "^5.
|
|
1004
|
+
typescript: "^5.9.2",
|
|
78
1005
|
};
|
|
79
1006
|
function npmPkg(name) {
|
|
80
1007
|
return npmPinnedVersions[name] ? `${name}@${npmPinnedVersions[name]}` : name;
|
|
@@ -88,64 +1015,451 @@ const composerPinnedVersions = {
|
|
|
88
1015
|
"symfony/uid": "^7.2.0",
|
|
89
1016
|
"brick/math": "^0.13.1",
|
|
90
1017
|
"cboden/ratchet": "^0.4.4",
|
|
91
|
-
"tsnc/prisma-php": "^1.0",
|
|
1018
|
+
"tsnc/prisma-php": "^1.0.0",
|
|
1019
|
+
"php-mcp/server": "3.3.0",
|
|
92
1020
|
};
|
|
93
1021
|
function composerPkg(name) {
|
|
94
1022
|
return composerPinnedVersions[name]
|
|
95
1023
|
? `${name}:${composerPinnedVersions[name]}`
|
|
96
1024
|
: name;
|
|
97
1025
|
}
|
|
1026
|
+
async function downloadStarterKit(starterKit, tempDir) {
|
|
1027
|
+
if (!starterKit.source) {
|
|
1028
|
+
throw new Error("No source defined for starter kit");
|
|
1029
|
+
}
|
|
1030
|
+
const { type, url, branch = "main", subfolder } = starterKit.source;
|
|
1031
|
+
switch (type) {
|
|
1032
|
+
case "git":
|
|
1033
|
+
console.log(chalk.blue(`Cloning ${starterKit.name} from ${url}...`));
|
|
1034
|
+
const cloneCommand = branch
|
|
1035
|
+
? `git clone -b ${branch} --depth 1 ${url} ${tempDir}`
|
|
1036
|
+
: `git clone --depth 1 ${url} ${tempDir}`;
|
|
1037
|
+
execSync(cloneCommand, { stdio: "inherit" });
|
|
1038
|
+
// Remove .git directory
|
|
1039
|
+
const gitDir = path.join(tempDir, ".git");
|
|
1040
|
+
if (fs.existsSync(gitDir)) {
|
|
1041
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
1042
|
+
}
|
|
1043
|
+
// Return the subfolder if specified
|
|
1044
|
+
return subfolder ? path.join(tempDir, subfolder) : tempDir;
|
|
1045
|
+
case "npm":
|
|
1046
|
+
console.log(chalk.blue(`Downloading ${starterKit.name} from npm...`));
|
|
1047
|
+
execSync(`npm pack ${url}`, { cwd: tempDir, stdio: "inherit" });
|
|
1048
|
+
// Extract the tarball
|
|
1049
|
+
const tarball = fs.readdirSync(tempDir).find((f) => f.endsWith(".tgz"));
|
|
1050
|
+
if (tarball) {
|
|
1051
|
+
execSync(`tar -xzf ${tarball}`, { cwd: tempDir });
|
|
1052
|
+
fs.unlinkSync(path.join(tempDir, tarball));
|
|
1053
|
+
return path.join(tempDir, "package");
|
|
1054
|
+
}
|
|
1055
|
+
throw new Error("Failed to extract npm package");
|
|
1056
|
+
case "url":
|
|
1057
|
+
throw new Error("URL download not implemented yet");
|
|
1058
|
+
default:
|
|
1059
|
+
throw new Error(`Unsupported source type: ${type}`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async function mergeStarterKitFiles(starterKitPath, projectPath, answer) {
|
|
1063
|
+
console.log(chalk.blue("Merging starter kit files..."));
|
|
1064
|
+
// Use the new copy function that respects exclusions
|
|
1065
|
+
copyRecursiveSyncWithExclusions(starterKitPath, projectPath, answer, true);
|
|
1066
|
+
// Look for starter kit specific configuration
|
|
1067
|
+
const starterKitConfig = path.join(starterKitPath, "starter-kit.json");
|
|
1068
|
+
if (fs.existsSync(starterKitConfig)) {
|
|
1069
|
+
const config = JSON.parse(fs.readFileSync(starterKitConfig, "utf8"));
|
|
1070
|
+
// Handle post-install scripts
|
|
1071
|
+
if (config.postInstall) {
|
|
1072
|
+
console.log(chalk.blue("Running post-install scripts..."));
|
|
1073
|
+
for (const script of config.postInstall) {
|
|
1074
|
+
console.log(chalk.gray(`Running: ${script}`));
|
|
1075
|
+
execSync(script, { cwd: projectPath, stdio: "inherit" });
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
// Handle additional dependencies
|
|
1079
|
+
if (config.additionalNpmDependencies) {
|
|
1080
|
+
await installNpmDependencies(
|
|
1081
|
+
projectPath,
|
|
1082
|
+
config.additionalNpmDependencies.map(npmPkg),
|
|
1083
|
+
true
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
if (config.additionalComposerDependencies) {
|
|
1087
|
+
await installComposerDependencies(
|
|
1088
|
+
projectPath,
|
|
1089
|
+
config.additionalComposerDependencies.map(composerPkg)
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
async function setupStarterKit(baseDir, answer) {
|
|
1095
|
+
if (!answer.starterKit) return;
|
|
1096
|
+
let starterKit = null;
|
|
1097
|
+
// Check if it's a built-in starter kit
|
|
1098
|
+
if (STARTER_KITS[answer.starterKit]) {
|
|
1099
|
+
starterKit = STARTER_KITS[answer.starterKit];
|
|
1100
|
+
}
|
|
1101
|
+
// Handle custom starter kit URL
|
|
1102
|
+
else if (answer.starterKitSource) {
|
|
1103
|
+
starterKit = {
|
|
1104
|
+
id: answer.starterKit,
|
|
1105
|
+
name: `Custom Starter Kit (${answer.starterKit})`,
|
|
1106
|
+
description: "Custom starter kit from external source",
|
|
1107
|
+
features: {}, // Will be determined from the downloaded kit
|
|
1108
|
+
requiredFiles: [],
|
|
1109
|
+
source: {
|
|
1110
|
+
type: "git", // Assume git for now, could be enhanced
|
|
1111
|
+
url: answer.starterKitSource,
|
|
1112
|
+
},
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
if (!starterKit) {
|
|
1116
|
+
console.warn(
|
|
1117
|
+
chalk.yellow(`Starter kit '${answer.starterKit}' not found. Skipping...`)
|
|
1118
|
+
);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
console.log(chalk.green(`Setting up ${starterKit.name}...`));
|
|
1122
|
+
// If it's a custom starter kit with source, download it
|
|
1123
|
+
if (starterKit.source) {
|
|
1124
|
+
const tempDir = path.join(baseDir, ".temp-starter-kit");
|
|
1125
|
+
try {
|
|
1126
|
+
// Create temp directory
|
|
1127
|
+
if (fs.existsSync(tempDir)) {
|
|
1128
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1129
|
+
}
|
|
1130
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
1131
|
+
// Download the starter kit
|
|
1132
|
+
const kitPath = await downloadStarterKit(starterKit, tempDir);
|
|
1133
|
+
// Merge files from starter kit
|
|
1134
|
+
await mergeStarterKitFiles(kitPath, baseDir, answer);
|
|
1135
|
+
// Check if starter kit has its own configuration
|
|
1136
|
+
const kitConfigPath = path.join(kitPath, "prisma-php-starter.json");
|
|
1137
|
+
if (fs.existsSync(kitConfigPath)) {
|
|
1138
|
+
const kitConfig = JSON.parse(fs.readFileSync(kitConfigPath, "utf8"));
|
|
1139
|
+
// Override features with starter kit configuration
|
|
1140
|
+
Object.assign(answer, kitConfig.features || {});
|
|
1141
|
+
console.log(chalk.green(`Applied starter kit configuration`));
|
|
1142
|
+
}
|
|
1143
|
+
// Clean up temp directory
|
|
1144
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
console.error(chalk.red(`Failed to setup starter kit: ${error}`));
|
|
1147
|
+
// Clean up temp directory on error
|
|
1148
|
+
if (fs.existsSync(tempDir)) {
|
|
1149
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1150
|
+
}
|
|
1151
|
+
throw error;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
// Run custom setup if defined
|
|
1155
|
+
if (starterKit.customSetup) {
|
|
1156
|
+
await starterKit.customSetup(baseDir, answer);
|
|
1157
|
+
}
|
|
1158
|
+
console.log(chalk.green(`✓ ${starterKit.name} setup complete!`));
|
|
1159
|
+
}
|
|
1160
|
+
function showStarterKits() {
|
|
1161
|
+
console.log(chalk.blue("\n🚀 Available Starter Kits:\n"));
|
|
1162
|
+
Object.values(STARTER_KITS).forEach((kit) => {
|
|
1163
|
+
const isCustom = kit.source ? " (Custom)" : " (Built-in)";
|
|
1164
|
+
console.log(chalk.green(` ${kit.id}${chalk.gray(isCustom)}`));
|
|
1165
|
+
console.log(` ${kit.name}`);
|
|
1166
|
+
console.log(chalk.gray(` ${kit.description}`));
|
|
1167
|
+
if (kit.source) {
|
|
1168
|
+
console.log(chalk.cyan(` Source: ${kit.source.url}`));
|
|
1169
|
+
}
|
|
1170
|
+
const features = Object.entries(kit.features)
|
|
1171
|
+
.filter(([, value]) => value === true)
|
|
1172
|
+
.map(([key]) => key)
|
|
1173
|
+
.join(", ");
|
|
1174
|
+
if (features) {
|
|
1175
|
+
console.log(chalk.magenta(` Features: ${features}`));
|
|
1176
|
+
}
|
|
1177
|
+
console.log();
|
|
1178
|
+
});
|
|
1179
|
+
console.log(chalk.yellow("Usage:"));
|
|
1180
|
+
console.log(` npx create-prisma-php-app my-project --starter-kit=basic`);
|
|
1181
|
+
console.log(
|
|
1182
|
+
` npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo`
|
|
1183
|
+
);
|
|
1184
|
+
console.log();
|
|
1185
|
+
}
|
|
1186
|
+
// Starter kit specific copy function that respects exclusions
|
|
1187
|
+
function copyRecursiveSyncWithExclusions(
|
|
1188
|
+
src,
|
|
1189
|
+
dest,
|
|
1190
|
+
answer,
|
|
1191
|
+
respectExclusions = true
|
|
1192
|
+
) {
|
|
1193
|
+
const exists = fs.existsSync(src);
|
|
1194
|
+
const stats = exists && fs.statSync(src);
|
|
1195
|
+
const isDirectory = exists && stats && stats.isDirectory();
|
|
1196
|
+
if (isDirectory) {
|
|
1197
|
+
const destLower = dest.toLowerCase();
|
|
1198
|
+
// Apply feature-based exclusions
|
|
1199
|
+
if (!answer.websocket && destLower.includes("websocket")) return;
|
|
1200
|
+
if (!answer.mcp && destLower.includes("mcp")) return;
|
|
1201
|
+
if (!answer.swaggerDocs && destLower.includes("swagger-docs")) return;
|
|
1202
|
+
if (
|
|
1203
|
+
answer.backendOnly &&
|
|
1204
|
+
(destLower.includes("js") ||
|
|
1205
|
+
destLower.includes("css") ||
|
|
1206
|
+
destLower.includes("assets"))
|
|
1207
|
+
)
|
|
1208
|
+
return;
|
|
1209
|
+
// Apply user-defined exclusions
|
|
1210
|
+
const destModified = dest.replace(/\\/g, "/");
|
|
1211
|
+
if (
|
|
1212
|
+
respectExclusions &&
|
|
1213
|
+
updateAnswer?.excludeFilePath?.includes(destModified)
|
|
1214
|
+
) {
|
|
1215
|
+
console.log(chalk.yellow(`Skipping excluded directory: ${destModified}`));
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
1219
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
1220
|
+
copyRecursiveSyncWithExclusions(
|
|
1221
|
+
path.join(src, childItemName),
|
|
1222
|
+
path.join(dest, childItemName),
|
|
1223
|
+
answer,
|
|
1224
|
+
respectExclusions
|
|
1225
|
+
);
|
|
1226
|
+
});
|
|
1227
|
+
} else {
|
|
1228
|
+
const fileName = path.basename(dest);
|
|
1229
|
+
// Always exclude critical config files from starter kit overwrites
|
|
1230
|
+
if (fileName === "prisma-php.json") {
|
|
1231
|
+
console.log(
|
|
1232
|
+
chalk.yellow(`Protecting config file: ${dest.replace(/\\/g, "/")}`)
|
|
1233
|
+
);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
// Apply user-defined exclusions for files
|
|
1237
|
+
if (respectExclusions && checkExcludeFiles(dest)) {
|
|
1238
|
+
console.log(
|
|
1239
|
+
chalk.yellow(`Skipping excluded file: ${dest.replace(/\\/g, "/")}`)
|
|
1240
|
+
);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
// Apply feature-based exclusions
|
|
1244
|
+
if (
|
|
1245
|
+
!answer.tailwindcss &&
|
|
1246
|
+
(dest.includes("tailwind.css") || dest.includes("styles.css"))
|
|
1247
|
+
)
|
|
1248
|
+
return;
|
|
1249
|
+
if (!answer.websocket && dest.includes("restart-websocket.ts")) return;
|
|
1250
|
+
if (!answer.mcp && dest.includes("restart-mcp.ts")) return;
|
|
1251
|
+
if (!answer.docker && dockerFiles.some((file) => dest.includes(file)))
|
|
1252
|
+
return;
|
|
1253
|
+
if (
|
|
1254
|
+
answer.backendOnly &&
|
|
1255
|
+
nonBackendFiles.some((file) => dest.includes(file))
|
|
1256
|
+
)
|
|
1257
|
+
return;
|
|
1258
|
+
if (!answer.backendOnly && dest.includes("route.php")) return;
|
|
1259
|
+
if (
|
|
1260
|
+
answer.backendOnly &&
|
|
1261
|
+
!answer.swaggerDocs &&
|
|
1262
|
+
dest.includes("layout.php")
|
|
1263
|
+
)
|
|
1264
|
+
return;
|
|
1265
|
+
if (!answer.swaggerDocs && dest.includes("swagger-config.ts")) return;
|
|
1266
|
+
if (answer.tailwindcss && dest.includes("index.css")) return;
|
|
1267
|
+
fs.copyFileSync(src, dest, 0);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function mergeConfigurationFiles(
|
|
1271
|
+
projectPath,
|
|
1272
|
+
answer,
|
|
1273
|
+
latestVersion,
|
|
1274
|
+
existingConfig
|
|
1275
|
+
) {
|
|
1276
|
+
const projectPathModified = projectPath.replace(/\\/g, "\\");
|
|
1277
|
+
const bsConfig = bsConfigUrls(projectPathModified);
|
|
1278
|
+
// If we have existing config, merge with it
|
|
1279
|
+
if (existingConfig) {
|
|
1280
|
+
console.log(chalk.blue("Merging with existing configuration..."));
|
|
1281
|
+
console.log(
|
|
1282
|
+
chalk.gray(
|
|
1283
|
+
`Preserving excludeFiles: ${JSON.stringify(
|
|
1284
|
+
existingConfig.excludeFiles || []
|
|
1285
|
+
)}`
|
|
1286
|
+
)
|
|
1287
|
+
);
|
|
1288
|
+
const mergedConfig = {
|
|
1289
|
+
...existingConfig, // Start with existing config to preserve all existing fields
|
|
1290
|
+
// Only update specific fields that should be updated for starter kits
|
|
1291
|
+
projectName: answer.projectName,
|
|
1292
|
+
projectRootPath: projectPathModified,
|
|
1293
|
+
bsTarget: bsConfig.bsTarget,
|
|
1294
|
+
bsPathRewrite: bsConfig.bsPathRewrite,
|
|
1295
|
+
version: latestVersion,
|
|
1296
|
+
// Update feature flags based on starter kit
|
|
1297
|
+
backendOnly: answer.backendOnly,
|
|
1298
|
+
swaggerDocs: answer.swaggerDocs,
|
|
1299
|
+
tailwindcss: answer.tailwindcss,
|
|
1300
|
+
websocket: answer.websocket,
|
|
1301
|
+
mcp: answer.mcp,
|
|
1302
|
+
prisma: answer.prisma,
|
|
1303
|
+
docker: answer.docker,
|
|
1304
|
+
// CRITICAL: Always preserve existing excludeFiles
|
|
1305
|
+
excludeFiles: existingConfig.excludeFiles || [],
|
|
1306
|
+
};
|
|
1307
|
+
fs.writeFileSync(
|
|
1308
|
+
path.join(projectPath, "prisma-php.json"),
|
|
1309
|
+
JSON.stringify(mergedConfig, null, 2),
|
|
1310
|
+
{ flag: "w" }
|
|
1311
|
+
);
|
|
1312
|
+
console.log(chalk.green("✓ Configuration merged successfully!"));
|
|
1313
|
+
console.log(
|
|
1314
|
+
chalk.green(
|
|
1315
|
+
`✓ Preserved ${
|
|
1316
|
+
(existingConfig.excludeFiles || []).length
|
|
1317
|
+
} excluded files`
|
|
1318
|
+
)
|
|
1319
|
+
);
|
|
1320
|
+
} else {
|
|
1321
|
+
console.log(chalk.blue("Creating new configuration..."));
|
|
1322
|
+
// New project - create fresh config
|
|
1323
|
+
const prismaPhpConfig = {
|
|
1324
|
+
projectName: answer.projectName,
|
|
1325
|
+
projectRootPath: projectPathModified,
|
|
1326
|
+
phpEnvironment: "XAMPP",
|
|
1327
|
+
phpRootPathExe: "C:\\xampp\\php\\php.exe",
|
|
1328
|
+
bsTarget: bsConfig.bsTarget,
|
|
1329
|
+
bsPathRewrite: bsConfig.bsPathRewrite,
|
|
1330
|
+
backendOnly: answer.backendOnly,
|
|
1331
|
+
swaggerDocs: answer.swaggerDocs,
|
|
1332
|
+
tailwindcss: answer.tailwindcss,
|
|
1333
|
+
websocket: answer.websocket,
|
|
1334
|
+
mcp: answer.mcp,
|
|
1335
|
+
prisma: answer.prisma,
|
|
1336
|
+
docker: answer.docker,
|
|
1337
|
+
version: latestVersion,
|
|
1338
|
+
excludeFiles: [],
|
|
1339
|
+
};
|
|
1340
|
+
fs.writeFileSync(
|
|
1341
|
+
path.join(projectPath, "prisma-php.json"),
|
|
1342
|
+
JSON.stringify(prismaPhpConfig, null, 2),
|
|
1343
|
+
{ flag: "w" }
|
|
1344
|
+
);
|
|
1345
|
+
console.log(chalk.green("✓ New configuration created"));
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
98
1348
|
async function main() {
|
|
99
1349
|
try {
|
|
100
1350
|
const args = process.argv.slice(2);
|
|
101
1351
|
let projectName = args[0];
|
|
1352
|
+
// Parse starter kit arguments
|
|
1353
|
+
const starterKitArg = args.find((arg) => arg.startsWith("--starter-kit="));
|
|
1354
|
+
const starterKitFromArgs = starterKitArg?.split("=")[1];
|
|
1355
|
+
// Parse custom starter kit source
|
|
1356
|
+
const starterKitSourceArg = args.find((arg) =>
|
|
1357
|
+
arg.startsWith("--starter-kit-source=")
|
|
1358
|
+
);
|
|
1359
|
+
const starterKitSource = starterKitSourceArg?.split("=")[1];
|
|
1360
|
+
// Show help
|
|
1361
|
+
if (args.includes("--list-starter-kits")) {
|
|
1362
|
+
showStarterKits();
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
102
1365
|
let answer = null;
|
|
103
1366
|
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
|
-
}
|
|
124
1367
|
const currentDir = process.cwd();
|
|
125
1368
|
const configPath = path.join(currentDir, "prisma-php.json");
|
|
1369
|
+
const projectNamePath = path.join(currentDir, projectName);
|
|
1370
|
+
const projectNameConfigPath = path.join(
|
|
1371
|
+
projectNamePath,
|
|
1372
|
+
"prisma-php.json"
|
|
1373
|
+
);
|
|
1374
|
+
// Check if there's an existing config in current directory or project directory
|
|
1375
|
+
let existingConfigPath = null;
|
|
126
1376
|
if (fs.existsSync(configPath)) {
|
|
127
|
-
|
|
1377
|
+
existingConfigPath = configPath;
|
|
1378
|
+
} else if (fs.existsSync(projectNameConfigPath)) {
|
|
1379
|
+
existingConfigPath = projectNameConfigPath;
|
|
1380
|
+
}
|
|
1381
|
+
// If we found an existing config and we're using a starter kit, load exclusions
|
|
1382
|
+
if (existingConfigPath && (starterKitFromArgs || starterKitSource)) {
|
|
1383
|
+
const localSettings = readJsonFile(existingConfigPath);
|
|
128
1384
|
let excludeFiles = [];
|
|
129
1385
|
localSettings.excludeFiles?.map((file) => {
|
|
130
|
-
const filePath = path.join(
|
|
1386
|
+
const filePath = path.join(
|
|
1387
|
+
existingConfigPath === configPath ? currentDir : projectNamePath,
|
|
1388
|
+
file
|
|
1389
|
+
);
|
|
131
1390
|
if (fs.existsSync(filePath))
|
|
132
1391
|
excludeFiles.push(filePath.replace(/\\/g, "/"));
|
|
133
1392
|
});
|
|
1393
|
+
// Set updateAnswer to respect exclusions during starter kit setup
|
|
134
1394
|
updateAnswer = {
|
|
135
1395
|
projectName,
|
|
136
|
-
backendOnly:
|
|
137
|
-
swaggerDocs:
|
|
138
|
-
tailwindcss:
|
|
139
|
-
websocket:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1396
|
+
backendOnly: localSettings.backendOnly,
|
|
1397
|
+
swaggerDocs: localSettings.swaggerDocs,
|
|
1398
|
+
tailwindcss: localSettings.tailwindcss,
|
|
1399
|
+
websocket: localSettings.websocket,
|
|
1400
|
+
mcp: localSettings.mcp,
|
|
1401
|
+
prisma: localSettings.prisma,
|
|
1402
|
+
docker: localSettings.docker,
|
|
1403
|
+
isUpdate: false, // Not a true update, but we need exclusions to work
|
|
143
1404
|
excludeFiles: localSettings.excludeFiles ?? [],
|
|
144
1405
|
excludeFilePath: excludeFiles ?? [],
|
|
145
|
-
filePath:
|
|
1406
|
+
filePath:
|
|
1407
|
+
existingConfigPath === configPath ? currentDir : projectNamePath,
|
|
1408
|
+
};
|
|
1409
|
+
// For updates, use existing settings but allow CLI overrides
|
|
1410
|
+
const predefinedAnswers = {
|
|
1411
|
+
projectName,
|
|
1412
|
+
backendOnly:
|
|
1413
|
+
args.includes("--backend-only") || localSettings.backendOnly,
|
|
1414
|
+
swaggerDocs:
|
|
1415
|
+
args.includes("--swagger-docs") || localSettings.swaggerDocs,
|
|
1416
|
+
tailwindcss:
|
|
1417
|
+
args.includes("--tailwindcss") || localSettings.tailwindcss,
|
|
1418
|
+
websocket: args.includes("--websocket") || localSettings.websocket,
|
|
1419
|
+
prisma: args.includes("--prisma") || localSettings.prisma,
|
|
1420
|
+
docker: args.includes("--docker") || localSettings.docker,
|
|
1421
|
+
mcp: args.includes("--mcp") || localSettings.mcp,
|
|
1422
|
+
};
|
|
1423
|
+
answer = await getAnswer(predefinedAnswers);
|
|
1424
|
+
// IMPORTANT: Update updateAnswer with the NEW answer after getting user input
|
|
1425
|
+
if (answer !== null) {
|
|
1426
|
+
updateAnswer = {
|
|
1427
|
+
projectName,
|
|
1428
|
+
backendOnly: answer.backendOnly,
|
|
1429
|
+
swaggerDocs: answer.swaggerDocs,
|
|
1430
|
+
tailwindcss: answer.tailwindcss,
|
|
1431
|
+
websocket: answer.websocket,
|
|
1432
|
+
mcp: answer.mcp,
|
|
1433
|
+
prisma: answer.prisma,
|
|
1434
|
+
docker: answer.docker,
|
|
1435
|
+
isUpdate: true,
|
|
1436
|
+
excludeFiles: localSettings.excludeFiles ?? [],
|
|
1437
|
+
excludeFilePath: excludeFiles ?? [],
|
|
1438
|
+
filePath: currentDir,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
} else {
|
|
1442
|
+
// New project
|
|
1443
|
+
const predefinedAnswers = {
|
|
1444
|
+
projectName,
|
|
1445
|
+
starterKit: starterKitFromArgs,
|
|
1446
|
+
starterKitSource: starterKitSource,
|
|
1447
|
+
backendOnly: args.includes("--backend-only"),
|
|
1448
|
+
swaggerDocs: args.includes("--swagger-docs"),
|
|
1449
|
+
tailwindcss: args.includes("--tailwindcss"),
|
|
1450
|
+
websocket: args.includes("--websocket"),
|
|
1451
|
+
mcp: args.includes("--mcp"),
|
|
1452
|
+
prisma: args.includes("--prisma"),
|
|
1453
|
+
docker: args.includes("--docker"),
|
|
146
1454
|
};
|
|
1455
|
+
answer = await getAnswer(predefinedAnswers);
|
|
1456
|
+
}
|
|
1457
|
+
if (answer === null) {
|
|
1458
|
+
console.log(chalk.red("Installation cancelled."));
|
|
1459
|
+
return;
|
|
147
1460
|
}
|
|
148
1461
|
} else {
|
|
1462
|
+
// Interactive mode
|
|
149
1463
|
answer = await getAnswer();
|
|
150
1464
|
}
|
|
151
1465
|
if (answer === null) {
|
|
@@ -176,12 +1490,44 @@ async function main() {
|
|
|
176
1490
|
execSync("npm install -g create-prisma-php-app", { stdio: "inherit" });
|
|
177
1491
|
}
|
|
178
1492
|
// Create the project directory
|
|
179
|
-
if (!projectName) fs.mkdirSync(answer.projectName);
|
|
180
1493
|
const currentDir = process.cwd();
|
|
181
|
-
let projectPath
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
1494
|
+
let projectPath;
|
|
1495
|
+
if (projectName) {
|
|
1496
|
+
// Check if we're in an existing project (has prisma-php.json) or creating a new one
|
|
1497
|
+
const configPath = path.join(currentDir, "prisma-php.json");
|
|
1498
|
+
const projectNamePath = path.join(currentDir, projectName);
|
|
1499
|
+
const projectNameConfigPath = path.join(
|
|
1500
|
+
projectNamePath,
|
|
1501
|
+
"prisma-php.json"
|
|
1502
|
+
);
|
|
1503
|
+
if (fs.existsSync(configPath)) {
|
|
1504
|
+
// We're updating an existing project in current directory
|
|
1505
|
+
projectPath = currentDir;
|
|
1506
|
+
} else if (
|
|
1507
|
+
fs.existsSync(projectNamePath) &&
|
|
1508
|
+
fs.existsSync(projectNameConfigPath)
|
|
1509
|
+
) {
|
|
1510
|
+
// We're updating an existing project in the named directory
|
|
1511
|
+
projectPath = projectNamePath;
|
|
1512
|
+
process.chdir(projectNamePath);
|
|
1513
|
+
} else {
|
|
1514
|
+
// We're creating a new project with the given name
|
|
1515
|
+
if (!fs.existsSync(projectNamePath)) {
|
|
1516
|
+
fs.mkdirSync(projectNamePath, { recursive: true });
|
|
1517
|
+
}
|
|
1518
|
+
projectPath = projectNamePath;
|
|
1519
|
+
process.chdir(projectNamePath);
|
|
1520
|
+
}
|
|
1521
|
+
} else {
|
|
1522
|
+
// Interactive mode - create directory with answer.projectName
|
|
1523
|
+
fs.mkdirSync(answer.projectName, { recursive: true });
|
|
1524
|
+
projectPath = path.join(currentDir, answer.projectName);
|
|
1525
|
+
process.chdir(answer.projectName);
|
|
1526
|
+
}
|
|
1527
|
+
// Add starter kit setup before npm/composer installation
|
|
1528
|
+
if (answer.starterKit) {
|
|
1529
|
+
await setupStarterKit(projectPath, answer);
|
|
1530
|
+
}
|
|
185
1531
|
let npmDependencies = [
|
|
186
1532
|
npmPkg("typescript"),
|
|
187
1533
|
npmPkg("@types/node"),
|
|
@@ -201,7 +1547,7 @@ async function main() {
|
|
|
201
1547
|
composerPkg("ezyang/htmlpurifier"),
|
|
202
1548
|
composerPkg("symfony/uid"),
|
|
203
1549
|
composerPkg("brick/math"),
|
|
204
|
-
composerPkg("tsnc/prisma-php"),
|
|
1550
|
+
// composerPkg("tsnc/prisma-php"),
|
|
205
1551
|
];
|
|
206
1552
|
if (answer.swaggerDocs) {
|
|
207
1553
|
npmDependencies.push(
|
|
@@ -222,9 +1568,11 @@ async function main() {
|
|
|
222
1568
|
);
|
|
223
1569
|
}
|
|
224
1570
|
if (answer.websocket) {
|
|
225
|
-
npmDependencies.push(npmPkg("chokidar-cli"));
|
|
226
1571
|
composerDependencies.push("cboden/ratchet");
|
|
227
1572
|
}
|
|
1573
|
+
if (answer.mcp) {
|
|
1574
|
+
composerDependencies.push("php-mcp/server");
|
|
1575
|
+
}
|
|
228
1576
|
if (answer.prisma) {
|
|
229
1577
|
execSync("npm install -g prisma-client-php", { stdio: "inherit" });
|
|
230
1578
|
}
|
|
@@ -266,24 +1614,57 @@ async function main() {
|
|
|
266
1614
|
if (updateAnswer?.isUpdate) {
|
|
267
1615
|
const updateUninstallNpmDependencies = [];
|
|
268
1616
|
const updateUninstallComposerDependencies = [];
|
|
1617
|
+
// Helper function to check if a composer package is installed
|
|
1618
|
+
const isComposerPackageInstalled = (packageName) => {
|
|
1619
|
+
try {
|
|
1620
|
+
const composerJsonPath = path.join(projectPath, "composer.json");
|
|
1621
|
+
if (fs.existsSync(composerJsonPath)) {
|
|
1622
|
+
const composerJson = JSON.parse(
|
|
1623
|
+
fs.readFileSync(composerJsonPath, "utf8")
|
|
1624
|
+
);
|
|
1625
|
+
return !!(
|
|
1626
|
+
composerJson.require && composerJson.require[packageName]
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
return false;
|
|
1630
|
+
} catch {
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
// Helper function to check if an npm package is installed
|
|
1635
|
+
const isNpmPackageInstalled = (packageName) => {
|
|
1636
|
+
try {
|
|
1637
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
1638
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1639
|
+
const packageJson = JSON.parse(
|
|
1640
|
+
fs.readFileSync(packageJsonPath, "utf8")
|
|
1641
|
+
);
|
|
1642
|
+
return !!(
|
|
1643
|
+
(packageJson.dependencies &&
|
|
1644
|
+
packageJson.dependencies[packageName]) ||
|
|
1645
|
+
(packageJson.devDependencies &&
|
|
1646
|
+
packageJson.devDependencies[packageName])
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
return false;
|
|
1650
|
+
} catch {
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
269
1654
|
if (updateAnswer.backendOnly) {
|
|
270
1655
|
nonBackendFiles.forEach((file) => {
|
|
271
1656
|
const filePath = path.join(projectPath, "src", "app", file);
|
|
272
1657
|
if (fs.existsSync(filePath)) {
|
|
273
|
-
fs.unlinkSync(filePath);
|
|
1658
|
+
fs.unlinkSync(filePath);
|
|
274
1659
|
console.log(`${file} was deleted successfully.`);
|
|
275
|
-
} else {
|
|
276
|
-
console.log(`${file} does not exist.`);
|
|
277
1660
|
}
|
|
278
1661
|
});
|
|
279
1662
|
const backendOnlyFolders = ["js", "css"];
|
|
280
1663
|
backendOnlyFolders.forEach((folder) => {
|
|
281
1664
|
const folderPath = path.join(projectPath, "src", "app", folder);
|
|
282
1665
|
if (fs.existsSync(folderPath)) {
|
|
283
|
-
fs.rmSync(folderPath, { recursive: true, force: true });
|
|
1666
|
+
fs.rmSync(folderPath, { recursive: true, force: true });
|
|
284
1667
|
console.log(`${folder} was deleted successfully.`);
|
|
285
|
-
} else {
|
|
286
|
-
console.log(`${folder} does not exist.`);
|
|
287
1668
|
}
|
|
288
1669
|
});
|
|
289
1670
|
}
|
|
@@ -295,81 +1676,108 @@ async function main() {
|
|
|
295
1676
|
"swagger-docs"
|
|
296
1677
|
);
|
|
297
1678
|
if (fs.existsSync(swaggerDocsFolder)) {
|
|
298
|
-
fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
|
|
1679
|
+
fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
|
|
299
1680
|
console.log(`swagger-docs was deleted successfully.`);
|
|
300
1681
|
}
|
|
301
1682
|
const swaggerFiles = ["swagger-config.ts"];
|
|
302
1683
|
swaggerFiles.forEach((file) => {
|
|
303
1684
|
const filePath = path.join(projectPath, "settings", file);
|
|
304
1685
|
if (fs.existsSync(filePath)) {
|
|
305
|
-
fs.unlinkSync(filePath);
|
|
1686
|
+
fs.unlinkSync(filePath);
|
|
306
1687
|
console.log(`${file} was deleted successfully.`);
|
|
307
|
-
} else {
|
|
308
|
-
console.log(`${file} does not exist.`);
|
|
309
1688
|
}
|
|
310
1689
|
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
"
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1690
|
+
// Only add to uninstall list if packages are actually installed
|
|
1691
|
+
if (isNpmPackageInstalled("swagger-jsdoc")) {
|
|
1692
|
+
updateUninstallNpmDependencies.push("swagger-jsdoc");
|
|
1693
|
+
}
|
|
1694
|
+
if (isNpmPackageInstalled("@types/swagger-jsdoc")) {
|
|
1695
|
+
updateUninstallNpmDependencies.push("@types/swagger-jsdoc");
|
|
1696
|
+
}
|
|
1697
|
+
if (isNpmPackageInstalled("prompts")) {
|
|
1698
|
+
updateUninstallNpmDependencies.push("prompts");
|
|
1699
|
+
}
|
|
1700
|
+
if (isNpmPackageInstalled("@types/prompts")) {
|
|
1701
|
+
updateUninstallNpmDependencies.push("@types/prompts");
|
|
1702
|
+
}
|
|
317
1703
|
}
|
|
318
1704
|
if (!updateAnswer.tailwindcss) {
|
|
319
1705
|
const tailwindFiles = ["postcss.config.js"];
|
|
320
1706
|
tailwindFiles.forEach((file) => {
|
|
321
1707
|
const filePath = path.join(projectPath, file);
|
|
322
1708
|
if (fs.existsSync(filePath)) {
|
|
323
|
-
fs.unlinkSync(filePath);
|
|
1709
|
+
fs.unlinkSync(filePath);
|
|
324
1710
|
console.log(`${file} was deleted successfully.`);
|
|
325
|
-
} else {
|
|
326
|
-
console.log(`${file} does not exist.`);
|
|
327
1711
|
}
|
|
328
1712
|
});
|
|
329
|
-
|
|
1713
|
+
// Only add to uninstall list if packages are actually installed
|
|
1714
|
+
const tailwindPackages = [
|
|
330
1715
|
"tailwindcss",
|
|
331
1716
|
"postcss",
|
|
332
1717
|
"postcss-cli",
|
|
333
1718
|
"@tailwindcss/postcss",
|
|
334
|
-
"cssnano"
|
|
335
|
-
|
|
1719
|
+
"cssnano",
|
|
1720
|
+
];
|
|
1721
|
+
tailwindPackages.forEach((pkg) => {
|
|
1722
|
+
if (isNpmPackageInstalled(pkg)) {
|
|
1723
|
+
updateUninstallNpmDependencies.push(pkg);
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
336
1726
|
}
|
|
337
1727
|
if (!updateAnswer.websocket) {
|
|
338
|
-
const websocketFiles = [
|
|
339
|
-
"restart-websocket.ts",
|
|
340
|
-
"restart-websocket.bat",
|
|
341
|
-
];
|
|
1728
|
+
const websocketFiles = ["restart-websocket.ts"];
|
|
342
1729
|
websocketFiles.forEach((file) => {
|
|
343
1730
|
const filePath = path.join(projectPath, "settings", file);
|
|
344
1731
|
if (fs.existsSync(filePath)) {
|
|
345
|
-
fs.unlinkSync(filePath);
|
|
1732
|
+
fs.unlinkSync(filePath);
|
|
346
1733
|
console.log(`${file} was deleted successfully.`);
|
|
347
|
-
} else {
|
|
348
|
-
console.log(`${file} does not exist.`);
|
|
349
1734
|
}
|
|
350
1735
|
});
|
|
351
|
-
const websocketFolder = path.join(
|
|
1736
|
+
const websocketFolder = path.join(
|
|
1737
|
+
projectPath,
|
|
1738
|
+
"src",
|
|
1739
|
+
"Lib",
|
|
1740
|
+
"Websocket"
|
|
1741
|
+
);
|
|
352
1742
|
if (fs.existsSync(websocketFolder)) {
|
|
353
|
-
fs.rmSync(websocketFolder, { recursive: true, force: true });
|
|
1743
|
+
fs.rmSync(websocketFolder, { recursive: true, force: true });
|
|
354
1744
|
console.log(`Websocket folder was deleted successfully.`);
|
|
355
1745
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
1746
|
+
// composer package for websocket only
|
|
1747
|
+
if (isComposerPackageInstalled("cboden/ratchet")) {
|
|
1748
|
+
updateUninstallComposerDependencies.push("cboden/ratchet");
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
if (!updateAnswer.mcp) {
|
|
1752
|
+
const mcpFiles = ["restart-mcp.ts"];
|
|
1753
|
+
mcpFiles.forEach((file) => {
|
|
1754
|
+
const filePath = path.join(projectPath, "settings", file);
|
|
1755
|
+
if (fs.existsSync(filePath)) {
|
|
1756
|
+
fs.unlinkSync(filePath);
|
|
1757
|
+
console.log(`${file} was deleted successfully.`);
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
const mcpFolder = path.join(projectPath, "src", "Lib", "MCP");
|
|
1761
|
+
if (fs.existsSync(mcpFolder)) {
|
|
1762
|
+
fs.rmSync(mcpFolder, { recursive: true, force: true });
|
|
1763
|
+
console.log(`MCP folder was deleted successfully.`);
|
|
1764
|
+
}
|
|
1765
|
+
// composer package for MCP only
|
|
1766
|
+
if (isComposerPackageInstalled("php-mcp/server")) {
|
|
1767
|
+
updateUninstallComposerDependencies.push("php-mcp/server");
|
|
363
1768
|
}
|
|
364
|
-
updateUninstallNpmDependencies.push("chokidar-cli");
|
|
365
|
-
updateUninstallComposerDependencies.push("cboden/ratchet");
|
|
366
1769
|
}
|
|
367
1770
|
if (!updateAnswer.prisma) {
|
|
368
|
-
|
|
1771
|
+
const prismaPackages = [
|
|
369
1772
|
"prisma",
|
|
370
1773
|
"@prisma/client",
|
|
371
|
-
"@prisma/internals"
|
|
372
|
-
|
|
1774
|
+
"@prisma/internals",
|
|
1775
|
+
];
|
|
1776
|
+
prismaPackages.forEach((pkg) => {
|
|
1777
|
+
if (isNpmPackageInstalled(pkg)) {
|
|
1778
|
+
updateUninstallNpmDependencies.push(pkg);
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
373
1781
|
}
|
|
374
1782
|
if (!updateAnswer.docker) {
|
|
375
1783
|
const dockerFiles = [
|
|
@@ -381,49 +1789,127 @@ async function main() {
|
|
|
381
1789
|
dockerFiles.forEach((file) => {
|
|
382
1790
|
const filePath = path.join(projectPath, file);
|
|
383
1791
|
if (fs.existsSync(filePath)) {
|
|
384
|
-
fs.unlinkSync(filePath);
|
|
1792
|
+
fs.unlinkSync(filePath);
|
|
385
1793
|
console.log(`${file} was deleted successfully.`);
|
|
386
|
-
} else {
|
|
387
|
-
console.log(`${file} does not exist.`);
|
|
388
1794
|
}
|
|
389
1795
|
});
|
|
390
1796
|
}
|
|
391
|
-
if
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
);
|
|
1797
|
+
// Only uninstall if there are packages to uninstall
|
|
1798
|
+
const uniq = (arr) => Array.from(new Set(arr));
|
|
1799
|
+
const npmToUninstall = uniq(updateUninstallNpmDependencies);
|
|
1800
|
+
const composerToUninstall = uniq(updateUninstallComposerDependencies);
|
|
1801
|
+
if (npmToUninstall.length > 0) {
|
|
1802
|
+
console.log(`Uninstalling npm packages: ${npmToUninstall.join(", ")}`);
|
|
1803
|
+
await uninstallNpmDependencies(projectPath, npmToUninstall, true);
|
|
397
1804
|
}
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
updateUninstallComposerDependencies
|
|
1805
|
+
if (composerToUninstall.length > 0) {
|
|
1806
|
+
console.log(
|
|
1807
|
+
`Uninstalling composer packages: ${composerToUninstall.join(", ")}`
|
|
402
1808
|
);
|
|
1809
|
+
await uninstallComposerDependencies(projectPath, composerToUninstall);
|
|
403
1810
|
}
|
|
404
1811
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
1812
|
+
// Check for existing config to merge with
|
|
1813
|
+
let existingConfig = null;
|
|
1814
|
+
const finalProjectConfigPath = path.join(projectPath, "prisma-php.json");
|
|
1815
|
+
// Function to find prisma-php.json files with excludeFiles
|
|
1816
|
+
const findConfigWithExclusions = (searchDir) => {
|
|
1817
|
+
const entries = fs.readdirSync(searchDir, { withFileTypes: true });
|
|
1818
|
+
for (const entry of entries) {
|
|
1819
|
+
if (entry.isDirectory()) {
|
|
1820
|
+
const subConfigPath = path.join(
|
|
1821
|
+
searchDir,
|
|
1822
|
+
entry.name,
|
|
1823
|
+
"prisma-php.json"
|
|
1824
|
+
);
|
|
1825
|
+
if (fs.existsSync(subConfigPath)) {
|
|
1826
|
+
try {
|
|
1827
|
+
const config = JSON.parse(fs.readFileSync(subConfigPath, "utf8"));
|
|
1828
|
+
if (config.excludeFiles && config.excludeFiles.length > 0) {
|
|
1829
|
+
console.log(
|
|
1830
|
+
chalk.blue(
|
|
1831
|
+
`Found configuration with exclusions in: ${entry.name}/`
|
|
1832
|
+
)
|
|
1833
|
+
);
|
|
1834
|
+
console.log(
|
|
1835
|
+
chalk.gray(
|
|
1836
|
+
`Found excludeFiles: ${JSON.stringify(config.excludeFiles)}`
|
|
1837
|
+
)
|
|
1838
|
+
);
|
|
1839
|
+
return config;
|
|
1840
|
+
}
|
|
1841
|
+
} catch (error) {
|
|
1842
|
+
console.warn(
|
|
1843
|
+
chalk.yellow(`Could not read config in ${entry.name}/`)
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return null;
|
|
422
1850
|
};
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
1851
|
+
// First, look in current directory for prisma-php.json
|
|
1852
|
+
const currentDirConfigPath = path.join(currentDir, "prisma-php.json");
|
|
1853
|
+
if (
|
|
1854
|
+
fs.existsSync(currentDirConfigPath) &&
|
|
1855
|
+
currentDirConfigPath !== finalProjectConfigPath
|
|
1856
|
+
) {
|
|
1857
|
+
try {
|
|
1858
|
+
const rawConfig = fs.readFileSync(currentDirConfigPath, "utf8");
|
|
1859
|
+
existingConfig = JSON.parse(rawConfig);
|
|
1860
|
+
console.log(
|
|
1861
|
+
chalk.blue("Found existing configuration in current directory")
|
|
1862
|
+
);
|
|
1863
|
+
console.log(
|
|
1864
|
+
chalk.gray(
|
|
1865
|
+
`Current dir excludeFiles: ${JSON.stringify(
|
|
1866
|
+
existingConfig.excludeFiles || []
|
|
1867
|
+
)}`
|
|
1868
|
+
)
|
|
1869
|
+
);
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
console.warn(chalk.yellow("Could not read current directory config"));
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
// If no config in current directory, search subdirectories for configs with excludeFiles
|
|
1875
|
+
if (!existingConfig) {
|
|
1876
|
+
console.log(
|
|
1877
|
+
chalk.blue("Searching for existing configurations with exclusions...")
|
|
1878
|
+
);
|
|
1879
|
+
existingConfig = findConfigWithExclusions(currentDir);
|
|
1880
|
+
}
|
|
1881
|
+
// If still no config, check the target project directory
|
|
1882
|
+
if (!existingConfig && fs.existsSync(finalProjectConfigPath)) {
|
|
1883
|
+
try {
|
|
1884
|
+
const rawConfig = fs.readFileSync(finalProjectConfigPath, "utf8");
|
|
1885
|
+
existingConfig = JSON.parse(rawConfig);
|
|
1886
|
+
console.log(
|
|
1887
|
+
chalk.blue("Found existing configuration in project directory")
|
|
1888
|
+
);
|
|
1889
|
+
console.log(
|
|
1890
|
+
chalk.gray(
|
|
1891
|
+
`Project excludeFiles: ${JSON.stringify(
|
|
1892
|
+
existingConfig.excludeFiles || []
|
|
1893
|
+
)}`
|
|
1894
|
+
)
|
|
1895
|
+
);
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
console.warn(chalk.yellow("Could not read project config"));
|
|
1898
|
+
existingConfig = null;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
// If still no existing config found
|
|
1902
|
+
if (!existingConfig) {
|
|
1903
|
+
console.log(
|
|
1904
|
+
chalk.blue("No existing configuration found, creating new one...")
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
// Merge or create configuration
|
|
1908
|
+
mergeConfigurationFiles(
|
|
1909
|
+
projectPath,
|
|
1910
|
+
answer,
|
|
1911
|
+
latestVersionOfCreatePrismaPhpApp,
|
|
1912
|
+
existingConfig
|
|
427
1913
|
);
|
|
428
1914
|
if (updateAnswer?.isUpdate) {
|
|
429
1915
|
execSync(
|
|
@@ -445,7 +1931,7 @@ async function main() {
|
|
|
445
1931
|
`${chalk.green(
|
|
446
1932
|
"Success!"
|
|
447
1933
|
)} Prisma PHP project successfully created in ${chalk.green(
|
|
448
|
-
|
|
1934
|
+
projectPath.replace(/\\/g, "/")
|
|
449
1935
|
)}!`
|
|
450
1936
|
);
|
|
451
1937
|
console.log("\n=========================");
|