create-prisma-php-app 5.0.0-alpha.1 → 5.0.0-alpha.3
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/AGENTS.md +39 -2
- package/dist/README.md +115 -98
- package/dist/index.js +1 -1
- package/dist/public/js/pp-reactive-v2.js +1 -1
- package/package.json +1 -1
package/dist/AGENTS.md
CHANGED
|
@@ -131,6 +131,43 @@ Before generating code, choose the documentation file based on the task.
|
|
|
131
131
|
- **General doc entry point and framework orientation**
|
|
132
132
|
Read `index.md`
|
|
133
133
|
|
|
134
|
+
## Default interactive UI and fetching rule
|
|
135
|
+
|
|
136
|
+
For normal full-stack Prisma PHP work, assume the user wants the **PulsePoint-first** approach unless they explicitly ask otherwise.
|
|
137
|
+
|
|
138
|
+
Default interaction stack:
|
|
139
|
+
|
|
140
|
+
1. render route UI with `index.php`
|
|
141
|
+
2. keep browser-side interactivity in **PulsePoint**
|
|
142
|
+
3. call backend PHP from the frontend with **`pp.fetchFunction(...)`**
|
|
143
|
+
4. mark callable PHP functions or methods with **`#[Exposed]`**
|
|
144
|
+
5. validate and normalize input on the PHP side with **`PP\Validator`**
|
|
145
|
+
|
|
146
|
+
Treat this as the default for:
|
|
147
|
+
|
|
148
|
+
- search
|
|
149
|
+
- filters
|
|
150
|
+
- pagination
|
|
151
|
+
- quick edit flows
|
|
152
|
+
- toggles
|
|
153
|
+
- dialogs and drawers
|
|
154
|
+
- inline validation
|
|
155
|
+
- route-local CRUD actions
|
|
156
|
+
- dashboard interactions
|
|
157
|
+
- similar reactive page behavior
|
|
158
|
+
|
|
159
|
+
Do **not** default to:
|
|
160
|
+
|
|
161
|
+
- a PHP-only interaction style
|
|
162
|
+
- ad hoc `fetch('/api/...')` patterns
|
|
163
|
+
- extra `route.php` files for page-local interactions that already fit `pp.fetchFunction(...)`
|
|
164
|
+
|
|
165
|
+
Choose a more PHP-only pattern only when:
|
|
166
|
+
|
|
167
|
+
- the user explicitly asks for PHP-only behavior
|
|
168
|
+
- the task is clearly non-reactive
|
|
169
|
+
- the task is a standalone API, webhook, integration endpoint, or public JSON handler
|
|
170
|
+
|
|
134
171
|
## Default workflow for AI agents
|
|
135
172
|
|
|
136
173
|
Use this workflow unless the user asks for something narrower:
|
|
@@ -151,9 +188,9 @@ When the task is about creating or editing a route, do not guess.
|
|
|
151
188
|
|
|
152
189
|
Important: creating a route means creating or updating the correct folder and route file under `src/app`. It does **not** mean editing generated route metadata. In particular, never update `files-list.json` by hand.
|
|
153
190
|
|
|
154
|
-
- Use `index.php` for rendered UI and normal page routes
|
|
191
|
+
- Use `index.php` for rendered UI and normal page routes, and default to PulsePoint plus `pp.fetchFunction(...)` for interactive behavior inside those routes unless the user explicitly asks for PHP-only behavior
|
|
155
192
|
- Use `layout.php` for shared UI that wraps route subtrees
|
|
156
|
-
- Use `route.php` for direct handlers such as JSON endpoints, API-style routes, AJAX handlers, form-processing endpoints, webhooks, or other no-view server logic
|
|
193
|
+
- Use `route.php` for direct handlers such as JSON endpoints, API-style routes, AJAX handlers, form-processing endpoints, webhooks, or other no-view server logic; do not default to `route.php` for normal route-local interactions that fit PulsePoint plus `pp.fetchFunction(...)`
|
|
157
194
|
- Use `not-found.php` for route-specific not-found UI
|
|
158
195
|
- Use `error.php` for route or app-level error UI
|
|
159
196
|
|
package/dist/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Prisma PHP is a modern full-stack PHP framework that combines native PHP, PulsePoint reactivity, PHPX components, and a Prisma-inspired ORM into one cohesive developer experience.
|
|
4
4
|
|
|
5
|
-
Build
|
|
5
|
+
Build reactive interfaces with a server-first mental model, structured routing, type-safe data access, and a project layout designed for real applications.
|
|
6
6
|
|
|
7
7
|
## Getting Started
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Create a new Prisma PHP project:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx create-prisma-php-app@latest my-app
|
|
@@ -18,7 +18,7 @@ Start the development server:
|
|
|
18
18
|
npm run dev
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Open
|
|
21
|
+
Open the local app in your browser after the dev server starts.
|
|
22
22
|
|
|
23
23
|
## Prerequisites
|
|
24
24
|
|
|
@@ -36,10 +36,12 @@ If you are using XAMPP on Windows, enabling `extension=zip` in `php.ini` is reco
|
|
|
36
36
|
Prisma PHP brings together the core pieces needed to build full-stack PHP apps:
|
|
37
37
|
|
|
38
38
|
- **Native PHP + modern reactivity** with PulsePoint
|
|
39
|
-
- **PHPX component system**
|
|
39
|
+
- **PHPX component system** for reusable UI composition
|
|
40
40
|
- **Prisma PHP ORM** for schema-first, type-safe database access
|
|
41
|
+
- **Built-in authentication patterns** for sessions, route protection, RBAC, credentials auth, and provider login
|
|
42
|
+
- **File-based routing** with clear route file conventions
|
|
41
43
|
- **CLI scaffolding** for new apps, starter kits, and optional features
|
|
42
|
-
- **Flexible deployment options** for
|
|
44
|
+
- **Flexible deployment options** for local development and production workflows
|
|
43
45
|
|
|
44
46
|
## Common Create Commands
|
|
45
47
|
|
|
@@ -61,136 +63,151 @@ Use a starter kit:
|
|
|
61
63
|
npx create-prisma-php-app my-app --starter-kit=fullstack
|
|
62
64
|
```
|
|
63
65
|
|
|
64
|
-
Other documented flags include
|
|
66
|
+
Other documented flags may include capabilities such as WebSocket support, MCP support, backend-only mode, Swagger docs, and Prisma integration depending on the installed version.
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
- `--mcp`
|
|
68
|
-
- `--backend-only`
|
|
69
|
-
- `--swagger-docs`
|
|
70
|
-
- `--docker`
|
|
68
|
+
## Documentation
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
Prisma PHP ships with local documentation for the installed project version.
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
The installed docs live here:
|
|
75
73
|
|
|
76
|
-
```
|
|
77
|
-
prisma-php
|
|
78
|
-
├── prisma/ # schema, migrations, seed files
|
|
79
|
-
├── public/ # public entry point and assets
|
|
80
|
-
├── settings/ # project configuration
|
|
81
|
-
├── src/ # application source code
|
|
82
|
-
├── package.json # frontend/dev scripts
|
|
83
|
-
├── composer.json # PHP dependencies
|
|
84
|
-
└── prisma-php.json # Prisma PHP project capability manifest
|
|
74
|
+
```txt
|
|
75
|
+
node_modules/prisma-php/dist/docs
|
|
85
76
|
```
|
|
86
77
|
|
|
78
|
+
Treat these installed docs as the primary documentation source for the current project version.
|
|
79
|
+
|
|
80
|
+
You can also explore the public docs site:
|
|
81
|
+
|
|
82
|
+
```txt
|
|
83
|
+
https://prismaphp.tsnc.tech/
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## AI Quick Start
|
|
87
|
+
|
|
88
|
+
If you are using AI-assisted development in a Prisma PHP project:
|
|
89
|
+
|
|
90
|
+
1. Read `./prisma-php.json` first.
|
|
91
|
+
2. Read the installed docs in `node_modules/prisma-php/dist/docs`.
|
|
92
|
+
3. Read `AGENTS.md` for task-routing rules, framework constraints, and code-generation guidance.
|
|
93
|
+
4. Inspect `vendor/tsnc/prisma-php/src` only when the docs do not answer the task.
|
|
94
|
+
|
|
95
|
+
### Default interactive UI rule
|
|
96
|
+
|
|
97
|
+
In Prisma PHP, AI should treat **PulsePoint as the default frontend interactivity model**.
|
|
98
|
+
|
|
99
|
+
For normal full-stack page work:
|
|
100
|
+
|
|
101
|
+
- render the page with `index.php`
|
|
102
|
+
- manage browser-side state and UI behavior with **PulsePoint**
|
|
103
|
+
- call PHP from the frontend with **`pp.fetchFunction(...)`**
|
|
104
|
+
- expose callable PHP functions with **`#[Exposed]`**
|
|
105
|
+
- validate incoming data on the PHP side with **`PP\Validator`**
|
|
106
|
+
|
|
107
|
+
Do **not** default to PHP-only refresh cycles, handcrafted `fetch('/api/...')` patterns, or extra `route.php` handlers for normal interactive page behavior when PulsePoint plus `pp.fetchFunction(...)` already fits the task.
|
|
108
|
+
|
|
109
|
+
Only prefer a more PHP-only interaction style when the **user explicitly asks for PHP-only behavior**, or when the task is clearly non-reactive.
|
|
110
|
+
|
|
87
111
|
## Project Capability Manifest
|
|
88
112
|
|
|
89
113
|
Prisma PHP uses `prisma-php.json` at the repository root as the source of truth for enabled framework features and local environment configuration.
|
|
90
114
|
|
|
91
|
-
|
|
115
|
+
Use it to verify capabilities such as:
|
|
92
116
|
|
|
93
|
-
-
|
|
117
|
+
- Tailwind CSS support
|
|
94
118
|
- backend-only mode
|
|
95
|
-
- Prisma ORM
|
|
96
|
-
- Tailwind CSS availability
|
|
119
|
+
- Prisma ORM support
|
|
97
120
|
- Swagger docs
|
|
98
|
-
-
|
|
121
|
+
- WebSocket support
|
|
99
122
|
- MCP support
|
|
100
123
|
- TypeScript support
|
|
101
|
-
- local development paths and BrowserSync
|
|
102
|
-
|
|
103
|
-
A typical file looks like this:
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"projectName": "test-latest",
|
|
108
|
-
"projectRootPath": "C:\\xampp\\htdocs\\projects\\prisma-php\\test\\test-latest",
|
|
109
|
-
"phpEnvironment": "XAMPP",
|
|
110
|
-
"phpRootPathExe": "C:\\xampp\\php\\php.exe",
|
|
111
|
-
"bsTarget": "http://localhost/projects/prisma-php/test/test-latest/",
|
|
112
|
-
"bsPathRewrite": {
|
|
113
|
-
"^/": "/projects/prisma-php/test/test-latest/"
|
|
114
|
-
},
|
|
115
|
-
"backendOnly": false,
|
|
116
|
-
"swaggerDocs": false,
|
|
117
|
-
"tailwindcss": true,
|
|
118
|
-
"websocket": false,
|
|
119
|
-
"mcp": false,
|
|
120
|
-
"prisma": false,
|
|
121
|
-
"typescript": false,
|
|
122
|
-
"version": "4.4.9",
|
|
123
|
-
"componentScanDirs": ["src", "vendor/tsnc/prisma-php/src"],
|
|
124
|
-
"excludeFiles": []
|
|
125
|
-
}
|
|
126
|
-
```
|
|
124
|
+
- local development paths and BrowserSync settings
|
|
127
125
|
|
|
128
|
-
|
|
126
|
+
Do not assume a feature is enabled unless `prisma-php.json` confirms it.
|
|
129
127
|
|
|
130
|
-
|
|
128
|
+
## Documentation Map
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
- do not assume Tailwind CSS, Prisma ORM, MCP, Swagger, TypeScript, or websocket support is enabled unless the file says so
|
|
134
|
-
- read the installed Prisma PHP docs for the current version before changing routing, layouts, middleware, or framework-specific behavior
|
|
135
|
-
- use `prisma-php.json` together with the installed docs to make safer decisions
|
|
130
|
+
Use these docs as the main entry points for common work:
|
|
136
131
|
|
|
137
|
-
|
|
132
|
+
- `index.md` for the general documentation entry point
|
|
133
|
+
- `project-structure.md` for project structure, route placement, and file conventions
|
|
134
|
+
- `layouts-and-pages.md` for pages, layouts, nested routes, and dynamic routes
|
|
135
|
+
- `components.md` for PHPX components, props, children, fragments, icons, buttons, and composition
|
|
136
|
+
- `fetching-data.md` for `pp.fetchFunction(...)`, `#[Exposed]`, and interactive backend flows
|
|
137
|
+
- `prisma-php-orm.md` for Prisma ORM, `schema.prisma`, migrations, and generated PHP classes
|
|
138
|
+
- `authentication.md` for auth strategy, sessions, RBAC, credentials auth, and provider flows
|
|
139
|
+
- `file-manager.md` for uploads, `multipart/form-data`, `$_FILES`, and `PP\FileManager\UploadFile`
|
|
140
|
+
- `route-handlers.md` for `route.php`, JSON responses, and direct server handlers
|
|
141
|
+
- `error-handling.md` for expected errors, `error.php`, `not-found.php`, and validation failures
|
|
142
|
+
- `caching.md` for cache behavior and `CacheHandler`
|
|
143
|
+
- `metadata-and-og-images.md` for metadata, title, description, icons, and head behavior
|
|
144
|
+
- `pulsepoint.md` for PulsePoint runtime usage and reactivity patterns
|
|
145
|
+
- `upgrading.md` for feature enablement and project update workflows
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
## Recommended Development Approach
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
When working in Prisma PHP:
|
|
142
150
|
|
|
143
|
-
|
|
151
|
+
- prefer the installed Prisma PHP docs over assumptions from other frameworks
|
|
152
|
+
- use `AGENTS.md` as the strict AI operating guide
|
|
153
|
+
- inspect nearby project files before generating new framework-specific code
|
|
154
|
+
- inspect framework internals only when the installed docs are not enough
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
## Route and File Conventions
|
|
146
157
|
|
|
147
|
-
-
|
|
148
|
-
- `findFirst`
|
|
149
|
-
- `findUnique`
|
|
150
|
-
- `create`
|
|
151
|
-
- `update`
|
|
152
|
-
- `delete`
|
|
153
|
-
- `upsert`
|
|
158
|
+
Prisma PHP uses file-based routing with special route files such as:
|
|
154
159
|
|
|
155
|
-
|
|
160
|
+
- `index.php` for rendered UI routes
|
|
161
|
+
- `layout.php` for shared UI wrappers
|
|
162
|
+
- `route.php` for direct handlers such as JSON or API-style endpoints
|
|
163
|
+
- `loading.php` for loading UI
|
|
164
|
+
- `not-found.php` for route-level not-found UI
|
|
165
|
+
- `error.php` for route-level or app-level error UI
|
|
156
166
|
|
|
157
|
-
- `
|
|
158
|
-
- `generate`
|
|
159
|
-
- `push`
|
|
160
|
-
- `reset`
|
|
167
|
+
For task-specific route decision rules and framework generation rules, read `AGENTS.md`.
|
|
161
168
|
|
|
162
|
-
##
|
|
169
|
+
## PulsePoint and Frontend Reactivity
|
|
163
170
|
|
|
164
|
-
Prisma PHP
|
|
171
|
+
Prisma PHP uses PulsePoint for browser-side reactivity, and AI should assume this is the **default choice** for interactive UI behavior in full-stack routes.
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
- **CI/CD deployment** with GitHub Actions
|
|
168
|
-
- **Docker deployment** for containerized environments
|
|
173
|
+
When working with runtime features such as:
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
- `pp.state`
|
|
176
|
+
- `pp.effect`
|
|
177
|
+
- `pp.ref`
|
|
178
|
+
- `pp-for`
|
|
179
|
+
- `pp-spread`
|
|
180
|
+
- `pp-ref`
|
|
171
181
|
|
|
172
|
-
|
|
173
|
-
npm run build
|
|
174
|
-
```
|
|
182
|
+
read the installed Prisma PHP docs for the current version first, then consult:
|
|
175
183
|
|
|
176
|
-
|
|
184
|
+
```txt
|
|
185
|
+
https://pulsepoint.tsnc.tech/llms
|
|
186
|
+
```
|
|
177
187
|
|
|
178
|
-
##
|
|
188
|
+
## Project Structure
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
A generated Prisma PHP project typically includes folders like these:
|
|
181
191
|
|
|
182
|
-
|
|
183
|
-
-
|
|
192
|
+
```text
|
|
193
|
+
prisma-php-project/
|
|
194
|
+
├── prisma/ # schema, migrations, seed files
|
|
195
|
+
├── public/ # public entry point and assets
|
|
196
|
+
├── settings/ # project configuration
|
|
197
|
+
├── src/ # application source code
|
|
198
|
+
├── package.json # frontend/dev scripts
|
|
199
|
+
├── composer.json # PHP dependencies
|
|
200
|
+
└── prisma-php.json # Prisma PHP project capability manifest
|
|
201
|
+
```
|
|
184
202
|
|
|
185
|
-
##
|
|
203
|
+
## Updating Existing Projects
|
|
186
204
|
|
|
187
|
-
|
|
205
|
+
When enabling features or syncing framework-managed project files:
|
|
188
206
|
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
1. Update `prisma-php.json` first.
|
|
208
|
+
2. Read `upgrading.md` in the installed docs.
|
|
209
|
+
3. Run the documented project update workflow for the current version.
|
|
192
210
|
|
|
193
|
-
##
|
|
211
|
+
## Learn More
|
|
194
212
|
|
|
195
|
-
|
|
196
|
-
- If PowerShell blocks local scripts during `npm run dev`, check your Windows execution policy.
|
|
213
|
+
Start with the installed docs for the current project version, use the topic-specific markdown guides for focused work, and rely on `AGENTS.md` when strict AI generation rules are needed.
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!1,swaggerDocs:!1,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","src/app/layout.php","src/app/index.php"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/app/layout.php","src/app/index.php","public/js/main.js","src/app/globals.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,websocket:!0,prisma:!0,swaggerDocs:!0,mcp:!0},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/lib/websocket","src/lib/mcp"]},ecommerce:{id:"ecommerce",name:"E-commerce Starter",description:"Full e-commerce application with cart, payments, and admin",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-ecommerce-starter",branch:"main"}},blog:{id:"blog",name:"Blog CMS",description:"Blog content management system",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!1,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-blog-starter"}}};function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return console.error("Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"),{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+8).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,"/"),r=n.replace(/\/\/+/g,"/");return{bsTarget:`${o}/`,bsPathRewrite:{"^/":`/${r.startsWith("/")?r.substring(1):r}/`}}}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/globals.css -o public/css/styles.css --watch","tailwind:build":"postcss src/app/globals.css -o public/css/styles.css"},c.push("tailwind")),s.typescript&&!s.backendOnly&&(n.scripts={...n.scripts,"ts:watch":"vite build --watch","ts:build":"vite build"},c.push("ts:watch")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.mcp&&(n.scripts={...n.scripts,mcp:"tsx settings/restart-mcp.ts"},c.push("mcp")),s.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(" ")}`;let r=["browserSync:build"];s.tailwindcss&&r.unshift("tailwind:build"),s.typescript&&!s.backendOnly&&r.unshift("ts:build"),o.build=`npm-run-all ${r.join(" ")}`,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,"public","js","main.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\nwindow.ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(!t.mcp&&n.includes("src\\lib\\mcp"))return;if((!t.typescript||t.backendOnly)&&(n.endsWith("\\ts")||n.includes("\\ts\\")))return;if((!t.typescript||t.backendOnly)&&(n.endsWith("\\vite-plugins")||n.includes("\\vite-plugins\\")||n.includes("\\vite-plugins")))return;if(t.backendOnly&&n.includes("public\\js")||t.backendOnly&&n.includes("public\\css")||t.backendOnly&&n.includes("public\\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("globals.css")||s.includes("styles.css")))return;if(!t.websocket&&s.includes("restart-websocket.ts"))return;if(!t.mcp&&s.includes("restart-mcp.ts"))return;if(t.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="/css/index.css" rel="stylesheet" />'),n+='\n <script type="module" src="/js/main.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="/css/styles.css" rel="stylesheet" /> ${n}`:n),e=e.replace("</head>",`${c}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){console.error(chalk.red("Error modifying layout.php:"),e)}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");fs.existsSync(t)&&checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){if(!updateAnswer?.isUpdate)return!1;const s=e.replace(/\\/g,"/");return!!updateAnswer?.excludeFilePath?.includes(s)||!!updateAnswer?.excludeFiles&&updateAnswer.excludeFiles.some(e=>{const t=e.replace(/\\/g,"/");return s.endsWith("/"+t)||s===t})}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"},{src:"/CLAUDE.md",dest:"/CLAUDE.md"},{src:"/AGENTS.md",dest:"/AGENTS.md"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),s.typescript&&!s.backendOnly&&t.push({src:"/vite.config.ts",dest:"/vite.config.ts"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"},{src:"/public",dest:"/public"}];s.typescript&&!s.backendOnly&&n.push({src:"/ts",dest:"/ts"}),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(),r=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${c}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${o}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins (CSV or JSON array)\nCORS_ALLOWED_ORIGINS=[]\n\n# If you need cookies/Authorization across origins, keep this true\nCORS_ALLOW_CREDENTIALS="true"\n\n# Optional tuning\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\nCORS_EXPOSE_HEADERS=""\nCORS_MAX_AGE="86400"\n\n# Rate Limiting\nRATE_LIMIT_DEFAULT="200 per minute"\nRATE_LIMIT_RPC="60 per minute"\nRATE_LIMIT_AUTH="60 per minute"`;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${r}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,r)}async function getAnswer(e={},s=!1){if(s)return{projectName:e.projectName??"my-app",backendOnly:e.backendOnly??!1,swaggerDocs:e.swaggerDocs??!1,tailwindcss:e.tailwindcss??!1,typescript:e.typescript??!1,websocket:e.websocket??!1,mcp:e.mcp??!1,prisma:e.prisma??!1};if(e.starterKit){const s=e.starterKit;let t=null;if(STARTER_KITS[s]&&(t=STARTER_KITS[s]),t){const n={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,websocket:t.features.websocket??!1,prisma:t.features.prisma??!1,swaggerDocs:t.features.swaggerDocs??!1,mcp:t.features.mcp??!1,typescript:t.features.typescript??!1},c=process.argv.slice(2);return c.includes("--backend-only")&&(n.backendOnly=!0),c.includes("--swagger-docs")&&(n.swaggerDocs=!0),c.includes("--tailwindcss")&&(n.tailwindcss=!0),c.includes("--websocket")&&(n.websocket=!0),c.includes("--mcp")&&(n.mcp=!0),c.includes("--prisma")&&(n.prisma=!0),c.includes("--typescript")&&(n.typescript=!0),n}if(e.starterKitSource){const t={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1,typescript:!1},n=process.argv.slice(2);return n.includes("--backend-only")&&(t.backendOnly=!0),n.includes("--swagger-docs")&&(t.swaggerDocs=!0),n.includes("--tailwindcss")&&(t.tailwindcss=!0),n.includes("--websocket")&&(t.websocket=!0),n.includes("--mcp")&&(t.mcp=!0),n.includes("--prisma")&&(t.prisma=!0),n.includes("--typescript")&&(t.typescript=!0),t}}const t=[];e.projectName||t.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||t.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const n=()=>{console.warn(chalk.red("Operation cancelled by the user.")),process.exit(0)},c=await prompts(t,{onCancel:n}),o=[];c.backendOnly??e.backendOnly??!1?(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||o.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.typescript||o.push({type:"toggle",name:"typescript",message:`Would you like to use ${chalk.blue("TypeScript")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"}));const r=await prompts(o,{onCancel:n});return{projectName:c.projectName?String(c.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:c.backendOnly??e.backendOnly??!1,swaggerDocs:r.swaggerDocs??e.swaggerDocs??!1,tailwindcss:r.tailwindcss??e.tailwindcss??!1,typescript:r.typescript??e.typescript??!1,websocket:r.websocket??e.websocket??!1,mcp:r.mcp??e.mcp??!1,prisma:r.prisma??e.prisma??!1}}async function uninstallNpmDependencies(e,s,t=!1){console.log("Uninstalling Node dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){console.log("Uninstalling Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(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]:(console.error(`Package ${e} is not installed`),null)}catch(e){return console.error(e instanceof Error?e.message:String(e)),null}}async function installNpmDependencies(e,s,t=!1){fs.existsSync(path.join(e,"package.json"))?console.log("Updating existing Node.js project..."):console.log("Initializing new Node.js project..."),fs.existsSync(path.join(e,"package.json"))||execSync("npm init -y",{stdio:"inherit",cwd:e}),console.log((t?"Installing development dependencies":"Installing dependencies")+":"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const n=`npm install ${t?"--save-dev":""} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}function getComposerCmd(){try{return execSync("composer --version",{stdio:"ignore"}),console.log("✓ Using global composer command"),{cmd:"composer",baseArgs:[]}}catch{const e="C:\\xampp\\php\\php.exe",s="C:\\ProgramData\\ComposerSetup\\bin\\composer.phar";if(!fs.existsSync(e))throw console.error(`✗ PHP not found at ${e}`),new Error(`PHP executable not found at ${e}`);if(!fs.existsSync(s))throw console.error(`✗ Composer not found at ${s}`),new Error(`Composer phar not found at ${s}`);return console.log("✓ Using XAMPP PHP with Composer phar"),{cmd:e,baseArgs:[s]}}}export async function installComposerDependencies(e,s){const{cmd:t,baseArgs:n}=getComposerCmd(),c=path.join(e,"composer.json"),o=fs.existsSync(c);if(console.log(chalk.green("Composer project initialization: "+(o?"Updating existing project…":"Setting up new project…"))),fs.existsSync(e)||(console.log(`Creating base directory: ${e}`),fs.mkdirSync(e,{recursive:!0})),!o){const s=[...n,"init","--no-interaction","--name","tsnc/prisma-php-app","--require","php:^8.2","--type","project","--version","1.0.0"];console.log("Attempting composer init...");const o=spawnSync(t,s,{cwd:e,stdio:["ignore","pipe","pipe"],encoding:"utf8"}),r=fs.existsSync(c);if(0===o.status&&r)console.log("✓ Composer init successful and composer.json created");else{0!==o.status?(console.log(`Composer init failed with status ${o.status}`),o.stderr&&console.log(`Stderr: ${o.stderr}`)):console.log("Composer init reported success but didn't create composer.json"),console.log("Creating composer.json manually...");const s={name:"tsnc/prisma-php-app",type:"project",version:"1.0.0",require:{php:"^8.2"},autoload:{"psr-4":{"":"src/"}}};try{const t=path.resolve(e,"composer.json");if(console.log(`Writing composer.json to: ${t}`),fs.writeFileSync(t,JSON.stringify(s,null,2),{encoding:"utf8"}),!fs.existsSync(t))throw new Error("File creation appeared to succeed but file doesn't exist");console.log("✓ Successfully created composer.json")}catch(s){if(console.error("✗ Failed to create composer.json:",s),console.error(`Base directory: ${e}`),console.error(`Absolute base directory: ${path.resolve(e)}`),console.error(`Target file path: ${c}`),console.error(`Absolute target file path: ${path.resolve(c)}`),console.error(`Current working directory: ${process.cwd()}`),console.error(`Base directory exists: ${fs.existsSync(e)}`),fs.existsSync(e))try{const s=fs.statSync(e);console.error(`Base directory is writable: ${s.isDirectory()}`)}catch(e){console.error(`Cannot stat base directory: ${e}`)}throw new Error(`Cannot create composer.json: ${s}`)}}}const r=path.resolve(e,"composer.json");if(!fs.existsSync(r))throw console.error(`✗ composer.json still not found at ${r}`),console.error("Directory contents:",fs.readdirSync(e)),new Error("Failed to create composer.json - file does not exist after all attempts");let i;try{const e=fs.readFileSync(r,"utf8");console.log("✓ Successfully read composer.json"),i=JSON.parse(e)}catch(e){throw console.error("✗ Failed to read/parse composer.json:",e),new Error(`Cannot read composer.json: ${e}`)}i.autoload??={},i.autoload["psr-4"]??={},i.autoload["psr-4"][""]??="src/";try{fs.writeFileSync(r,JSON.stringify(i,null,2)),console.log("✓ Updated composer.json with PSR-4 autoload")}catch(e){throw console.error("✗ Failed to update composer.json:",e),e}if(s.length){console.log("Installing Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));try{const c=`${t} ${[...n,"require","--no-interaction","-W",...s].join(" ")}`;execSync(c,{stdio:"inherit",cwd:e,env:{...process.env}}),console.log("✓ Composer dependencies installed")}catch(e){throw console.error("✗ Failed to install composer dependencies:",e),e}}if(o)try{execSync(`${t} ${[...n,"update","--lock","--no-install","--no-interaction"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer lock updated")}catch(e){throw console.error("✗ Failed to update composer lock:",e),e}try{execSync(`${t} ${[...n,"dump-autoload","--quiet"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer autoloader regenerated")}catch(e){throw console.error("✗ Failed to regenerate autoloader:",e),e}}const npmPinnedVersions={"@tailwindcss/postcss":"4.2.2","@types/browser-sync":"2.29.1","@types/node":"25.5.2","@types/prompts":"2.4.9","browser-sync":"3.0.4",chalk:"5.6.2","chokidar-cli":"3.0.0",cssnano:"7.1.4","http-proxy-middleware":"3.0.5","npm-run-all":"4.1.5","php-parser":"3.5.1",postcss:"8.5.9","postcss-cli":"11.0.1",prompts:"2.4.2",tailwindcss:"4.2.2",tsx:"4.21.0",typescript:"6.0.2",vite:"7.3.0","fast-glob":"3.3.3","prisma-php":"0.0.1"};function npmPkg(e){return npmPinnedVersions[e]?`${e}@${npmPinnedVersions[e]}`:e}const composerPinnedVersions={"vlucas/phpdotenv":"5.6.3","firebase/php-jwt":"7.0.5","phpmailer/phpmailer":"7.0.1","guzzlehttp/guzzle":"7.10.0","symfony/uid":"7.4.8","brick/math":"0.17.0","cboden/ratchet":"0.4.4","tsnc/prisma-php":"2.0.0","php-mcp/server":"3.3.0","gehrisandro/tailwind-merge-php":"1.2.0"};function composerPkg(e){return composerPinnedVersions[e]?`${e}:${composerPinnedVersions[e]}`:e}function removeDirectorySafe(e){if(fs.existsSync(e))try{return void fs.rmSync(e,{recursive:!0,force:!0,maxRetries:5,retryDelay:250})}catch(s){const t=s;if("win32"===globalThis.process?.platform&&("EPERM"===t.code||"EACCES"===t.code)){try{spawnSync("cmd",["/c","attrib","-R","-H","-S","/S","/D",`${e}\\*`],{stdio:"ignore"})}catch{}return void spawnSync("cmd",["/c","rd","/s","/q",e],{stdio:"ignore"})}throw s}}async function setupStarterKit(e,s){if(!s.starterKit)return;let t=null;if(STARTER_KITS[s.starterKit]?t=STARTER_KITS[s.starterKit]:s.starterKitSource&&(t={id:s.starterKit,name:`Custom Starter Kit (${s.starterKit})`,description:"Custom starter kit from external source",features:{},requiredFiles:[],source:{type:"git",url:s.starterKitSource}}),t){if(console.log(chalk.green(`Setting up ${t.name}...`)),t.source)try{const n=t.source.branch?`git clone -b ${t.source.branch} --depth 1 ${t.source.url} "${e}"`:`git clone --depth 1 ${t.source.url} "${e}"`;execSync(n,{stdio:"inherit"});removeDirectorySafe(path.join(e,".git")),console.log(chalk.blue("Starter kit cloned successfully!"));const c=path.join(e,"prisma-php.json");if(fs.existsSync(c))try{const t=JSON.parse(fs.readFileSync(c,"utf8")),n=e,o=bsConfigUrls(n);t.projectName=s.projectName,t.projectRootPath=n,t.bsTarget=o.bsTarget,t.bsPathRewrite=o.bsPathRewrite;const r=await fetchPackageVersion("create-prisma-php-app");t.version=t.version||r,fs.writeFileSync(c,JSON.stringify(t,null,2)),console.log(chalk.green("Updated prisma-php.json with new project details"))}catch(e){console.warn(chalk.yellow("Failed to update prisma-php.json, will create new one"))}}catch(e){throw console.error(chalk.red(`Failed to setup starter kit: ${e}`)),e}t.customSetup&&await t.customSetup(e,s),console.log(chalk.green(`✓ ${t.name} setup complete!`))}else console.warn(chalk.yellow(`Starter kit '${s.starterKit}' not found. Skipping...`))}function showStarterKits(){console.log(chalk.blue("\n🚀 Available Starter Kits:\n")),Object.values(STARTER_KITS).forEach(e=>{const s=e.source?" (Custom)":" (Built-in)";console.log(chalk.green(` ${e.id}${chalk.gray(s)}`)),console.log(` ${e.name}`),console.log(chalk.gray(` ${e.description}`)),e.source&&console.log(chalk.cyan(` Source: ${e.source.url}`));const t=Object.entries(e.features).filter(([,e])=>!0===e).map(([e])=>e).join(", ");t&&console.log(chalk.magenta(` Features: ${t}`)),console.log()}),console.log(chalk.yellow("Usage:")),console.log(" npx create-prisma-php-app my-project --starter-kit=basic"),console.log(" npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo"),console.log()}async function main(){try{const e=process.argv.slice(2),s=e.includes("-y");let t=e[0];const n=e.find(e=>e.startsWith("--starter-kit=")),c=n?.split("=")[1],o=e.find(e=>e.startsWith("--starter-kit-source=")),r=o?.split("=")[1];if(e.includes("--list-starter-kits"))return void showStarterKits();let i=null,a=!1;if(t){const n=process.cwd(),o=path.join(n,"prisma-php.json");if(c&&r){a=!0;const n={projectName:t,starterKit:c,starterKitSource:r,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};i=await getAnswer(n,s)}else if(fs.existsSync(o)){const c=readJsonFile(o);let r=[];c.excludeFiles?.map(e=>{const s=path.join(n,e);fs.existsSync(s)&&r.push(s.replace(/\\/g,"/"))}),updateAnswer={projectName:t,backendOnly:c.backendOnly,swaggerDocs:c.swaggerDocs,tailwindcss:c.tailwindcss,websocket:c.websocket,mcp:c.mcp,prisma:c.prisma,typescript:c.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:r??[],filePath:n};const a={projectName:t,backendOnly:e.includes("--backend-only")||c.backendOnly,swaggerDocs:e.includes("--swagger-docs")||c.swaggerDocs,tailwindcss:e.includes("--tailwindcss")||c.tailwindcss,typescript:e.includes("--typescript")||c.typescript,websocket:e.includes("--websocket")||c.websocket,prisma:e.includes("--prisma")||c.prisma,mcp:e.includes("--mcp")||c.mcp};i=await getAnswer(a,s),null!==i&&(updateAnswer={projectName:t,backendOnly:i.backendOnly,swaggerDocs:i.swaggerDocs,tailwindcss:i.tailwindcss,websocket:i.websocket,mcp:i.mcp,prisma:i.prisma,typescript:i.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:r??[],filePath:n})}else{const n={projectName:t,starterKit:c,starterKitSource:r,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};i=await getAnswer(n,s)}if(null===i)return void console.log(chalk.red("Installation cancelled."))}else i=await getAnswer({},s);if(null===i)return void console.warn(chalk.red("Installation cancelled."));const p=await fetchPackageVersion("create-prisma-php-app"),l=getInstalledPackageVersion("create-prisma-php-app");l?-1===compareVersions(l,p)&&(execSync("npm uninstall -g create-prisma-php-app",{stdio:"inherit"}),execSync("npm install -g create-prisma-php-app",{stdio:"inherit"})):execSync("npm install -g create-prisma-php-app",{stdio:"inherit"});const d=process.cwd();let u;if(t)if(a){const s=path.join(d,t);fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),u=s,await setupStarterKit(u,i),process.chdir(u);const n=path.join(u,"prisma-php.json");if(fs.existsSync(n)){const s=JSON.parse(fs.readFileSync(n,"utf8"));e.includes("--backend-only")&&(s.backendOnly=!0),e.includes("--swagger-docs")&&(s.swaggerDocs=!0),e.includes("--tailwindcss")&&(s.tailwindcss=!0),e.includes("--typescript")&&(s.typescript=!0),e.includes("--websocket")&&(s.websocket=!0),e.includes("--mcp")&&(s.mcp=!0),e.includes("--prisma")&&(s.prisma=!0),i={...i,backendOnly:s.backendOnly,swaggerDocs:s.swaggerDocs,tailwindcss:s.tailwindcss,typescript:s.typescript,websocket:s.websocket,mcp:s.mcp,prisma:s.prisma};let t=[];s.excludeFiles?.map(e=>{const s=path.join(u,e);fs.existsSync(s)&&t.push(s.replace(/\\/g,"/"))}),updateAnswer={...i,isUpdate:!0,componentScanDirs:s.componentScanDirs??[],excludeFiles:s.excludeFiles??[],excludeFilePath:t??[],filePath:u}}}else{const e=path.join(d,"prisma-php.json"),s=path.join(d,t),n=path.join(s,"prisma-php.json");fs.existsSync(e)?u=d:fs.existsSync(s)&&fs.existsSync(n)?(u=s,process.chdir(s)):(fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),u=s,process.chdir(s))}else fs.mkdirSync(i.projectName,{recursive:!0}),u=path.join(d,i.projectName),process.chdir(i.projectName);let m=[npmPkg("typescript"),npmPkg("@types/node"),npmPkg("tsx"),npmPkg("http-proxy-middleware"),npmPkg("chalk"),npmPkg("npm-run-all"),npmPkg("browser-sync"),npmPkg("@types/browser-sync"),npmPkg("php-parser"),npmPkg("prisma-php")],g=[composerPkg("vlucas/phpdotenv"),composerPkg("firebase/php-jwt"),composerPkg("phpmailer/phpmailer"),composerPkg("guzzlehttp/guzzle"),composerPkg("symfony/uid"),composerPkg("brick/math"),composerPkg("tsnc/prisma-php")];if(i.swaggerDocs&&m.push(npmPkg("swagger-jsdoc"),npmPkg("@types/swagger-jsdoc")),i.swaggerDocs&&i.prisma&&m.push(npmPkg("prompts"),npmPkg("@types/prompts")),i.tailwindcss&&(m.push(npmPkg("tailwindcss"),npmPkg("postcss"),npmPkg("postcss-cli"),npmPkg("@tailwindcss/postcss"),npmPkg("cssnano")),g.push("gehrisandro/tailwind-merge-php")),i.websocket&&g.push("cboden/ratchet"),i.mcp&&g.push("php-mcp/server"),i.prisma&&execSync("npm install -g prisma-client-php@latest",{stdio:"inherit"}),i.typescript&&!i.backendOnly&&m.push(npmPkg("vite"),npmPkg("fast-glob")),i.starterKit&&!a&&await setupStarterKit(u,i),await installNpmDependencies(u,m,!0),await installComposerDependencies(u,g),t||execSync("npx tsc --init",{stdio:"inherit"}),await createDirectoryStructure(u,i),i.prisma&&execSync("npx ppo init --prisma-php",{stdio:"inherit"}),i.swaggerDocs){const e=path.join(u,"src","app","swagger-docs"),s=path.join(e,"apis"),t=path.join(u,"public","assets"),n=path.join(t,"dist");fs.existsSync(e)&&fs.readdirSync(e).length>0&&(console.log("Removing existing swagger-docs directory..."),fs.rmSync(e,{recursive:!0,force:!0})),console.log(chalk.blue("Cloning swagger-docs repository...")),execSync(`git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${e}`,{stdio:"inherit"});const c=path.join(e,".git");fs.existsSync(c)&&fs.rmSync(c,{recursive:!0,force:!0}),fs.existsSync(t)||(console.log(chalk.blue("Creating public/assets directory...")),fs.mkdirSync(t,{recursive:!0}));const o=path.join(e,"dist");fs.existsSync(o)?(console.log(chalk.blue("Moving dist folder to public/assets/dist...")),fs.existsSync(n)&&fs.rmSync(n,{recursive:!0,force:!0}),fs.renameSync(o,n),console.log(chalk.green("✓ Moved dist to public/assets/dist"))):console.warn(chalk.yellow("Warning: dist folder not found in cloned repository")),fs.existsSync(s)?console.log(chalk.green("✓ APIs folder preserved in src/app/swagger-docs/apis")):console.warn(chalk.yellow("Warning: apis folder not found in cloned repository")),console.log(chalk.green("✓ Swagger docs setup complete"))}if(updateAnswer?.isUpdate){const e=[],s=[],t=e=>{try{const s=path.join(u,"composer.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!(!t.require||!t.require[e])}return!1}catch{return!1}},n=e=>{try{const s=path.join(u,"package.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!!(t.dependencies&&t.dependencies[e]||t.devDependencies&&t.devDependencies[e])}return!1}catch{return!1}};if(updateAnswer.backendOnly){nonBackendFiles.forEach(e=>{const s=path.join(u,"src","app",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["js","css"].forEach(e=>{const s=path.join(u,"src","app",e);fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.swaggerDocs){const s=path.join(u,"src","app","swagger-docs");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("swagger-docs was deleted successfully."));["swagger-config.ts"].forEach(e=>{const s=path.join(u,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))}),n("swagger-jsdoc")&&e.push("swagger-jsdoc"),n("@types/swagger-jsdoc")&&e.push("@types/swagger-jsdoc"),n("prompts")&&e.push("prompts"),n("@types/prompts")&&e.push("@types/prompts")}if(!updateAnswer.tailwindcss){["postcss.config.js"].forEach(e=>{const s=path.join(u,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["tailwindcss","postcss","postcss-cli","@tailwindcss/postcss","cssnano"].forEach(s=>{n(s)&&e.push(s)});const c="gehrisandro/tailwind-merge-php";t(c)&&s.push(c)}if(!updateAnswer.websocket){["restart-websocket.ts"].forEach(e=>{const s=path.join(u,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(u,"src","Lib","Websocket");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("Websocket folder was deleted successfully.")),t("cboden/ratchet")&&s.push("cboden/ratchet")}if(!updateAnswer.mcp){["restart-mcp.ts"].forEach(e=>{const s=path.join(u,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(u,"src","Lib","MCP");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("MCP folder was deleted successfully.")),t("php-mcp/server")&&s.push("php-mcp/server")}if(!updateAnswer.prisma){["prisma","@prisma/client","@prisma/internals","better-sqlite3","@prisma/adapter-better-sqlite3","mariadb","@prisma/adapter-mariadb","pg","@prisma/adapter-pg","@types/pg"].forEach(s=>{n(s)&&e.push(s)})}if(!updateAnswer.typescript||updateAnswer.backendOnly){["vite.config.ts"].forEach(e=>{const s=path.join(u,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const s=path.join(u,"ts");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("ts folder was deleted successfully."));const t=path.join(u,"settings","vite-plugins");fs.existsSync(t)&&(fs.rmSync(t,{recursive:!0,force:!0}),console.log("settings/vite-plugins folder was deleted successfully."));["vite","fast-glob"].forEach(s=>{n(s)&&e.push(s)})}const c=e=>Array.from(new Set(e)),o=c(e),r=c(s);o.length>0&&(console.log(`Uninstalling npm packages: ${o.join(", ")}`),await uninstallNpmDependencies(u,o,!0)),r.length>0&&(console.log(`Uninstalling composer packages: ${r.join(", ")}`),await uninstallComposerDependencies(u,r))}if(!a||!fs.existsSync(path.join(u,"prisma-php.json"))){const e=u.replace(/\\/g,"\\"),s=bsConfigUrls(e),t={projectName:i.projectName,projectRootPath:e,phpEnvironment:"XAMPP",phpRootPathExe:"C:\\xampp\\php\\php.exe",bsTarget:s.bsTarget,bsPathRewrite:s.bsPathRewrite,backendOnly:i.backendOnly,swaggerDocs:i.swaggerDocs,tailwindcss:i.tailwindcss,websocket:i.websocket,mcp:i.mcp,prisma:i.prisma,typescript:i.typescript,version:p,componentScanDirs:updateAnswer?.componentScanDirs??["src","vendor/tsnc/prisma-php/src"],excludeFiles:updateAnswer?.excludeFiles??[]};fs.writeFileSync(path.join(u,"prisma-php.json"),JSON.stringify(t,null,2),{flag:"w"})}execSync(updateAnswer?.isUpdate?"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar update":"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar install",{stdio:"inherit"}),console.log("\n=========================\n"),console.log(`${chalk.green("Success!")} Prisma PHP project successfully created in ${chalk.green(u.replace(/\\/g,"/"))}!`),console.log("\n=========================")}catch(e){console.error("Error while creating the project:",e),process.exit(1)}}main();
|
|
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"],STARTER_KITS={basic:{id:"basic",name:"Basic PHP Application",description:"Simple PHP backend with minimal dependencies",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!1,swaggerDocs:!1,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","src/app/layout.php","src/app/index.php"]},fullstack:{id:"fullstack",name:"Full-Stack Application",description:"Complete web application with frontend and backend",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/app/layout.php","src/app/index.php","public/js/main.js","src/app/globals.css"]},api:{id:"api",name:"REST API",description:"Backend API with database and documentation",features:{backendOnly:!0,tailwindcss:!1,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1},requiredFiles:["bootstrap.php",".htaccess"]},realtime:{id:"realtime",name:"Real-time Application",description:"Application with WebSocket support and MCP",features:{backendOnly:!1,tailwindcss:!0,websocket:!0,prisma:!0,swaggerDocs:!0,mcp:!0},requiredFiles:["bootstrap.php",".htaccess","postcss.config.js","src/lib/websocket","src/lib/mcp"]},ecommerce:{id:"ecommerce",name:"E-commerce Starter",description:"Full e-commerce application with cart, payments, and admin",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-ecommerce-starter",branch:"main"}},blog:{id:"blog",name:"Blog CMS",description:"Blog content management system",features:{backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!1,mcp:!1},requiredFiles:[],source:{type:"git",url:"https://github.com/your-org/prisma-php-blog-starter"}}};function bsConfigUrls(e){const s=e.indexOf("\\htdocs\\");if(-1===s)return console.error("Invalid PROJECT_ROOT_PATH. The path does not contain \\htdocs\\"),{bsTarget:"",bsPathRewrite:{}};const t=e.substring(0,s+8).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,"/"),r=n.replace(/\/\/+/g,"/");return{bsTarget:`${o}/`,bsPathRewrite:{"^/":`/${r.startsWith("/")?r.substring(1):r}/`}}}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/globals.css -o public/css/styles.css --watch","tailwind:build":"postcss src/app/globals.css -o public/css/styles.css"},c.push("tailwind")),s.typescript&&!s.backendOnly&&(n.scripts={...n.scripts,"ts:watch":"vite build --watch","ts:build":"vite build"},c.push("ts:watch")),s.websocket&&(n.scripts={...n.scripts,websocket:"tsx settings/restart-websocket.ts"},c.push("websocket")),s.mcp&&(n.scripts={...n.scripts,mcp:"tsx settings/restart-mcp.ts"},c.push("mcp")),s.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(" ")}`;let r=["browserSync:build"];s.tailwindcss&&r.unshift("tailwind:build"),s.typescript&&!s.backendOnly&&r.unshift("ts:build"),o.build=`npm-run-all ${r.join(" ")}`,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,"public","js","main.js");if(checkExcludeFiles(t))return;let n=fs.readFileSync(t,"utf8");n+='\nwindow.ws = new WebSocket("ws://localhost:8080");\n',fs.writeFileSync(t,n,"utf8")}function generateAuthSecret(){return randomBytes(33).toString("base64")}function generateHexEncodedKey(e=16){return randomBytes(e).toString("hex")}function copyRecursiveSync(e,s,t){const n=fs.existsSync(e),c=n&&fs.statSync(e);if(n&&c&&c.isDirectory()){const n=s.toLowerCase();if(!t.websocket&&n.includes("src\\lib\\websocket"))return;if(!t.mcp&&n.includes("src\\lib\\mcp"))return;if((!t.typescript||t.backendOnly)&&(n.endsWith("\\ts")||n.includes("\\ts\\")))return;if((!t.typescript||t.backendOnly)&&(n.endsWith("\\vite-plugins")||n.includes("\\vite-plugins\\")||n.includes("\\vite-plugins")))return;if(t.backendOnly&&n.includes("public\\js")||t.backendOnly&&n.includes("public\\css")||t.backendOnly&&n.includes("public\\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("globals.css")||s.includes("styles.css")))return;if(!t.websocket&&s.includes("restart-websocket.ts"))return;if(!t.mcp&&s.includes("restart-mcp.ts"))return;if(t.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="/css/index.css" rel="stylesheet" />'),n+='\n <script type="module" src="/js/main.js"><\/script>');let c="";s.backendOnly||(c=s.tailwindcss?` <link href="/css/styles.css" rel="stylesheet" /> ${n}`:n),e=e.replace("</head>",`${c}\n</head>`),fs.writeFileSync(t,e,{flag:"w"})}catch(e){console.error(chalk.red("Error modifying layout.php:"),e)}}async function createOrUpdateEnvFile(e,s){const t=path.join(e,".env");fs.existsSync(t)&&checkExcludeFiles(t)||fs.writeFileSync(t,s,{flag:"w"})}function checkExcludeFiles(e){if(!updateAnswer?.isUpdate)return!1;const s=e.replace(/\\/g,"/");return!!updateAnswer?.excludeFilePath?.includes(s)||!!updateAnswer?.excludeFiles&&updateAnswer.excludeFiles.some(e=>{const t=e.replace(/\\/g,"/");return s.endsWith("/"+t)||s===t})}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"},{src:"/CLAUDE.md",dest:"/CLAUDE.md"},{src:"/AGENTS.md",dest:"/AGENTS.md"}];s.tailwindcss&&t.push({src:"/postcss.config.js",dest:"/postcss.config.js"}),s.typescript&&!s.backendOnly&&t.push({src:"/vite.config.ts",dest:"/vite.config.ts"});const n=[{src:"/settings",dest:"/settings"},{src:"/src",dest:"/src"},{src:"/public",dest:"/public"}];s.typescript&&!s.backendOnly&&n.push({src:"/ts",dest:"/ts"}),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(),r=`# Authentication secret key for JWT or session encryption.\nAUTH_SECRET="${c}"\n# Name of the authentication cookie.\nAUTH_COOKIE_NAME="${generateHexEncodedKey(8)}"\n\n# Show errors in the browser (development only). Set to false in production.\nSHOW_ERRORS="true"\n\n# Application timezone (default: UTC)\nAPP_TIMEZONE="UTC"\n\n# Application environment (development or production)\nAPP_ENV="development"\n\n# Enable or disable application cache (default: false)\nCACHE_ENABLED="false"\n# Cache time-to-live in seconds (default: 600)\nCACHE_TTL="600"\n\n# Local storage key for browser storage (auto-generated if not set).\n# Spaces will be replaced with underscores and converted to lowercase.\nLOCALSTORE_KEY="${o}"\n\n# Secret key for encrypting function calls.\nFUNCTION_CALL_SECRET="${generateHexEncodedKey(32)}"\n\n# Single or multiple origins (CSV or JSON array)\nCORS_ALLOWED_ORIGINS=[]\n\n# If you need cookies/Authorization across origins, keep this true\nCORS_ALLOW_CREDENTIALS="true"\n\n# Optional tuning\nCORS_ALLOWED_METHODS="GET,POST,PUT,PATCH,DELETE,OPTIONS"\nCORS_ALLOWED_HEADERS="Content-Type,Authorization,X-Requested-With"\nCORS_EXPOSE_HEADERS=""\nCORS_MAX_AGE="86400"\n\n# Rate Limiting\nRATE_LIMIT_DEFAULT="200 per minute"\nRATE_LIMIT_RPC="60 per minute"\nRATE_LIMIT_AUTH="60 per minute"`;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${r}`;await createOrUpdateEnvFile(e,s)}else await createOrUpdateEnvFile(e,r)}async function getAnswer(e={},s=!1){if(s)return{projectName:e.projectName??"my-app",backendOnly:e.backendOnly??!1,swaggerDocs:e.swaggerDocs??!1,tailwindcss:e.tailwindcss??!1,typescript:e.typescript??!1,websocket:e.websocket??!1,mcp:e.mcp??!1,prisma:e.prisma??!1};if(e.starterKit){const s=e.starterKit;let t=null;if(STARTER_KITS[s]&&(t=STARTER_KITS[s]),t){const n={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:t.features.backendOnly??!1,tailwindcss:t.features.tailwindcss??!1,websocket:t.features.websocket??!1,prisma:t.features.prisma??!1,swaggerDocs:t.features.swaggerDocs??!1,mcp:t.features.mcp??!1,typescript:t.features.typescript??!1},c=process.argv.slice(2);return c.includes("--backend-only")&&(n.backendOnly=!0),c.includes("--swagger-docs")&&(n.swaggerDocs=!0),c.includes("--tailwindcss")&&(n.tailwindcss=!0),c.includes("--websocket")&&(n.websocket=!0),c.includes("--mcp")&&(n.mcp=!0),c.includes("--prisma")&&(n.prisma=!0),c.includes("--typescript")&&(n.typescript=!0),n}if(e.starterKitSource){const t={projectName:e.projectName??"my-app",starterKit:s,starterKitSource:e.starterKitSource,backendOnly:!1,tailwindcss:!0,websocket:!1,prisma:!0,swaggerDocs:!0,mcp:!1,typescript:!1},n=process.argv.slice(2);return n.includes("--backend-only")&&(t.backendOnly=!0),n.includes("--swagger-docs")&&(t.swaggerDocs=!0),n.includes("--tailwindcss")&&(t.tailwindcss=!0),n.includes("--websocket")&&(t.websocket=!0),n.includes("--mcp")&&(t.mcp=!0),n.includes("--prisma")&&(t.prisma=!0),n.includes("--typescript")&&(t.typescript=!0),t}}const t=[];e.projectName||t.push({type:"text",name:"projectName",message:"What is your project named?",initial:"my-app"}),e.backendOnly||updateAnswer?.isUpdate||t.push({type:"toggle",name:"backendOnly",message:`Would you like to create a ${chalk.blue("backend-only project")}?`,initial:!1,active:"Yes",inactive:"No"});const n=()=>{console.warn(chalk.red("Operation cancelled by the user.")),process.exit(0)},c=await prompts(t,{onCancel:n}),o=[];c.backendOnly??e.backendOnly??!1?(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"})):(e.swaggerDocs||o.push({type:"toggle",name:"swaggerDocs",message:`Would you like to use ${chalk.blue("Swagger Docs")}?`,initial:!1,active:"Yes",inactive:"No"}),e.tailwindcss||o.push({type:"toggle",name:"tailwindcss",message:`Would you like to use ${chalk.blue("Tailwind CSS")}?`,initial:!1,active:"Yes",inactive:"No"}),e.typescript||o.push({type:"toggle",name:"typescript",message:`Would you like to use ${chalk.blue("TypeScript")}?`,initial:!1,active:"Yes",inactive:"No"}),e.websocket||o.push({type:"toggle",name:"websocket",message:`Would you like to use ${chalk.blue("Websocket")}?`,initial:!1,active:"Yes",inactive:"No"}),e.mcp||o.push({type:"toggle",name:"mcp",message:`Would you like to use ${chalk.blue("MCP (Model Context Protocol)")}?`,initial:!1,active:"Yes",inactive:"No"}),e.prisma||o.push({type:"toggle",name:"prisma",message:`Would you like to use ${chalk.blue("Prisma ORM")}?`,initial:!1,active:"Yes",inactive:"No"}));const r=await prompts(o,{onCancel:n});return{projectName:c.projectName?String(c.projectName).trim().replace(/ /g,"-"):e.projectName??"my-app",backendOnly:c.backendOnly??e.backendOnly??!1,swaggerDocs:r.swaggerDocs??e.swaggerDocs??!1,tailwindcss:r.tailwindcss??e.tailwindcss??!1,typescript:r.typescript??e.typescript??!1,websocket:r.websocket??e.websocket??!1,mcp:r.mcp??e.mcp??!1,prisma:r.prisma??e.prisma??!1}}async function uninstallNpmDependencies(e,s,t=!1){console.log("Uninstalling Node dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const n=`npm uninstall ${t?"--save-dev":"--save"} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}async function uninstallComposerDependencies(e,s){console.log("Uninstalling Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(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]:(console.error(`Package ${e} is not installed`),null)}catch(e){return console.error(e instanceof Error?e.message:String(e)),null}}async function installNpmDependencies(e,s,t=!1){fs.existsSync(path.join(e,"package.json"))?console.log("Updating existing Node.js project..."):console.log("Initializing new Node.js project..."),fs.existsSync(path.join(e,"package.json"))||execSync("npm init -y",{stdio:"inherit",cwd:e}),console.log((t?"Installing development dependencies":"Installing dependencies")+":"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));const n=`npm install ${t?"--save-dev":""} ${s.join(" ")}`;execSync(n,{stdio:"inherit",cwd:e})}function getComposerCmd(){try{return execSync("composer --version",{stdio:"ignore"}),console.log("✓ Using global composer command"),{cmd:"composer",baseArgs:[]}}catch{const e="C:\\xampp\\php\\php.exe",s="C:\\ProgramData\\ComposerSetup\\bin\\composer.phar";if(!fs.existsSync(e))throw console.error(`✗ PHP not found at ${e}`),new Error(`PHP executable not found at ${e}`);if(!fs.existsSync(s))throw console.error(`✗ Composer not found at ${s}`),new Error(`Composer phar not found at ${s}`);return console.log("✓ Using XAMPP PHP with Composer phar"),{cmd:e,baseArgs:[s]}}}export async function installComposerDependencies(e,s){const{cmd:t,baseArgs:n}=getComposerCmd(),c=path.join(e,"composer.json"),o=fs.existsSync(c);if(console.log(chalk.green("Composer project initialization: "+(o?"Updating existing project…":"Setting up new project…"))),fs.existsSync(e)||(console.log(`Creating base directory: ${e}`),fs.mkdirSync(e,{recursive:!0})),!o){const s=[...n,"init","--no-interaction","--name","tsnc/prisma-php-app","--require","php:^8.2","--type","project","--version","1.0.0"];console.log("Attempting composer init...");const o=spawnSync(t,s,{cwd:e,stdio:["ignore","pipe","pipe"],encoding:"utf8"}),r=fs.existsSync(c);if(0===o.status&&r)console.log("✓ Composer init successful and composer.json created");else{0!==o.status?(console.log(`Composer init failed with status ${o.status}`),o.stderr&&console.log(`Stderr: ${o.stderr}`)):console.log("Composer init reported success but didn't create composer.json"),console.log("Creating composer.json manually...");const s={name:"tsnc/prisma-php-app",type:"project",version:"1.0.0",require:{php:"^8.2"},autoload:{"psr-4":{"":"src/"}}};try{const t=path.resolve(e,"composer.json");if(console.log(`Writing composer.json to: ${t}`),fs.writeFileSync(t,JSON.stringify(s,null,2),{encoding:"utf8"}),!fs.existsSync(t))throw new Error("File creation appeared to succeed but file doesn't exist");console.log("✓ Successfully created composer.json")}catch(s){if(console.error("✗ Failed to create composer.json:",s),console.error(`Base directory: ${e}`),console.error(`Absolute base directory: ${path.resolve(e)}`),console.error(`Target file path: ${c}`),console.error(`Absolute target file path: ${path.resolve(c)}`),console.error(`Current working directory: ${process.cwd()}`),console.error(`Base directory exists: ${fs.existsSync(e)}`),fs.existsSync(e))try{const s=fs.statSync(e);console.error(`Base directory is writable: ${s.isDirectory()}`)}catch(e){console.error(`Cannot stat base directory: ${e}`)}throw new Error(`Cannot create composer.json: ${s}`)}}}const r=path.resolve(e,"composer.json");if(!fs.existsSync(r))throw console.error(`✗ composer.json still not found at ${r}`),console.error("Directory contents:",fs.readdirSync(e)),new Error("Failed to create composer.json - file does not exist after all attempts");let i;try{const e=fs.readFileSync(r,"utf8");console.log("✓ Successfully read composer.json"),i=JSON.parse(e)}catch(e){throw console.error("✗ Failed to read/parse composer.json:",e),new Error(`Cannot read composer.json: ${e}`)}i.autoload??={},i.autoload["psr-4"]??={},i.autoload["psr-4"][""]??="src/";try{fs.writeFileSync(r,JSON.stringify(i,null,2)),console.log("✓ Updated composer.json with PSR-4 autoload")}catch(e){throw console.error("✗ Failed to update composer.json:",e),e}if(s.length){console.log("Installing Composer dependencies:"),s.forEach(e=>console.log(`- ${chalk.blue(e)}`));try{const c=`${t} ${[...n,"require","--no-interaction","-W",...s].join(" ")}`;execSync(c,{stdio:"inherit",cwd:e,env:{...process.env}}),console.log("✓ Composer dependencies installed")}catch(e){throw console.error("✗ Failed to install composer dependencies:",e),e}}if(o)try{execSync(`${t} ${[...n,"update","--lock","--no-install","--no-interaction"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer lock updated")}catch(e){throw console.error("✗ Failed to update composer lock:",e),e}try{execSync(`${t} ${[...n,"dump-autoload","--quiet"].join(" ")}`,{stdio:"inherit",cwd:e}),console.log("✓ Composer autoloader regenerated")}catch(e){throw console.error("✗ Failed to regenerate autoloader:",e),e}}const npmPinnedVersions={"@tailwindcss/postcss":"4.2.2","@types/browser-sync":"2.29.1","@types/node":"25.5.2","@types/prompts":"2.4.9","browser-sync":"3.0.4",chalk:"5.6.2","chokidar-cli":"3.0.0",cssnano:"7.1.4","http-proxy-middleware":"3.0.5","npm-run-all":"4.1.5","php-parser":"3.5.1",postcss:"8.5.9","postcss-cli":"11.0.1",prompts:"2.4.2",tailwindcss:"4.2.2",tsx:"4.21.0",typescript:"6.0.2",vite:"7.3.0","fast-glob":"3.3.3","prisma-php":"0.0.2"};function npmPkg(e){return npmPinnedVersions[e]?`${e}@${npmPinnedVersions[e]}`:e}const composerPinnedVersions={"vlucas/phpdotenv":"5.6.3","firebase/php-jwt":"7.0.5","phpmailer/phpmailer":"7.0.1","guzzlehttp/guzzle":"7.10.0","symfony/uid":"7.4.8","brick/math":"0.17.0","cboden/ratchet":"0.4.4","tsnc/prisma-php":"2.0.0","php-mcp/server":"3.3.0","gehrisandro/tailwind-merge-php":"1.2.0"};function composerPkg(e){return composerPinnedVersions[e]?`${e}:${composerPinnedVersions[e]}`:e}function removeDirectorySafe(e){if(fs.existsSync(e))try{return void fs.rmSync(e,{recursive:!0,force:!0,maxRetries:5,retryDelay:250})}catch(s){const t=s;if("win32"===globalThis.process?.platform&&("EPERM"===t.code||"EACCES"===t.code)){try{spawnSync("cmd",["/c","attrib","-R","-H","-S","/S","/D",`${e}\\*`],{stdio:"ignore"})}catch{}return void spawnSync("cmd",["/c","rd","/s","/q",e],{stdio:"ignore"})}throw s}}async function setupStarterKit(e,s){if(!s.starterKit)return;let t=null;if(STARTER_KITS[s.starterKit]?t=STARTER_KITS[s.starterKit]:s.starterKitSource&&(t={id:s.starterKit,name:`Custom Starter Kit (${s.starterKit})`,description:"Custom starter kit from external source",features:{},requiredFiles:[],source:{type:"git",url:s.starterKitSource}}),t){if(console.log(chalk.green(`Setting up ${t.name}...`)),t.source)try{const n=t.source.branch?`git clone -b ${t.source.branch} --depth 1 ${t.source.url} "${e}"`:`git clone --depth 1 ${t.source.url} "${e}"`;execSync(n,{stdio:"inherit"});removeDirectorySafe(path.join(e,".git")),console.log(chalk.blue("Starter kit cloned successfully!"));const c=path.join(e,"prisma-php.json");if(fs.existsSync(c))try{const t=JSON.parse(fs.readFileSync(c,"utf8")),n=e,o=bsConfigUrls(n);t.projectName=s.projectName,t.projectRootPath=n,t.bsTarget=o.bsTarget,t.bsPathRewrite=o.bsPathRewrite;const r=await fetchPackageVersion("create-prisma-php-app");t.version=t.version||r,fs.writeFileSync(c,JSON.stringify(t,null,2)),console.log(chalk.green("Updated prisma-php.json with new project details"))}catch(e){console.warn(chalk.yellow("Failed to update prisma-php.json, will create new one"))}}catch(e){throw console.error(chalk.red(`Failed to setup starter kit: ${e}`)),e}t.customSetup&&await t.customSetup(e,s),console.log(chalk.green(`✓ ${t.name} setup complete!`))}else console.warn(chalk.yellow(`Starter kit '${s.starterKit}' not found. Skipping...`))}function showStarterKits(){console.log(chalk.blue("\n🚀 Available Starter Kits:\n")),Object.values(STARTER_KITS).forEach(e=>{const s=e.source?" (Custom)":" (Built-in)";console.log(chalk.green(` ${e.id}${chalk.gray(s)}`)),console.log(` ${e.name}`),console.log(chalk.gray(` ${e.description}`)),e.source&&console.log(chalk.cyan(` Source: ${e.source.url}`));const t=Object.entries(e.features).filter(([,e])=>!0===e).map(([e])=>e).join(", ");t&&console.log(chalk.magenta(` Features: ${t}`)),console.log()}),console.log(chalk.yellow("Usage:")),console.log(" npx create-prisma-php-app my-project --starter-kit=basic"),console.log(" npx create-prisma-php-app my-project --starter-kit=custom --starter-kit-source=https://github.com/user/repo"),console.log()}async function main(){try{const e=process.argv.slice(2),s=e.includes("-y");let t=e[0];const n=e.find(e=>e.startsWith("--starter-kit=")),c=n?.split("=")[1],o=e.find(e=>e.startsWith("--starter-kit-source=")),r=o?.split("=")[1];if(e.includes("--list-starter-kits"))return void showStarterKits();let i=null,a=!1;if(t){const n=process.cwd(),o=path.join(n,"prisma-php.json");if(c&&r){a=!0;const n={projectName:t,starterKit:c,starterKitSource:r,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};i=await getAnswer(n,s)}else if(fs.existsSync(o)){const c=readJsonFile(o);let r=[];c.excludeFiles?.map(e=>{const s=path.join(n,e);fs.existsSync(s)&&r.push(s.replace(/\\/g,"/"))}),updateAnswer={projectName:t,backendOnly:c.backendOnly,swaggerDocs:c.swaggerDocs,tailwindcss:c.tailwindcss,websocket:c.websocket,mcp:c.mcp,prisma:c.prisma,typescript:c.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:r??[],filePath:n};const a={projectName:t,backendOnly:e.includes("--backend-only")||c.backendOnly,swaggerDocs:e.includes("--swagger-docs")||c.swaggerDocs,tailwindcss:e.includes("--tailwindcss")||c.tailwindcss,typescript:e.includes("--typescript")||c.typescript,websocket:e.includes("--websocket")||c.websocket,prisma:e.includes("--prisma")||c.prisma,mcp:e.includes("--mcp")||c.mcp};i=await getAnswer(a,s),null!==i&&(updateAnswer={projectName:t,backendOnly:i.backendOnly,swaggerDocs:i.swaggerDocs,tailwindcss:i.tailwindcss,websocket:i.websocket,mcp:i.mcp,prisma:i.prisma,typescript:i.typescript,isUpdate:!0,componentScanDirs:c.componentScanDirs??[],excludeFiles:c.excludeFiles??[],excludeFilePath:r??[],filePath:n})}else{const n={projectName:t,starterKit:c,starterKitSource:r,backendOnly:e.includes("--backend-only"),swaggerDocs:e.includes("--swagger-docs"),tailwindcss:e.includes("--tailwindcss"),typescript:e.includes("--typescript"),websocket:e.includes("--websocket"),mcp:e.includes("--mcp"),prisma:e.includes("--prisma")};i=await getAnswer(n,s)}if(null===i)return void console.log(chalk.red("Installation cancelled."))}else i=await getAnswer({},s);if(null===i)return void console.warn(chalk.red("Installation cancelled."));const p=await fetchPackageVersion("create-prisma-php-app"),l=getInstalledPackageVersion("create-prisma-php-app");l?-1===compareVersions(l,p)&&(execSync("npm uninstall -g create-prisma-php-app",{stdio:"inherit"}),execSync("npm install -g create-prisma-php-app",{stdio:"inherit"})):execSync("npm install -g create-prisma-php-app",{stdio:"inherit"});const d=process.cwd();let u;if(t)if(a){const s=path.join(d,t);fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),u=s,await setupStarterKit(u,i),process.chdir(u);const n=path.join(u,"prisma-php.json");if(fs.existsSync(n)){const s=JSON.parse(fs.readFileSync(n,"utf8"));e.includes("--backend-only")&&(s.backendOnly=!0),e.includes("--swagger-docs")&&(s.swaggerDocs=!0),e.includes("--tailwindcss")&&(s.tailwindcss=!0),e.includes("--typescript")&&(s.typescript=!0),e.includes("--websocket")&&(s.websocket=!0),e.includes("--mcp")&&(s.mcp=!0),e.includes("--prisma")&&(s.prisma=!0),i={...i,backendOnly:s.backendOnly,swaggerDocs:s.swaggerDocs,tailwindcss:s.tailwindcss,typescript:s.typescript,websocket:s.websocket,mcp:s.mcp,prisma:s.prisma};let t=[];s.excludeFiles?.map(e=>{const s=path.join(u,e);fs.existsSync(s)&&t.push(s.replace(/\\/g,"/"))}),updateAnswer={...i,isUpdate:!0,componentScanDirs:s.componentScanDirs??[],excludeFiles:s.excludeFiles??[],excludeFilePath:t??[],filePath:u}}}else{const e=path.join(d,"prisma-php.json"),s=path.join(d,t),n=path.join(s,"prisma-php.json");fs.existsSync(e)?u=d:fs.existsSync(s)&&fs.existsSync(n)?(u=s,process.chdir(s)):(fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0}),u=s,process.chdir(s))}else fs.mkdirSync(i.projectName,{recursive:!0}),u=path.join(d,i.projectName),process.chdir(i.projectName);let m=[npmPkg("typescript"),npmPkg("@types/node"),npmPkg("tsx"),npmPkg("http-proxy-middleware"),npmPkg("chalk"),npmPkg("npm-run-all"),npmPkg("browser-sync"),npmPkg("@types/browser-sync"),npmPkg("php-parser"),npmPkg("prisma-php")],g=[composerPkg("vlucas/phpdotenv"),composerPkg("firebase/php-jwt"),composerPkg("phpmailer/phpmailer"),composerPkg("guzzlehttp/guzzle"),composerPkg("symfony/uid"),composerPkg("brick/math"),composerPkg("tsnc/prisma-php")];if(i.swaggerDocs&&m.push(npmPkg("swagger-jsdoc"),npmPkg("@types/swagger-jsdoc")),i.swaggerDocs&&i.prisma&&m.push(npmPkg("prompts"),npmPkg("@types/prompts")),i.tailwindcss&&(m.push(npmPkg("tailwindcss"),npmPkg("postcss"),npmPkg("postcss-cli"),npmPkg("@tailwindcss/postcss"),npmPkg("cssnano")),g.push("gehrisandro/tailwind-merge-php")),i.websocket&&g.push("cboden/ratchet"),i.mcp&&g.push("php-mcp/server"),i.prisma&&execSync("npm install -g prisma-client-php@latest",{stdio:"inherit"}),i.typescript&&!i.backendOnly&&m.push(npmPkg("vite"),npmPkg("fast-glob")),i.starterKit&&!a&&await setupStarterKit(u,i),await installNpmDependencies(u,m,!0),await installComposerDependencies(u,g),t||execSync("npx tsc --init",{stdio:"inherit"}),await createDirectoryStructure(u,i),i.prisma&&execSync("npx ppo init --prisma-php",{stdio:"inherit"}),i.swaggerDocs){const e=path.join(u,"src","app","swagger-docs"),s=path.join(e,"apis"),t=path.join(u,"public","assets"),n=path.join(t,"dist");fs.existsSync(e)&&fs.readdirSync(e).length>0&&(console.log("Removing existing swagger-docs directory..."),fs.rmSync(e,{recursive:!0,force:!0})),console.log(chalk.blue("Cloning swagger-docs repository...")),execSync(`git clone https://github.com/TheSteelNinjaCode/prisma-php-swagger-docs.git ${e}`,{stdio:"inherit"});const c=path.join(e,".git");fs.existsSync(c)&&fs.rmSync(c,{recursive:!0,force:!0}),fs.existsSync(t)||(console.log(chalk.blue("Creating public/assets directory...")),fs.mkdirSync(t,{recursive:!0}));const o=path.join(e,"dist");fs.existsSync(o)?(console.log(chalk.blue("Moving dist folder to public/assets/dist...")),fs.existsSync(n)&&fs.rmSync(n,{recursive:!0,force:!0}),fs.renameSync(o,n),console.log(chalk.green("✓ Moved dist to public/assets/dist"))):console.warn(chalk.yellow("Warning: dist folder not found in cloned repository")),fs.existsSync(s)?console.log(chalk.green("✓ APIs folder preserved in src/app/swagger-docs/apis")):console.warn(chalk.yellow("Warning: apis folder not found in cloned repository")),console.log(chalk.green("✓ Swagger docs setup complete"))}if(updateAnswer?.isUpdate){const e=[],s=[],t=e=>{try{const s=path.join(u,"composer.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!(!t.require||!t.require[e])}return!1}catch{return!1}},n=e=>{try{const s=path.join(u,"package.json");if(fs.existsSync(s)){const t=JSON.parse(fs.readFileSync(s,"utf8"));return!!(t.dependencies&&t.dependencies[e]||t.devDependencies&&t.devDependencies[e])}return!1}catch{return!1}};if(updateAnswer.backendOnly){nonBackendFiles.forEach(e=>{const s=path.join(u,"src","app",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["js","css"].forEach(e=>{const s=path.join(u,"src","app",e);fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log(`${e} was deleted successfully.`))})}if(!updateAnswer.swaggerDocs){const s=path.join(u,"src","app","swagger-docs");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("swagger-docs was deleted successfully."));["swagger-config.ts"].forEach(e=>{const s=path.join(u,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))}),n("swagger-jsdoc")&&e.push("swagger-jsdoc"),n("@types/swagger-jsdoc")&&e.push("@types/swagger-jsdoc"),n("prompts")&&e.push("prompts"),n("@types/prompts")&&e.push("@types/prompts")}if(!updateAnswer.tailwindcss){["postcss.config.js"].forEach(e=>{const s=path.join(u,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});["tailwindcss","postcss","postcss-cli","@tailwindcss/postcss","cssnano"].forEach(s=>{n(s)&&e.push(s)});const c="gehrisandro/tailwind-merge-php";t(c)&&s.push(c)}if(!updateAnswer.websocket){["restart-websocket.ts"].forEach(e=>{const s=path.join(u,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(u,"src","Lib","Websocket");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("Websocket folder was deleted successfully.")),t("cboden/ratchet")&&s.push("cboden/ratchet")}if(!updateAnswer.mcp){["restart-mcp.ts"].forEach(e=>{const s=path.join(u,"settings",e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const e=path.join(u,"src","Lib","MCP");fs.existsSync(e)&&(fs.rmSync(e,{recursive:!0,force:!0}),console.log("MCP folder was deleted successfully.")),t("php-mcp/server")&&s.push("php-mcp/server")}if(!updateAnswer.prisma){["prisma","@prisma/client","@prisma/internals","better-sqlite3","@prisma/adapter-better-sqlite3","mariadb","@prisma/adapter-mariadb","pg","@prisma/adapter-pg","@types/pg"].forEach(s=>{n(s)&&e.push(s)})}if(!updateAnswer.typescript||updateAnswer.backendOnly){["vite.config.ts"].forEach(e=>{const s=path.join(u,e);fs.existsSync(s)&&(fs.unlinkSync(s),console.log(`${e} was deleted successfully.`))});const s=path.join(u,"ts");fs.existsSync(s)&&(fs.rmSync(s,{recursive:!0,force:!0}),console.log("ts folder was deleted successfully."));const t=path.join(u,"settings","vite-plugins");fs.existsSync(t)&&(fs.rmSync(t,{recursive:!0,force:!0}),console.log("settings/vite-plugins folder was deleted successfully."));["vite","fast-glob"].forEach(s=>{n(s)&&e.push(s)})}const c=e=>Array.from(new Set(e)),o=c(e),r=c(s);o.length>0&&(console.log(`Uninstalling npm packages: ${o.join(", ")}`),await uninstallNpmDependencies(u,o,!0)),r.length>0&&(console.log(`Uninstalling composer packages: ${r.join(", ")}`),await uninstallComposerDependencies(u,r))}if(!a||!fs.existsSync(path.join(u,"prisma-php.json"))){const e=u.replace(/\\/g,"\\"),s=bsConfigUrls(e),t={projectName:i.projectName,projectRootPath:e,phpEnvironment:"XAMPP",phpRootPathExe:"C:\\xampp\\php\\php.exe",bsTarget:s.bsTarget,bsPathRewrite:s.bsPathRewrite,backendOnly:i.backendOnly,swaggerDocs:i.swaggerDocs,tailwindcss:i.tailwindcss,websocket:i.websocket,mcp:i.mcp,prisma:i.prisma,typescript:i.typescript,version:p,componentScanDirs:updateAnswer?.componentScanDirs??["src","vendor/tsnc/prisma-php/src"],excludeFiles:updateAnswer?.excludeFiles??[]};fs.writeFileSync(path.join(u,"prisma-php.json"),JSON.stringify(t,null,2),{flag:"w"})}execSync(updateAnswer?.isUpdate?"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar update":"C:\\xampp\\php\\php.exe C:\\ProgramData\\ComposerSetup\\bin\\composer.phar install",{stdio:"inherit"}),console.log("\n=========================\n"),console.log(`${chalk.green("Success!")} Prisma PHP project successfully created in ${chalk.green(u.replace(/\\/g,"/"))}!`),console.log("\n=========================")}catch(e){console.error("Error while creating the project:",e),process.exit(1)}}main();
|