create-prisma-php-app 3.3.2 → 3.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +826 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,715 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{execSync,spawnSync}from"child_process";import fs from"fs";import{fileURLToPath}from"url";import path from"path";import chalk from"chalk";import prompts from"prompts";import https from"https";import{randomBytes}from"crypto";const __filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename);let updateAnswer=null;const nonBackendFiles=["favicon.ico","\\src\\app\\index.php","metadata.php","not-found.php","error.php"],dockerFiles=[".dockerignore","docker-compose.yml","Dockerfile","apache.conf"];function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+"\\htdocs\\".length).replace(/\\/g,"\\\\"),n=e.replace(new RegExp(`^${t}`),"").replace(/\\/g,"/");let c=`http://localhost/${n}`;c=c.endsWith("/")?c.slice(0,-1):c;const o=c.replace(/(?<!:)(\/\/+)/g,"/"),i=n.replace(/\/\/+/g,"/");return{bsTarget:`${o}/`,bsPathRewrite:{"^/":`/${i.startsWith("/")?i.substring(1):i}/`}}}async function updatePackageJson(e,s){const t=path.join(e,"package.json");if(checkExcludeFiles(t))return;const n=JSON.parse(fs.readFileSync(t,"utf8"));n.scripts={...n.scripts,projectName:"tsx settings/project-name.ts"};let c=[];if(s.tailwindcss&&(n.scripts={...n.scripts,tailwind:"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch","tailwind:build":"postcss src/app/css/tailwind.css -o src/app/css/styles.css"},c.push("tailwind")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.docker&&(n.scripts={...n.scripts,docker:"docker-compose up"},c.push("docker")),s.swaggerDocs){const e=s.prisma?"tsx settings/auto-swagger-docs.ts":"tsx settings/swagger-config.ts";n.scripts={...n.scripts,"create-swagger-docs":e}}let o={...n.scripts};o.browserSync="tsx settings/bs-config.ts",o["browserSync:build"]="tsx settings/build.ts",o.dev=`npm-run-all projectName -p browserSync ${c.join(" ")}`,o.build=`npm-run-all${s.tailwindcss?" tailwind:build":""} browserSync:build`,n.scripts=o,n.type="module",fs.writeFileSync(t,JSON.stringify(n,null,2))}async function updateComposerJson(e){checkExcludeFiles(path.join(e,"composer.json"))}async function updateIndexJsForWebSocket(e,s){if(!s.websocket)return;const t=path.join(e,"src","app","js","index.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\n// WebSocket initialization\nvar ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(t.backendOnly&&n.includes("src\\app\\js")||t.backendOnly&&n.includes("src\\app\\css")||t.backendOnly&&n.includes("src\\app\\assets"))return;if(!t.swaggerDocs&&n.includes("src\\app\\swagger-docs"))return;const c=s.replace(/\\/g,"/");if(updateAnswer?.excludeFilePath?.includes(c))return;fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),fs.readdirSync(e).forEach((n=>{copyRecursiveSync(path.join(e,n),path.join(s,n),t)}))}else{if(checkExcludeFiles(s))return;if(!t.tailwindcss&&(s.includes("tailwind.css")||s.includes("styles.css")))return;if(!t.websocket&&(s.includes("restart-websocket.ts")||s.includes("restart-websocket.bat")))return;if(!t.docker&&dockerFiles.some((e=>s.includes(e))))return;if(t.backendOnly&&nonBackendFiles.some((e=>s.includes(e))))return;if(!t.backendOnly&&s.includes("route.php"))return;if(t.backendOnly&&!t.swaggerDocs&&s.includes("layout.php"))return;if(!t.swaggerDocs&&s.includes("swagger-config.ts"))return;if(t.tailwindcss&&s.includes("index.css"))return;if((!t.swaggerDocs||!t.prisma)&&(s.includes("auto-swagger-docs.ts")||s.includes("prisma-schema-config.json")))return;fs.copyFileSync(e,s,0)}}async function executeCopy(e,s,t){s.forEach((({src:s,dest:n})=>{copyRecursiveSync(path.join(__dirname,s),path.join(e,n),t)}))}function modifyPostcssConfig(e){const s=path.join(e,"postcss.config.js");if(checkExcludeFiles(s))return;fs.writeFileSync(s,'export default {\n plugins: {\n "@tailwindcss/postcss": {},\n cssnano: {},\n },\n};',{flag:"w"})}function modifyLayoutPHP(e,s){const t=path.join(e,"src","app","layout.php");if(!checkExcludeFiles(t))try{let e=fs.readFileSync(t,"utf8"),n="";s.backendOnly||(s.tailwindcss||(n='\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />'),n+='\n <script src="<?= Request::baseUrl; ?>/js/morphdom-umd.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/json5.min.js"><\/script>\n <script src="<?= Request::baseUrl; ?>/js/index.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${n}`:n),e=e.replace("</head>",`${c}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){return!!updateAnswer?.isUpdate&&(updateAnswer?.excludeFilePath?.includes(e.replace(/\\/g,"/"))??!1)}async function createDirectoryStructure(e,s){const t=[{src:"/bootstrap.php",dest:"/bootstrap.php"},{src:"/.htaccess",dest:"/.htaccess"},{src:"/tsconfig.json",dest:"/tsconfig.json"},{src:"/app-gitignore",dest:"/.gitignore"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"}];s.docker&&n.push({src:"/.dockerignore",dest:"/.dockerignore"},{src:"/docker-compose.yml",dest:"/docker-compose.yml"},{src:"/Dockerfile",dest:"/Dockerfile"},{src:"/apache.conf",dest:"/apache.conf"}),t.forEach((({src:s,dest:t})=>{const n=path.join(__dirname,s),c=path.join(e,t);if(checkExcludeFiles(c))return;const o=fs.readFileSync(n,"utf8");fs.writeFileSync(c,o,{flag:"w"})})),await executeCopy(e,n,s),await updatePackageJson(e,s),await updateComposerJson(e),s.backendOnly||await updateIndexJsForWebSocket(e,s),s.tailwindcss&&modifyPostcssConfig(e),(s.tailwindcss||!s.backendOnly||s.swaggerDocs)&&modifyLayoutPHP(e,s);const c=generateAuthSecret(),o=generateHexEncodedKey(),i=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${c}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# PHPMailer SMTP configuration (uncomment and set as needed)\n# SMTP_HOST="smtp.gmail.com" # Your SMTP host\n# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username\n# SMTP_PASSWORD="123456" # Your SMTP password\n# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port\n# SMTP_ENCRYPTION="ssl" # ssl or tls\n# MAIL_FROM="john.doe@gmail.com" # Sender email address\n# MAIL_FROM_NAME="John Doe" # Sender name\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${o}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"`;if(s.prisma){const s=`${'# Environment variables declared in this file are automatically made available to Prisma.\n# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema\n\n# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.\n# See the documentation for all the connection string options: https://pris.ly/d/connection-strings\n\nDATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"'}\n\n${i}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,i)}async function getAnswer(e={}){const s=[];e.projectName||s.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||s.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const t=()=>{process.exit(0)},n=await prompts(s,{onCancel:t}),c=[];n.backendOnly??e.backendOnly??!1?(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!0,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!0,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||c.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||c.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||c.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||c.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,initial:!1,active:"Yes",inactive:"No"}),e.docker||c.push({type:"toggle",name:"docker",message:`Would you like to use ${chalk.blue("Docker")}?`,initial:!1,active:"Yes",inactive:"No"}));const o=await prompts(c,{onCancel:t});return{projectName:n.projectName?String(n.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:n.backendOnly??e.backendOnly??!1,swaggerDocs:o.swaggerDocs??e.swaggerDocs??!1,tailwindcss:o.tailwindcss??e.tailwindcss??!1,websocket:o.websocket??e.websocket??!1,prisma:o.prisma??e.prisma??!1,docker:o.docker??e.docker??!1}}async function uninstallNpmDependencies(e,s,t=!1){s.forEach((e=>{}));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){s.forEach((e=>{}));const t=`C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${s.join(" ")}`;execSync(t,{stdio:"inherit",cwd:e})}function fetchPackageVersion(e){return new Promise(((s,t)=>{https.get(`https://registry.npmjs.org/${e}`,(e=>{let n="";e.on("data",(e=>n+=e)),e.on("end",(()=>{try{const e=JSON.parse(n);s(e["dist-tags"].latest)}catch(e){t(new Error("Failed to parse JSON response"))}}))})).on("error",(e=>t(e)))}))}const readJsonFile=e=>{const s=fs.readFileSync(e,"utf8");return JSON.parse(s)};function compareVersions(e,s){const t=e.split(".").map(Number),n=s.split(".").map(Number);for(let e=0;e<t.length;e++){if(t[e]>n[e])return 1;if(t[e]<n[e])return-1}return 0}function getInstalledPackageVersion(e){try{const s=execSync(`npm list -g ${e} --depth=0`).toString().match(new RegExp(`${e}@(\\d+\\.\\d+\\.\\d+)`));return s?s[1]:null}catch(e){return null}}
|
|
2
|
+
import { execSync, spawnSync } from "child_process";
|
|
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
|
+
function bsConfigUrls(projectRootPath) {
|
|
27
|
+
// Identify the base path dynamically up to and including 'htdocs'
|
|
28
|
+
const htdocsIndex = projectRootPath.indexOf("\\htdocs\\");
|
|
29
|
+
if (htdocsIndex === -1) {
|
|
30
|
+
console.error(
|
|
31
|
+
"Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"
|
|
32
|
+
);
|
|
33
|
+
return {
|
|
34
|
+
bsTarget: "",
|
|
35
|
+
bsPathRewrite: {},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Extract the path up to and including 'htdocs\\'
|
|
39
|
+
const basePathToRemove = projectRootPath.substring(
|
|
40
|
+
0,
|
|
41
|
+
htdocsIndex + "\\htdocs\\".length
|
|
42
|
+
);
|
|
43
|
+
// Escape backslashes for the regex pattern
|
|
44
|
+
const escapedBasePathToRemove = basePathToRemove.replace(/\\/g, "\\\\");
|
|
45
|
+
// Remove the base path and replace backslashes with forward slashes for URL compatibility
|
|
46
|
+
const relativeWebPath = projectRootPath
|
|
47
|
+
.replace(new RegExp(`^${escapedBasePathToRemove}`), "")
|
|
48
|
+
.replace(/\\/g, "/");
|
|
49
|
+
// Construct the Browser Sync command with the correct proxy URL, being careful not to affect the protocol part
|
|
50
|
+
let proxyUrl = `http://localhost/${relativeWebPath}`;
|
|
51
|
+
// Ensure the proxy URL does not end with a slash before appending '/public'
|
|
52
|
+
proxyUrl = proxyUrl.endsWith("/") ? proxyUrl.slice(0, -1) : proxyUrl;
|
|
53
|
+
// Clean the URL by replacing "//" with "/" but not affecting "http://"
|
|
54
|
+
// We replace instances of "//" that are not preceded by ":"
|
|
55
|
+
const cleanUrl = proxyUrl.replace(/(?<!:)(\/\/+)/g, "/");
|
|
56
|
+
const cleanRelativeWebPath = relativeWebPath.replace(/\/\/+/g, "/");
|
|
57
|
+
// Correct the relativeWebPath to ensure it does not start with a "/"
|
|
58
|
+
const adjustedRelativeWebPath = cleanRelativeWebPath.startsWith("/")
|
|
59
|
+
? cleanRelativeWebPath.substring(1)
|
|
60
|
+
: cleanRelativeWebPath;
|
|
61
|
+
return {
|
|
62
|
+
bsTarget: `${cleanUrl}/`,
|
|
63
|
+
bsPathRewrite: {
|
|
64
|
+
"^/": `/${adjustedRelativeWebPath}/`,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function updatePackageJson(baseDir, answer) {
|
|
69
|
+
const packageJsonPath = path.join(baseDir, "package.json");
|
|
70
|
+
if (checkExcludeFiles(packageJsonPath)) return;
|
|
71
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
72
|
+
packageJson.scripts = {
|
|
73
|
+
...packageJson.scripts,
|
|
74
|
+
projectName: "tsx settings/project-name.ts",
|
|
75
|
+
};
|
|
76
|
+
let answersToInclude = [];
|
|
77
|
+
if (answer.tailwindcss) {
|
|
78
|
+
packageJson.scripts = {
|
|
79
|
+
...packageJson.scripts,
|
|
80
|
+
tailwind:
|
|
81
|
+
"postcss src/app/css/tailwind.css -o src/app/css/styles.css --watch",
|
|
82
|
+
"tailwind:build":
|
|
83
|
+
"postcss src/app/css/tailwind.css -o src/app/css/styles.css",
|
|
84
|
+
};
|
|
85
|
+
answersToInclude.push("tailwind");
|
|
86
|
+
}
|
|
87
|
+
if (answer.websocket) {
|
|
88
|
+
packageJson.scripts = {
|
|
89
|
+
...packageJson.scripts,
|
|
90
|
+
websocket: "tsx settings/restart-websocket.ts",
|
|
91
|
+
};
|
|
92
|
+
answersToInclude.push("websocket");
|
|
93
|
+
}
|
|
94
|
+
if (answer.docker) {
|
|
95
|
+
packageJson.scripts = {
|
|
96
|
+
...packageJson.scripts,
|
|
97
|
+
docker: "docker-compose up",
|
|
98
|
+
};
|
|
99
|
+
answersToInclude.push("docker");
|
|
100
|
+
}
|
|
101
|
+
if (answer.swaggerDocs) {
|
|
102
|
+
const swaggerDocsExecuteScript = answer.prisma
|
|
103
|
+
? "tsx settings/auto-swagger-docs.ts"
|
|
104
|
+
: "tsx settings/swagger-config.ts";
|
|
105
|
+
packageJson.scripts = {
|
|
106
|
+
...packageJson.scripts,
|
|
107
|
+
"create-swagger-docs": swaggerDocsExecuteScript,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Initialize with existing scripts
|
|
111
|
+
let updatedScripts = {
|
|
112
|
+
...packageJson.scripts,
|
|
113
|
+
};
|
|
114
|
+
updatedScripts.browserSync = "tsx settings/bs-config.ts";
|
|
115
|
+
updatedScripts["browserSync:build"] = "tsx settings/build.ts";
|
|
116
|
+
updatedScripts.dev = `npm-run-all projectName -p browserSync ${answersToInclude.join(
|
|
117
|
+
" "
|
|
118
|
+
)}`;
|
|
119
|
+
updatedScripts.build = `npm-run-all${
|
|
120
|
+
answer.tailwindcss ? " tailwind:build" : ""
|
|
121
|
+
} browserSync:build`;
|
|
122
|
+
// Finally, assign the updated scripts back to packageJson
|
|
123
|
+
packageJson.scripts = updatedScripts;
|
|
124
|
+
packageJson.type = "module";
|
|
125
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
126
|
+
}
|
|
127
|
+
async function updateComposerJson(baseDir) {
|
|
128
|
+
const composerJsonPath = path.join(baseDir, "composer.json");
|
|
129
|
+
if (checkExcludeFiles(composerJsonPath)) return;
|
|
130
|
+
}
|
|
131
|
+
async function updateIndexJsForWebSocket(baseDir, answer) {
|
|
132
|
+
if (!answer.websocket) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const indexPath = path.join(baseDir, "src", "app", "js", "index.js");
|
|
136
|
+
if (checkExcludeFiles(indexPath)) return;
|
|
137
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
138
|
+
// WebSocket initialization code to be appended
|
|
139
|
+
const webSocketCode = `
|
|
140
|
+
// WebSocket initialization
|
|
141
|
+
var ws = new WebSocket("ws://localhost:8080");
|
|
142
|
+
`;
|
|
143
|
+
// Append WebSocket code if user chose to use WebSocket
|
|
144
|
+
indexContent += webSocketCode;
|
|
145
|
+
fs.writeFileSync(indexPath, indexContent, "utf8");
|
|
146
|
+
console.log("WebSocket code added to index.js successfully.");
|
|
147
|
+
}
|
|
148
|
+
function generateAuthSecret() {
|
|
149
|
+
// Generate 33 random bytes and encode them as a base64 string
|
|
150
|
+
return randomBytes(33).toString("base64");
|
|
151
|
+
}
|
|
152
|
+
function generateHexEncodedKey(size = 16) {
|
|
153
|
+
return randomBytes(size).toString("hex"); // Hex encoding ensures safe session keys
|
|
154
|
+
}
|
|
155
|
+
// Recursive copy function
|
|
156
|
+
function copyRecursiveSync(src, dest, answer) {
|
|
157
|
+
const exists = fs.existsSync(src);
|
|
158
|
+
const stats = exists && fs.statSync(src);
|
|
159
|
+
const isDirectory = exists && stats && stats.isDirectory();
|
|
160
|
+
if (isDirectory) {
|
|
161
|
+
const destLower = dest.toLowerCase();
|
|
162
|
+
if (!answer.websocket && destLower.includes("src\\lib\\websocket")) return;
|
|
163
|
+
if (
|
|
164
|
+
(answer.backendOnly && destLower.includes("src\\app\\js")) ||
|
|
165
|
+
(answer.backendOnly && destLower.includes("src\\app\\css")) ||
|
|
166
|
+
(answer.backendOnly && destLower.includes("src\\app\\assets"))
|
|
167
|
+
)
|
|
168
|
+
return;
|
|
169
|
+
if (!answer.swaggerDocs && destLower.includes("src\\app\\swagger-docs"))
|
|
170
|
+
return;
|
|
171
|
+
const destModified = dest.replace(/\\/g, "/");
|
|
172
|
+
if (updateAnswer?.excludeFilePath?.includes(destModified)) return;
|
|
173
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
174
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
175
|
+
copyRecursiveSync(
|
|
176
|
+
path.join(src, childItemName),
|
|
177
|
+
path.join(dest, childItemName),
|
|
178
|
+
answer
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
if (checkExcludeFiles(dest)) return;
|
|
183
|
+
if (
|
|
184
|
+
!answer.tailwindcss &&
|
|
185
|
+
(dest.includes("tailwind.css") || dest.includes("styles.css"))
|
|
186
|
+
)
|
|
187
|
+
return;
|
|
188
|
+
if (
|
|
189
|
+
!answer.websocket &&
|
|
190
|
+
(dest.includes("restart-websocket.ts") ||
|
|
191
|
+
dest.includes("restart-websocket.bat"))
|
|
192
|
+
)
|
|
193
|
+
return;
|
|
194
|
+
if (!answer.docker && dockerFiles.some((file) => dest.includes(file)))
|
|
195
|
+
return;
|
|
196
|
+
if (
|
|
197
|
+
answer.backendOnly &&
|
|
198
|
+
nonBackendFiles.some((file) => dest.includes(file))
|
|
199
|
+
)
|
|
200
|
+
return;
|
|
201
|
+
if (!answer.backendOnly && dest.includes("route.php")) return;
|
|
202
|
+
if (
|
|
203
|
+
answer.backendOnly &&
|
|
204
|
+
!answer.swaggerDocs &&
|
|
205
|
+
dest.includes("layout.php")
|
|
206
|
+
)
|
|
207
|
+
return;
|
|
208
|
+
if (!answer.swaggerDocs && dest.includes("swagger-config.ts")) return;
|
|
209
|
+
if (answer.tailwindcss && dest.includes("index.css")) return;
|
|
210
|
+
if (
|
|
211
|
+
(!answer.swaggerDocs || !answer.prisma) &&
|
|
212
|
+
(dest.includes("auto-swagger-docs.ts") ||
|
|
213
|
+
dest.includes("prisma-schema-config.json"))
|
|
214
|
+
)
|
|
215
|
+
return;
|
|
216
|
+
fs.copyFileSync(src, dest, 0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Function to execute the recursive copy for entire directories
|
|
220
|
+
async function executeCopy(baseDir, directoriesToCopy, answer) {
|
|
221
|
+
directoriesToCopy.forEach(({ src: srcDir, dest: destDir }) => {
|
|
222
|
+
const sourcePath = path.join(__dirname, srcDir);
|
|
223
|
+
const destPath = path.join(baseDir, destDir);
|
|
224
|
+
copyRecursiveSync(sourcePath, destPath, answer);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function modifyPostcssConfig(baseDir) {
|
|
228
|
+
const filePath = path.join(baseDir, "postcss.config.js");
|
|
229
|
+
if (checkExcludeFiles(filePath)) return;
|
|
230
|
+
const newContent = `export default {
|
|
231
|
+
plugins: {
|
|
232
|
+
"@tailwindcss/postcss": {},
|
|
233
|
+
cssnano: {},
|
|
234
|
+
},
|
|
235
|
+
};`;
|
|
236
|
+
fs.writeFileSync(filePath, newContent, { flag: "w" });
|
|
237
|
+
console.log(chalk.green("postcss.config.js updated successfully."));
|
|
238
|
+
}
|
|
239
|
+
function modifyLayoutPHP(baseDir, answer) {
|
|
240
|
+
const layoutPath = path.join(baseDir, "src", "app", "layout.php");
|
|
241
|
+
if (checkExcludeFiles(layoutPath)) return;
|
|
242
|
+
try {
|
|
243
|
+
let indexContent = fs.readFileSync(layoutPath, "utf8");
|
|
244
|
+
let stylesAndLinks = "";
|
|
245
|
+
if (!answer.backendOnly) {
|
|
246
|
+
if (!answer.tailwindcss) {
|
|
247
|
+
stylesAndLinks = `\n <link href="<?= Request::baseUrl; ?>/css/index.css" rel="stylesheet" />`;
|
|
248
|
+
}
|
|
249
|
+
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>`;
|
|
250
|
+
}
|
|
251
|
+
// Tailwind CSS link or CDN script
|
|
252
|
+
let tailwindLink = "";
|
|
253
|
+
if (!answer.backendOnly) {
|
|
254
|
+
tailwindLink = answer.tailwindcss
|
|
255
|
+
? ` <link href="<?= Request::baseUrl; ?>/css/styles.css" rel="stylesheet" /> ${stylesAndLinks}`
|
|
256
|
+
: stylesAndLinks;
|
|
257
|
+
}
|
|
258
|
+
// Insert before the closing </head> tag
|
|
259
|
+
indexContent = indexContent.replace(
|
|
260
|
+
"</head>",
|
|
261
|
+
`${tailwindLink}
|
|
262
|
+
</head>`
|
|
263
|
+
);
|
|
264
|
+
fs.writeFileSync(layoutPath, indexContent, { flag: "w" });
|
|
265
|
+
console.log(
|
|
266
|
+
chalk.green(
|
|
267
|
+
`layout.php modified successfully for ${
|
|
268
|
+
answer.tailwindcss ? "local Tailwind CSS" : "Tailwind CSS CDN"
|
|
269
|
+
}.`
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error(chalk.red("Error modifying layout.php:"), error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// This function updates or creates the .env file
|
|
277
|
+
async function createOrUpdateEnvFile(baseDir, content) {
|
|
278
|
+
const envPath = path.join(baseDir, ".env");
|
|
279
|
+
if (checkExcludeFiles(envPath)) return;
|
|
280
|
+
console.log("🚀 ~ content:", content);
|
|
281
|
+
fs.writeFileSync(envPath, content, { flag: "w" });
|
|
282
|
+
}
|
|
283
|
+
function checkExcludeFiles(destPath) {
|
|
284
|
+
if (!updateAnswer?.isUpdate) return false;
|
|
285
|
+
return (
|
|
286
|
+
updateAnswer?.excludeFilePath?.includes(destPath.replace(/\\/g, "/")) ??
|
|
287
|
+
false
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
async function createDirectoryStructure(baseDir, answer) {
|
|
291
|
+
console.log("🚀 ~ baseDir:", baseDir);
|
|
292
|
+
console.log("🚀 ~ answer:", answer);
|
|
293
|
+
const filesToCopy = [
|
|
294
|
+
{ src: "/bootstrap.php", dest: "/bootstrap.php" },
|
|
295
|
+
{ src: "/.htaccess", dest: "/.htaccess" },
|
|
296
|
+
{ src: "/tsconfig.json", dest: "/tsconfig.json" },
|
|
297
|
+
{ src: "/app-gitignore", dest: "/.gitignore" },
|
|
298
|
+
];
|
|
299
|
+
if (answer.tailwindcss) {
|
|
300
|
+
filesToCopy.push({ src: "/postcss.config.js", dest: "/postcss.config.js" });
|
|
301
|
+
}
|
|
302
|
+
const directoriesToCopy = [
|
|
303
|
+
{
|
|
304
|
+
src: "/settings",
|
|
305
|
+
dest: "/settings",
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
src: "/src",
|
|
309
|
+
dest: "/src",
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
if (answer.docker) {
|
|
313
|
+
directoriesToCopy.push(
|
|
314
|
+
{ src: "/.dockerignore", dest: "/.dockerignore" },
|
|
315
|
+
{ src: "/docker-compose.yml", dest: "/docker-compose.yml" },
|
|
316
|
+
{ src: "/Dockerfile", dest: "/Dockerfile" },
|
|
317
|
+
{ src: "/apache.conf", dest: "/apache.conf" }
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
console.log("🚀 ~ directoriesToCopy:", directoriesToCopy);
|
|
321
|
+
filesToCopy.forEach(({ src, dest }) => {
|
|
322
|
+
const sourcePath = path.join(__dirname, src);
|
|
323
|
+
const destPath = path.join(baseDir, dest);
|
|
324
|
+
if (checkExcludeFiles(destPath)) return;
|
|
325
|
+
const code = fs.readFileSync(sourcePath, "utf8");
|
|
326
|
+
fs.writeFileSync(destPath, code, { flag: "w" });
|
|
327
|
+
});
|
|
328
|
+
await executeCopy(baseDir, directoriesToCopy, answer);
|
|
329
|
+
await updatePackageJson(baseDir, answer);
|
|
330
|
+
await updateComposerJson(baseDir);
|
|
331
|
+
if (!answer.backendOnly) {
|
|
332
|
+
await updateIndexJsForWebSocket(baseDir, answer);
|
|
333
|
+
}
|
|
334
|
+
if (answer.tailwindcss) {
|
|
335
|
+
modifyPostcssConfig(baseDir);
|
|
336
|
+
}
|
|
337
|
+
if (answer.tailwindcss || !answer.backendOnly || answer.swaggerDocs) {
|
|
338
|
+
modifyLayoutPHP(baseDir, answer);
|
|
339
|
+
}
|
|
340
|
+
const authSecret = generateAuthSecret();
|
|
341
|
+
const localStoreKey = generateHexEncodedKey();
|
|
342
|
+
const authCookieName = generateHexEncodedKey(8);
|
|
343
|
+
const functionCallSecret = generateHexEncodedKey(32);
|
|
344
|
+
const prismaPHPEnvContent = `# Authentication secret key for JWT or session encryption.
|
|
345
|
+
AUTH_SECRET="${authSecret}"
|
|
346
|
+
# Name of the authentication cookie.
|
|
347
|
+
AUTH_COOKIE_NAME="${authCookieName}"
|
|
348
|
+
|
|
349
|
+
# PHPMailer SMTP configuration (uncomment and set as needed)
|
|
350
|
+
# SMTP_HOST="smtp.gmail.com" # Your SMTP host
|
|
351
|
+
# SMTP_USERNAME="john.doe@gmail.com" # Your SMTP username
|
|
352
|
+
# SMTP_PASSWORD="123456" # Your SMTP password
|
|
353
|
+
# SMTP_PORT="587" # 587 for TLS, 465 for SSL, or your SMTP port
|
|
354
|
+
# SMTP_ENCRYPTION="ssl" # ssl or tls
|
|
355
|
+
# MAIL_FROM="john.doe@gmail.com" # Sender email address
|
|
356
|
+
# MAIL_FROM_NAME="John Doe" # Sender name
|
|
357
|
+
|
|
358
|
+
# Show errors in the browser (development only). Set to false in production.
|
|
359
|
+
SHOW_ERRORS="true"
|
|
360
|
+
|
|
361
|
+
# Application timezone (default: UTC)
|
|
362
|
+
APP_TIMEZONE="UTC"
|
|
363
|
+
|
|
364
|
+
# Application environment (development or production)
|
|
365
|
+
APP_ENV="development"
|
|
366
|
+
|
|
367
|
+
# Enable or disable application cache (default: false)
|
|
368
|
+
CACHE_ENABLED="false"
|
|
369
|
+
# Cache time-to-live in seconds (default: 600)
|
|
370
|
+
CACHE_TTL="600"
|
|
371
|
+
|
|
372
|
+
# Local storage key for browser storage (auto-generated if not set).
|
|
373
|
+
# Spaces will be replaced with underscores and converted to lowercase.
|
|
374
|
+
LOCALSTORE_KEY="${localStoreKey}"
|
|
375
|
+
|
|
376
|
+
# Secret key for encrypting function calls.
|
|
377
|
+
FUNCTION_CALL_SECRET="${functionCallSecret}"`;
|
|
378
|
+
if (answer.prisma) {
|
|
379
|
+
const prismaEnvContent = `# Environment variables declared in this file are automatically made available to Prisma.
|
|
380
|
+
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
|
381
|
+
|
|
382
|
+
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
|
383
|
+
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
|
384
|
+
|
|
385
|
+
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"`;
|
|
386
|
+
const envContent = `${prismaEnvContent}\n\n${prismaPHPEnvContent}`;
|
|
387
|
+
await createOrUpdateEnvFile(baseDir, envContent);
|
|
388
|
+
} else {
|
|
389
|
+
await createOrUpdateEnvFile(baseDir, prismaPHPEnvContent);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function getAnswer(predefinedAnswers = {}) {
|
|
393
|
+
console.log("🚀 ~ predefinedAnswers:", predefinedAnswers);
|
|
394
|
+
const questionsArray = [];
|
|
395
|
+
// Ask for project name if not provided
|
|
396
|
+
if (!predefinedAnswers.projectName) {
|
|
397
|
+
questionsArray.push({
|
|
398
|
+
type: "text",
|
|
399
|
+
name: "projectName",
|
|
400
|
+
message: "What is your project named?",
|
|
401
|
+
initial: "my-app",
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
// IMPORTANT: Only ask backendOnly for NEW projects (not updates)
|
|
405
|
+
if (!predefinedAnswers.backendOnly && !updateAnswer?.isUpdate) {
|
|
406
|
+
questionsArray.push({
|
|
407
|
+
type: "toggle",
|
|
408
|
+
name: "backendOnly",
|
|
409
|
+
message: `Would you like to create a ${chalk.blue(
|
|
410
|
+
"backend-only project"
|
|
411
|
+
)}?`,
|
|
412
|
+
initial: false,
|
|
413
|
+
active: "Yes",
|
|
414
|
+
inactive: "No",
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const onCancel = () => {
|
|
418
|
+
console.log(chalk.red("Operation cancelled by the user."));
|
|
419
|
+
process.exit(0);
|
|
420
|
+
};
|
|
421
|
+
const initialResponse = await prompts(questionsArray, { onCancel });
|
|
422
|
+
console.log("🚀 ~ initialResponse:", initialResponse);
|
|
423
|
+
const nonBackendOnlyQuestionsArray = [];
|
|
424
|
+
const isBackendOnly =
|
|
425
|
+
initialResponse.backendOnly ?? predefinedAnswers.backendOnly ?? false;
|
|
426
|
+
if (isBackendOnly) {
|
|
427
|
+
// For backend-only project (skip Tailwind), but still ask other features
|
|
428
|
+
// During updates, ALWAYS ask these questions regardless of predefinedAnswers
|
|
429
|
+
if (!predefinedAnswers.swaggerDocs && !updateAnswer?.isUpdate) {
|
|
430
|
+
nonBackendOnlyQuestionsArray.push({
|
|
431
|
+
type: "toggle",
|
|
432
|
+
name: "swaggerDocs",
|
|
433
|
+
message: `Would you like to use ${chalk.blue("Swagger Docs")}?`,
|
|
434
|
+
initial: false,
|
|
435
|
+
active: "Yes",
|
|
436
|
+
inactive: "No",
|
|
437
|
+
});
|
|
438
|
+
} else if (updateAnswer?.isUpdate) {
|
|
439
|
+
// Always ask during updates
|
|
440
|
+
nonBackendOnlyQuestionsArray.push({
|
|
441
|
+
type: "toggle",
|
|
442
|
+
name: "swaggerDocs",
|
|
443
|
+
message: `Would you like to use ${chalk.blue("Swagger Docs")}?`,
|
|
444
|
+
initial: predefinedAnswers.swaggerDocs ?? false,
|
|
445
|
+
active: "Yes",
|
|
446
|
+
inactive: "No",
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (!predefinedAnswers.websocket && !updateAnswer?.isUpdate) {
|
|
450
|
+
nonBackendOnlyQuestionsArray.push({
|
|
451
|
+
type: "toggle",
|
|
452
|
+
name: "websocket",
|
|
453
|
+
message: `Would you like to use ${chalk.blue("Websocket")}?`,
|
|
454
|
+
initial: true,
|
|
455
|
+
active: "Yes",
|
|
456
|
+
inactive: "No",
|
|
457
|
+
});
|
|
458
|
+
} else if (updateAnswer?.isUpdate) {
|
|
459
|
+
// Always ask during updates
|
|
460
|
+
nonBackendOnlyQuestionsArray.push({
|
|
461
|
+
type: "toggle",
|
|
462
|
+
name: "websocket",
|
|
463
|
+
message: `Would you like to use ${chalk.blue("Websocket")}?`,
|
|
464
|
+
initial: predefinedAnswers.websocket ?? true,
|
|
465
|
+
active: "Yes",
|
|
466
|
+
inactive: "No",
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
if (!predefinedAnswers.prisma && !updateAnswer?.isUpdate) {
|
|
470
|
+
nonBackendOnlyQuestionsArray.push({
|
|
471
|
+
type: "toggle",
|
|
472
|
+
name: "prisma",
|
|
473
|
+
message: `Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,
|
|
474
|
+
initial: true,
|
|
475
|
+
active: "Yes",
|
|
476
|
+
inactive: "No",
|
|
477
|
+
});
|
|
478
|
+
} else if (updateAnswer?.isUpdate) {
|
|
479
|
+
// Always ask during updates
|
|
480
|
+
nonBackendOnlyQuestionsArray.push({
|
|
481
|
+
type: "toggle",
|
|
482
|
+
name: "prisma",
|
|
483
|
+
message: `Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,
|
|
484
|
+
initial: predefinedAnswers.prisma ?? true,
|
|
485
|
+
active: "Yes",
|
|
486
|
+
inactive: "No",
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (!predefinedAnswers.docker && !updateAnswer?.isUpdate) {
|
|
490
|
+
nonBackendOnlyQuestionsArray.push({
|
|
491
|
+
type: "toggle",
|
|
492
|
+
name: "docker",
|
|
493
|
+
message: `Would you like to use ${chalk.blue("Docker")}?`,
|
|
494
|
+
initial: false,
|
|
495
|
+
active: "Yes",
|
|
496
|
+
inactive: "No",
|
|
497
|
+
});
|
|
498
|
+
} else if (updateAnswer?.isUpdate) {
|
|
499
|
+
// Always ask during updates
|
|
500
|
+
nonBackendOnlyQuestionsArray.push({
|
|
501
|
+
type: "toggle",
|
|
502
|
+
name: "docker",
|
|
503
|
+
message: `Would you like to use ${chalk.blue("Docker")}?`,
|
|
504
|
+
initial: predefinedAnswers.docker ?? false,
|
|
505
|
+
active: "Yes",
|
|
506
|
+
inactive: "No",
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
// For full-stack project, include Tailwind
|
|
511
|
+
if (!predefinedAnswers.swaggerDocs && !updateAnswer?.isUpdate) {
|
|
512
|
+
nonBackendOnlyQuestionsArray.push({
|
|
513
|
+
type: "toggle",
|
|
514
|
+
name: "swaggerDocs",
|
|
515
|
+
message: `Would you like to use ${chalk.blue("Swagger Docs")}?`,
|
|
516
|
+
initial: false,
|
|
517
|
+
active: "Yes",
|
|
518
|
+
inactive: "No",
|
|
519
|
+
});
|
|
520
|
+
} else if (updateAnswer?.isUpdate) {
|
|
521
|
+
// Always ask during updates
|
|
522
|
+
nonBackendOnlyQuestionsArray.push({
|
|
523
|
+
type: "toggle",
|
|
524
|
+
name: "swaggerDocs",
|
|
525
|
+
message: `Would you like to use ${chalk.blue("Swagger Docs")}?`,
|
|
526
|
+
initial: predefinedAnswers.swaggerDocs ?? false,
|
|
527
|
+
active: "Yes",
|
|
528
|
+
inactive: "No",
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if (!predefinedAnswers.tailwindcss && !updateAnswer?.isUpdate) {
|
|
532
|
+
nonBackendOnlyQuestionsArray.push({
|
|
533
|
+
type: "toggle",
|
|
534
|
+
name: "tailwindcss",
|
|
535
|
+
message: `Would you like to use ${chalk.blue("Tailwind CSS")}?`,
|
|
536
|
+
initial: false,
|
|
537
|
+
active: "Yes",
|
|
538
|
+
inactive: "No",
|
|
539
|
+
});
|
|
540
|
+
} else if (updateAnswer?.isUpdate) {
|
|
541
|
+
// Always ask during updates
|
|
542
|
+
nonBackendOnlyQuestionsArray.push({
|
|
543
|
+
type: "toggle",
|
|
544
|
+
name: "tailwindcss",
|
|
545
|
+
message: `Would you like to use ${chalk.blue("Tailwind CSS")}?`,
|
|
546
|
+
initial: predefinedAnswers.tailwindcss ?? false,
|
|
547
|
+
active: "Yes",
|
|
548
|
+
inactive: "No",
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (!predefinedAnswers.websocket && !updateAnswer?.isUpdate) {
|
|
552
|
+
nonBackendOnlyQuestionsArray.push({
|
|
553
|
+
type: "toggle",
|
|
554
|
+
name: "websocket",
|
|
555
|
+
message: `Would you like to use ${chalk.blue("Websocket")}?`,
|
|
556
|
+
initial: false,
|
|
557
|
+
active: "Yes",
|
|
558
|
+
inactive: "No",
|
|
559
|
+
});
|
|
560
|
+
} else if (updateAnswer?.isUpdate) {
|
|
561
|
+
// Always ask during updates
|
|
562
|
+
nonBackendOnlyQuestionsArray.push({
|
|
563
|
+
type: "toggle",
|
|
564
|
+
name: "websocket",
|
|
565
|
+
message: `Would you like to use ${chalk.blue("Websocket")}?`,
|
|
566
|
+
initial: predefinedAnswers.websocket ?? false,
|
|
567
|
+
active: "Yes",
|
|
568
|
+
inactive: "No",
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
if (!predefinedAnswers.prisma && !updateAnswer?.isUpdate) {
|
|
572
|
+
nonBackendOnlyQuestionsArray.push({
|
|
573
|
+
type: "toggle",
|
|
574
|
+
name: "prisma",
|
|
575
|
+
message: `Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,
|
|
576
|
+
initial: false,
|
|
577
|
+
active: "Yes",
|
|
578
|
+
inactive: "No",
|
|
579
|
+
});
|
|
580
|
+
} else if (updateAnswer?.isUpdate) {
|
|
581
|
+
// Always ask during updates
|
|
582
|
+
nonBackendOnlyQuestionsArray.push({
|
|
583
|
+
type: "toggle",
|
|
584
|
+
name: "prisma",
|
|
585
|
+
message: `Would you like to use ${chalk.blue("Prisma PHP ORM")}?`,
|
|
586
|
+
initial: predefinedAnswers.prisma ?? false,
|
|
587
|
+
active: "Yes",
|
|
588
|
+
inactive: "No",
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (!predefinedAnswers.docker && !updateAnswer?.isUpdate) {
|
|
592
|
+
nonBackendOnlyQuestionsArray.push({
|
|
593
|
+
type: "toggle",
|
|
594
|
+
name: "docker",
|
|
595
|
+
message: `Would you like to use ${chalk.blue("Docker")}?`,
|
|
596
|
+
initial: false,
|
|
597
|
+
active: "Yes",
|
|
598
|
+
inactive: "No",
|
|
599
|
+
});
|
|
600
|
+
} else if (updateAnswer?.isUpdate) {
|
|
601
|
+
// Always ask during updates
|
|
602
|
+
nonBackendOnlyQuestionsArray.push({
|
|
603
|
+
type: "toggle",
|
|
604
|
+
name: "docker",
|
|
605
|
+
message: `Would you like to use ${chalk.blue("Docker")}?`,
|
|
606
|
+
initial: predefinedAnswers.docker ?? false,
|
|
607
|
+
active: "Yes",
|
|
608
|
+
inactive: "No",
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const nonBackendOnlyResponse = await prompts(nonBackendOnlyQuestionsArray, {
|
|
613
|
+
onCancel,
|
|
614
|
+
});
|
|
615
|
+
console.log("🚀 ~ nonBackendOnlyResponse:", nonBackendOnlyResponse);
|
|
616
|
+
return {
|
|
617
|
+
projectName: initialResponse.projectName
|
|
618
|
+
? String(initialResponse.projectName).trim().replace(/ /g, "-")
|
|
619
|
+
: predefinedAnswers.projectName ?? "my-app",
|
|
620
|
+
backendOnly:
|
|
621
|
+
initialResponse.backendOnly ?? predefinedAnswers.backendOnly ?? false,
|
|
622
|
+
swaggerDocs:
|
|
623
|
+
nonBackendOnlyResponse.swaggerDocs ??
|
|
624
|
+
predefinedAnswers.swaggerDocs ??
|
|
625
|
+
false,
|
|
626
|
+
tailwindcss:
|
|
627
|
+
nonBackendOnlyResponse.tailwindcss ??
|
|
628
|
+
predefinedAnswers.tailwindcss ??
|
|
629
|
+
false,
|
|
630
|
+
websocket:
|
|
631
|
+
nonBackendOnlyResponse.websocket ?? predefinedAnswers.websocket ?? false,
|
|
632
|
+
prisma: nonBackendOnlyResponse.prisma ?? predefinedAnswers.prisma ?? false,
|
|
633
|
+
docker: nonBackendOnlyResponse.docker ?? predefinedAnswers.docker ?? false,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
async function uninstallNpmDependencies(baseDir, dependencies, isDev = false) {
|
|
637
|
+
console.log("Uninstalling dependencies:");
|
|
638
|
+
dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
|
|
639
|
+
// Prepare the npm uninstall command with the appropriate flag for dev dependencies
|
|
640
|
+
const npmUninstallCommand = `npm uninstall ${
|
|
641
|
+
isDev ? "--save-dev" : "--save"
|
|
642
|
+
} ${dependencies.join(" ")}`;
|
|
643
|
+
// Execute the npm uninstall command
|
|
644
|
+
execSync(npmUninstallCommand, {
|
|
645
|
+
stdio: "inherit",
|
|
646
|
+
cwd: baseDir,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
async function uninstallComposerDependencies(baseDir, dependencies) {
|
|
650
|
+
console.log("Uninstalling Composer dependencies:");
|
|
651
|
+
dependencies.forEach((dep) => console.log(`- ${chalk.blue(dep)}`));
|
|
652
|
+
// Prepare the composer remove command
|
|
653
|
+
const composerRemoveCommand = `C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar remove ${dependencies.join(
|
|
654
|
+
" "
|
|
655
|
+
)}`;
|
|
656
|
+
// Execute the composer remove command
|
|
657
|
+
execSync(composerRemoveCommand, {
|
|
658
|
+
stdio: "inherit",
|
|
659
|
+
cwd: baseDir,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function fetchPackageVersion(packageName) {
|
|
663
|
+
return new Promise((resolve, reject) => {
|
|
664
|
+
https
|
|
665
|
+
.get(`https://registry.npmjs.org/${packageName}`, (res) => {
|
|
666
|
+
let data = "";
|
|
667
|
+
res.on("data", (chunk) => (data += chunk));
|
|
668
|
+
res.on("end", () => {
|
|
669
|
+
try {
|
|
670
|
+
const parsed = JSON.parse(data);
|
|
671
|
+
resolve(parsed["dist-tags"].latest);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
reject(new Error("Failed to parse JSON response"));
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
})
|
|
677
|
+
.on("error", (err) => reject(err));
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const readJsonFile = (filePath) => {
|
|
681
|
+
const jsonData = fs.readFileSync(filePath, "utf8");
|
|
682
|
+
return JSON.parse(jsonData);
|
|
683
|
+
};
|
|
684
|
+
function compareVersions(installedVersion, currentVersion) {
|
|
685
|
+
const installedVersionArray = installedVersion.split(".").map(Number);
|
|
686
|
+
const currentVersionArray = currentVersion.split(".").map(Number);
|
|
687
|
+
for (let i = 0; i < installedVersionArray.length; i++) {
|
|
688
|
+
if (installedVersionArray[i] > currentVersionArray[i]) {
|
|
689
|
+
return 1;
|
|
690
|
+
} else if (installedVersionArray[i] < currentVersionArray[i]) {
|
|
691
|
+
return -1;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return 0;
|
|
695
|
+
}
|
|
696
|
+
function getInstalledPackageVersion(packageName) {
|
|
697
|
+
try {
|
|
698
|
+
const output = execSync(`npm list -g ${packageName} --depth=0`).toString();
|
|
699
|
+
const versionMatch = output.match(
|
|
700
|
+
new RegExp(`${packageName}@(\\d+\\.\\d+\\.\\d+)`)
|
|
701
|
+
);
|
|
702
|
+
if (versionMatch) {
|
|
703
|
+
return versionMatch[1];
|
|
704
|
+
} else {
|
|
705
|
+
console.error(`Package ${packageName} is not installed`);
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
3
713
|
/**
|
|
4
714
|
* Install dependencies in the specified directory.
|
|
5
715
|
* @param {string} baseDir - The base directory where to install the dependencies.
|
|
@@ -7,7 +717,11 @@ import{execSync,spawnSync}from"child_process";import fs from"fs";import{fileURLT
|
|
|
7
717
|
* @param {boolean} [isDev=false] - Whether to install the dependencies as devDependencies.
|
|
8
718
|
*/
|
|
9
719
|
async function installNpmDependencies(baseDir, dependencies, isDev = false) {
|
|
10
|
-
|
|
720
|
+
if (!fs.existsSync(path.join(baseDir, "package.json"))) {
|
|
721
|
+
console.log("Initializing new Node.js project...");
|
|
722
|
+
} else {
|
|
723
|
+
console.log("Updating existing Node.js project...");
|
|
724
|
+
}
|
|
11
725
|
// Initialize a package.json if it doesn't exist
|
|
12
726
|
if (!fs.existsSync(path.join(baseDir, "package.json"))) {
|
|
13
727
|
execSync("npm init -y", {
|
|
@@ -185,6 +899,7 @@ async function main() {
|
|
|
185
899
|
if (fs.existsSync(filePath))
|
|
186
900
|
excludeFiles.push(filePath.replace(/\\/g, "/"));
|
|
187
901
|
});
|
|
902
|
+
// Set updateAnswer with OLD settings initially (for checkExcludeFiles function)
|
|
188
903
|
updateAnswer = {
|
|
189
904
|
projectName,
|
|
190
905
|
backendOnly: localSettings.backendOnly,
|
|
@@ -212,6 +927,22 @@ async function main() {
|
|
|
212
927
|
docker: args.includes("--docker") || localSettings.docker,
|
|
213
928
|
};
|
|
214
929
|
answer = await getAnswer(predefinedAnswers);
|
|
930
|
+
// IMPORTANT: Update updateAnswer with the NEW answer after getting user input
|
|
931
|
+
if (answer !== null) {
|
|
932
|
+
updateAnswer = {
|
|
933
|
+
projectName,
|
|
934
|
+
backendOnly: answer.backendOnly,
|
|
935
|
+
swaggerDocs: answer.swaggerDocs,
|
|
936
|
+
tailwindcss: answer.tailwindcss,
|
|
937
|
+
websocket: answer.websocket,
|
|
938
|
+
prisma: answer.prisma,
|
|
939
|
+
docker: answer.docker,
|
|
940
|
+
isUpdate: true,
|
|
941
|
+
excludeFiles: localSettings.excludeFiles ?? [],
|
|
942
|
+
excludeFilePath: excludeFiles ?? [],
|
|
943
|
+
filePath: currentDir,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
215
946
|
} else {
|
|
216
947
|
// It's a new project - use CLI arguments
|
|
217
948
|
let useBackendOnly = args.includes("--backend-only");
|
|
@@ -357,24 +1088,57 @@ async function main() {
|
|
|
357
1088
|
if (updateAnswer?.isUpdate) {
|
|
358
1089
|
const updateUninstallNpmDependencies = [];
|
|
359
1090
|
const updateUninstallComposerDependencies = [];
|
|
1091
|
+
// Helper function to check if a composer package is installed
|
|
1092
|
+
const isComposerPackageInstalled = (packageName) => {
|
|
1093
|
+
try {
|
|
1094
|
+
const composerJsonPath = path.join(projectPath, "composer.json");
|
|
1095
|
+
if (fs.existsSync(composerJsonPath)) {
|
|
1096
|
+
const composerJson = JSON.parse(
|
|
1097
|
+
fs.readFileSync(composerJsonPath, "utf8")
|
|
1098
|
+
);
|
|
1099
|
+
return !!(
|
|
1100
|
+
composerJson.require && composerJson.require[packageName]
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
return false;
|
|
1104
|
+
} catch {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
// Helper function to check if an npm package is installed
|
|
1109
|
+
const isNpmPackageInstalled = (packageName) => {
|
|
1110
|
+
try {
|
|
1111
|
+
const packageJsonPath = path.join(projectPath, "package.json");
|
|
1112
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1113
|
+
const packageJson = JSON.parse(
|
|
1114
|
+
fs.readFileSync(packageJsonPath, "utf8")
|
|
1115
|
+
);
|
|
1116
|
+
return !!(
|
|
1117
|
+
(packageJson.dependencies &&
|
|
1118
|
+
packageJson.dependencies[packageName]) ||
|
|
1119
|
+
(packageJson.devDependencies &&
|
|
1120
|
+
packageJson.devDependencies[packageName])
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
return false;
|
|
1124
|
+
} catch {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
360
1128
|
if (updateAnswer.backendOnly) {
|
|
361
1129
|
nonBackendFiles.forEach((file) => {
|
|
362
1130
|
const filePath = path.join(projectPath, "src", "app", file);
|
|
363
1131
|
if (fs.existsSync(filePath)) {
|
|
364
|
-
fs.unlinkSync(filePath);
|
|
1132
|
+
fs.unlinkSync(filePath);
|
|
365
1133
|
console.log(`${file} was deleted successfully.`);
|
|
366
|
-
} else {
|
|
367
|
-
console.log(`${file} does not exist.`);
|
|
368
1134
|
}
|
|
369
1135
|
});
|
|
370
1136
|
const backendOnlyFolders = ["js", "css"];
|
|
371
1137
|
backendOnlyFolders.forEach((folder) => {
|
|
372
1138
|
const folderPath = path.join(projectPath, "src", "app", folder);
|
|
373
1139
|
if (fs.existsSync(folderPath)) {
|
|
374
|
-
fs.rmSync(folderPath, { recursive: true, force: true });
|
|
1140
|
+
fs.rmSync(folderPath, { recursive: true, force: true });
|
|
375
1141
|
console.log(`${folder} was deleted successfully.`);
|
|
376
|
-
} else {
|
|
377
|
-
console.log(`${folder} does not exist.`);
|
|
378
1142
|
}
|
|
379
1143
|
});
|
|
380
1144
|
}
|
|
@@ -386,44 +1150,53 @@ async function main() {
|
|
|
386
1150
|
"swagger-docs"
|
|
387
1151
|
);
|
|
388
1152
|
if (fs.existsSync(swaggerDocsFolder)) {
|
|
389
|
-
fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
|
|
1153
|
+
fs.rmSync(swaggerDocsFolder, { recursive: true, force: true });
|
|
390
1154
|
console.log(`swagger-docs was deleted successfully.`);
|
|
391
1155
|
}
|
|
392
1156
|
const swaggerFiles = ["swagger-config.ts"];
|
|
393
1157
|
swaggerFiles.forEach((file) => {
|
|
394
1158
|
const filePath = path.join(projectPath, "settings", file);
|
|
395
1159
|
if (fs.existsSync(filePath)) {
|
|
396
|
-
fs.unlinkSync(filePath);
|
|
1160
|
+
fs.unlinkSync(filePath);
|
|
397
1161
|
console.log(`${file} was deleted successfully.`);
|
|
398
|
-
} else {
|
|
399
|
-
console.log(`${file} does not exist.`);
|
|
400
1162
|
}
|
|
401
1163
|
});
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
"
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
1164
|
+
// Only add to uninstall list if packages are actually installed
|
|
1165
|
+
if (isNpmPackageInstalled("swagger-jsdoc")) {
|
|
1166
|
+
updateUninstallNpmDependencies.push("swagger-jsdoc");
|
|
1167
|
+
}
|
|
1168
|
+
if (isNpmPackageInstalled("@types/swagger-jsdoc")) {
|
|
1169
|
+
updateUninstallNpmDependencies.push("@types/swagger-jsdoc");
|
|
1170
|
+
}
|
|
1171
|
+
if (isNpmPackageInstalled("prompts")) {
|
|
1172
|
+
updateUninstallNpmDependencies.push("prompts");
|
|
1173
|
+
}
|
|
1174
|
+
if (isNpmPackageInstalled("@types/prompts")) {
|
|
1175
|
+
updateUninstallNpmDependencies.push("@types/prompts");
|
|
1176
|
+
}
|
|
408
1177
|
}
|
|
409
1178
|
if (!updateAnswer.tailwindcss) {
|
|
410
1179
|
const tailwindFiles = ["postcss.config.js"];
|
|
411
1180
|
tailwindFiles.forEach((file) => {
|
|
412
1181
|
const filePath = path.join(projectPath, file);
|
|
413
1182
|
if (fs.existsSync(filePath)) {
|
|
414
|
-
fs.unlinkSync(filePath);
|
|
1183
|
+
fs.unlinkSync(filePath);
|
|
415
1184
|
console.log(`${file} was deleted successfully.`);
|
|
416
|
-
} else {
|
|
417
|
-
console.log(`${file} does not exist.`);
|
|
418
1185
|
}
|
|
419
1186
|
});
|
|
420
|
-
|
|
1187
|
+
// Only add to uninstall list if packages are actually installed
|
|
1188
|
+
const tailwindPackages = [
|
|
421
1189
|
"tailwindcss",
|
|
422
1190
|
"postcss",
|
|
423
1191
|
"postcss-cli",
|
|
424
1192
|
"@tailwindcss/postcss",
|
|
425
|
-
"cssnano"
|
|
426
|
-
|
|
1193
|
+
"cssnano",
|
|
1194
|
+
];
|
|
1195
|
+
tailwindPackages.forEach((pkg) => {
|
|
1196
|
+
if (isNpmPackageInstalled(pkg)) {
|
|
1197
|
+
updateUninstallNpmDependencies.push(pkg);
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
427
1200
|
}
|
|
428
1201
|
if (!updateAnswer.websocket) {
|
|
429
1202
|
const websocketFiles = [
|
|
@@ -433,10 +1206,8 @@ async function main() {
|
|
|
433
1206
|
websocketFiles.forEach((file) => {
|
|
434
1207
|
const filePath = path.join(projectPath, "settings", file);
|
|
435
1208
|
if (fs.existsSync(filePath)) {
|
|
436
|
-
fs.unlinkSync(filePath);
|
|
1209
|
+
fs.unlinkSync(filePath);
|
|
437
1210
|
console.log(`${file} was deleted successfully.`);
|
|
438
|
-
} else {
|
|
439
|
-
console.log(`${file} does not exist.`);
|
|
440
1211
|
}
|
|
441
1212
|
});
|
|
442
1213
|
const websocketFolder = path.join(
|
|
@@ -446,18 +1217,28 @@ async function main() {
|
|
|
446
1217
|
"Websocket"
|
|
447
1218
|
);
|
|
448
1219
|
if (fs.existsSync(websocketFolder)) {
|
|
449
|
-
fs.rmSync(websocketFolder, { recursive: true, force: true });
|
|
1220
|
+
fs.rmSync(websocketFolder, { recursive: true, force: true });
|
|
450
1221
|
console.log(`Websocket folder was deleted successfully.`);
|
|
451
1222
|
}
|
|
452
|
-
|
|
453
|
-
|
|
1223
|
+
// Only add to uninstall list if packages are actually installed
|
|
1224
|
+
if (isNpmPackageInstalled("chokidar-cli")) {
|
|
1225
|
+
updateUninstallNpmDependencies.push("chokidar-cli");
|
|
1226
|
+
}
|
|
1227
|
+
if (isComposerPackageInstalled("cboden/ratchet")) {
|
|
1228
|
+
updateUninstallComposerDependencies.push("cboden/ratchet");
|
|
1229
|
+
}
|
|
454
1230
|
}
|
|
455
1231
|
if (!updateAnswer.prisma) {
|
|
456
|
-
|
|
1232
|
+
const prismaPackages = [
|
|
457
1233
|
"prisma",
|
|
458
1234
|
"@prisma/client",
|
|
459
|
-
"@prisma/internals"
|
|
460
|
-
|
|
1235
|
+
"@prisma/internals",
|
|
1236
|
+
];
|
|
1237
|
+
prismaPackages.forEach((pkg) => {
|
|
1238
|
+
if (isNpmPackageInstalled(pkg)) {
|
|
1239
|
+
updateUninstallNpmDependencies.push(pkg);
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
461
1242
|
}
|
|
462
1243
|
if (!updateAnswer.docker) {
|
|
463
1244
|
const dockerFiles = [
|
|
@@ -469,14 +1250,18 @@ async function main() {
|
|
|
469
1250
|
dockerFiles.forEach((file) => {
|
|
470
1251
|
const filePath = path.join(projectPath, file);
|
|
471
1252
|
if (fs.existsSync(filePath)) {
|
|
472
|
-
fs.unlinkSync(filePath);
|
|
1253
|
+
fs.unlinkSync(filePath);
|
|
473
1254
|
console.log(`${file} was deleted successfully.`);
|
|
474
|
-
} else {
|
|
475
|
-
console.log(`${file} does not exist.`);
|
|
476
1255
|
}
|
|
477
1256
|
});
|
|
478
1257
|
}
|
|
1258
|
+
// Only uninstall if there are packages to uninstall
|
|
479
1259
|
if (updateUninstallNpmDependencies.length > 0) {
|
|
1260
|
+
console.log(
|
|
1261
|
+
`Uninstalling npm packages: ${updateUninstallNpmDependencies.join(
|
|
1262
|
+
", "
|
|
1263
|
+
)}`
|
|
1264
|
+
);
|
|
480
1265
|
await uninstallNpmDependencies(
|
|
481
1266
|
projectPath,
|
|
482
1267
|
updateUninstallNpmDependencies,
|
|
@@ -484,6 +1269,11 @@ async function main() {
|
|
|
484
1269
|
);
|
|
485
1270
|
}
|
|
486
1271
|
if (updateUninstallComposerDependencies.length > 0) {
|
|
1272
|
+
console.log(
|
|
1273
|
+
`Uninstalling composer packages: ${updateUninstallComposerDependencies.join(
|
|
1274
|
+
", "
|
|
1275
|
+
)}`
|
|
1276
|
+
);
|
|
487
1277
|
await uninstallComposerDependencies(
|
|
488
1278
|
projectPath,
|
|
489
1279
|
updateUninstallComposerDependencies
|