phpxui 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -1,56 +1,57 @@
1
- # **phpxui‑cli** — Instant PHPXUI Component Generator 🚀
1
+ # **phpxui‑cli** — Instant PHPXUI Component Generator 🚀
2
2
 
3
- > **Generate fully‑typed PHPXUI components for Prisma PHP right from the terminal.**
4
- > ⚡ **Single component**  `npx phpxui add Alert`   |   🌌 **Whole library**  `npx phpxui add --all`
3
+ > **Generate fully‑typed PHPXUI components for Prisma PHP right from the terminal.**
4
+ > ⚡ **Add one** `npx phpxui add Alert` | 🌌 **Add all** `npx phpxui add --all` | 🔁 **Update installed** → `npx phpxui update`
5
5
 
6
6
  ---
7
7
 
8
- ## ✨ Features
8
+ ## ✨ Features
9
9
 
10
- | Feature | Details |
11
- | --------------------- | ------------------------------------------------------------------------------------------------------------ |
12
- | **Bulk install** | `--all` downloads every component in one shot. |
13
- | **Ready‑to‑use code** | Each file already contains the `$class` merge logic and `{$attributes}` placeholder for **Wave** reactivity. |
14
- | **Clean paths** | Files are written under `src/Lib/PHPXUI/FancyName.php` with OS‑agnostic separators. |
15
- | **Friendly output** | Clear green / red summary with relative paths only. |
16
- | **Automatic icons** | Core **PPIcons** (`x`, `chevron‑down`, `chevron‑right`) are installed on the very first run. |
10
+ | Feature | Details |
11
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
12
+ | **Bulk install** | `--all` downloads every component in one shot. |
13
+ | **Update in place** | `update` scans your `outputDir` and re‑downloads every installed component (overwrite). |
14
+ | **Ready‑to‑use code** | Each file already contains the `$class` merge logic and `{$attributes}` placeholder for **Wave** reactivity. |
15
+ | **Clean paths** | Files are written under `src/Lib/PHPXUI/FancyName.php` with OS‑agnostic separators. |
16
+ | **Friendly output** | Clear green / red summary with relative paths only. |
17
+ | **PPIcons bootstrap** | `add` and `update` install the required baseline `ppicons` set automatically, and still install any extra icons detected in generated files. |
18
+ | **Tailwind bootstrap** | Ensures `tw-animate-css` and (on first run / missing file) writes `src/app/globals.css` for a sensible baseline. |
19
+ | **AI project context** | Refreshes the `manifest` block inside `phpxui.json` and managed `AGENTS.md` / `.github/copilot-instructions.md` blocks after changes. |
20
+
21
+ `phpxui.json` is the CLI's own config file. In a Prisma PHP app, the framework config remains the root `prisma-php.json` file.
17
22
 
18
23
  ---
19
24
 
20
- ## 📦 Installation
25
+ ## 📦 Installation
21
26
 
22
27
  ```bash
23
28
  # Global
24
29
  npm install -g phpxui
25
30
 
26
- # Or as a dev‑dependency
31
+ # Or as a dev‑dependency
27
32
  npm install -D phpxui
28
33
  ```
29
34
 
30
- > Requires **Node 18+** and a Prisma PHP project (PHP 8.2+).
35
+ > Requires **Node 18+** and a Prisma PHP project (PHP 8.2+).
31
36
 
32
37
  ---
33
38
 
34
- ## 🚀 Quick Start
39
+ ## 🚀 Quick Start
35
40
 
36
41
  ```bash
37
- # Add a single component
42
+ # Add a single component
38
43
  npx phpxui add Alert
39
44
 
40
- # Add multiple components at once
45
+ # Add multiple components at once
41
46
  npx phpxui add Alert Dialog Badge
42
47
 
43
- # Add the entire component set
48
+ # Add the entire component set
44
49
  npx phpxui add --all
45
50
  ```
46
51
 
47
52
  CLI output example:
48
53
 
49
54
  ```bash
50
- 📦 Installing ppicons CLI…
51
- ✨ Installing default icons: x chevron-down chevron-right
52
- ✔ Icons installed in src/Lib/PPIcons
53
-
54
55
  ✔ Alert → src/Lib/PHPXUI/Alert.php
55
56
  ✔ Dialog → src/Lib/PHPXUI/Dialog.php
56
57
  ✔ Badge → src/Lib/PHPXUI/Badge.php
@@ -68,11 +69,13 @@ class Alert extends PHPX
68
69
  {
69
70
  public function render(): string
70
71
  {
71
- $attributes = $this->getAttributes();
72
72
  $class = $this->getMergeClasses();
73
+ $attributes = $this->getAttributes([
74
+ 'class' => $class,
75
+ ]);
73
76
 
74
77
  return <<<HTML
75
- <div {$attributes} class="alert {$class}">
78
+ <div {$attributes}>
76
79
  {$this->children}
77
80
  </div>
78
81
  HTML;
@@ -82,19 +85,108 @@ class Alert extends PHPX
82
85
 
83
86
  ---
84
87
 
85
- ## 🔧 CLI Options
88
+ ## 🔁 Updating Components
89
+
90
+ ### Update everything you already installed
91
+
92
+ ```bash
93
+ npx phpxui update
94
+ ```
95
+
96
+ What it does:
97
+
98
+ - Reads your `phpxui.json` (creates it if missing)
99
+ - Resolves your `outputDir` (default: `src/Lib/PHPXUI`)
100
+ - Scans that folder for `*.php` files (e.g. `Alert.php`, `Dialog.php`)
101
+ - Re-downloads each matching component from the PHPXUI catalogue
102
+ - **Overwrites files automatically** (no `--force` required)
103
+
104
+ If you have no generated components yet, it will report:
105
+
106
+ - `⚠ No components found to update.`
107
+
108
+ ### Update a single component (targeted)
109
+
110
+ If you only want to refresh one component, use `add` with `--force`:
111
+
112
+ ```bash
113
+ npx phpxui add Alert --force
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 🔧 CLI Usage and Options
119
+
120
+ ```bash
121
+ phpxui <command> [--all] [--force] <component…>
122
+ ```
123
+
124
+ ### Commands
125
+
126
+ | Command | Purpose |
127
+ | -------- | ---------------------------------------------------------------------------- |
128
+ | `add` | Generate one or more components by name, or the full catalogue with `--all`. |
129
+ | `update` | Update **all installed** components found in `outputDir` (overwrites). |
130
+
131
+ ### Flags / Arguments
132
+
133
+ | Flag / Argument | Description |
134
+ | --------------- | ---------------------------------------------------------------------------- |
135
+ | `<component …>` | One or more component names separated by space (or comma). Applies to `add`. |
136
+ | `--all` | Download the full catalogue in one request. Applies to `add`. |
137
+ | `--force` | Overwrite existing files. Applies to `add`. |
138
+
139
+ > **Note:** `phpxui` installs the required baseline `ppicons` automatically. Install any extra icons separately with `npx ppicons add <icon-name>`.
140
+
141
+ ---
142
+
143
+ ## 🧩 Configuration (phpxui.json)
144
+
145
+ On first run, **phpxui‑cli** creates a `phpxui.json` in your project root. The most important fields are:
146
+
147
+ - `outputDir`: where PHPXUI components are written (default: `src/Lib/PHPXUI`)
148
+ - `psr4`: mapping hints for components and icons
149
+ - `tailwind.css`: where the base CSS should live (default: `src/app/globals.css`)
150
+ - `manifest`: auto-generated AI metadata and installed component inventory maintained by the CLI
151
+
152
+ This file is separate from the Prisma PHP framework config in `prisma-php.json` at the project root.
153
+
154
+ Example:
155
+
156
+ ```json
157
+ {
158
+ "outputDir": "src/Lib/PHPXUI",
159
+ "tailwind": {
160
+ "css": "src/app/globals.css",
161
+ "baseColor": "neutral",
162
+ "cssVariables": true,
163
+ "prefix": ""
164
+ },
165
+ "psr4": {
166
+ "Components": "src/Lib/PHPXUI/",
167
+ "Icons": "src/Lib/PPIcons/"
168
+ },
169
+ "iconLibrary": "ppicons"
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ## 🤖 AI Context
86
176
 
87
- | Flag / Argument | Description |
88
- | --------------- | -------------------------------------------------------- |
89
- | `<component …>` | One or more component names separated by space or comma. |
90
- | `--all` | Download the full catalogue in one request. |
91
- | `--force` | Overwrite existing files. |
177
+ After `add` or `update`, **phpxui-cli** refreshes these files in your project root:
92
178
 
93
- > **Note:** The CLI automatically installs a default set of core icons (such as `x`, `chevron-down`, `chevron-right`) on first use. Extra icons are not yet selectable via `phpxui` directly.
179
+ - `phpxui.json` (updates the `manifest` section)
180
+ - `AGENTS.md`
181
+ - `.github/copilot-instructions.md`
182
+
183
+ The markdown files receive a managed `phpxui` block that inventories installed components, records the component catalogue endpoints, and preserves any manual content outside that block.
94
184
 
95
185
  ---
96
186
 
97
- ## 🎨 Using Additional Icons
187
+ ## 🎨 Using Additional Icons
188
+
189
+ `phpxui add ...` and `phpxui update` already install the baseline icon set used across PHPXUI components.
98
190
 
99
191
  Need more icons? Use the **PPIcons** CLI directly:
100
192
 
@@ -102,35 +194,35 @@ Need more icons? Use the **PPIcons** CLI directly:
102
194
  npx ppicons add menu chevron-left arrow-right
103
195
  ```
104
196
 
105
- This will place the requested icons under `src/Lib/PPIcons` with full PHPXUI typings.
106
- Browse the complete icon catalogue and usage docs at **[https://ppicons.tsnc.tech/](https://ppicons.tsnc.tech/)**.
197
+ This will place the requested icons under `src/Lib/PPIcons` with full PHPXUI typings.
198
+ Browse the complete icon catalogue and usage docs at **https://ppicons.tsnc.tech/**.
107
199
 
108
200
  ---
109
201
 
110
- ## 📚 Documentation
202
+ ## 📚 Documentation
111
203
 
112
- Full guides and examples live at the [PHPXUI documentation site](https://phpxui.tsnc.tech/).
204
+ Full guides and examples live at the PHPXUI documentation site: [https://phpxui.tsnc.tech/](https://phpxui.tsnc.tech/)
113
205
 
114
206
  ---
115
207
 
116
- ## 💡 Contributing
208
+ ## 💡 Contributing
117
209
 
118
210
  We welcome contributions to improve **phpxui‑cli**. If you have ideas, find bugs, or want to add features, open an issue or submit a pull request.
119
211
 
120
212
  ---
121
213
 
122
- ## 📄 License
214
+ ## 📄 License
123
215
 
124
216
  `phpxui‑cli` is released under the MIT License. See `LICENSE` for details.
125
217
 
126
218
  ---
127
219
 
128
- ## 👤 Author
220
+ ## 👤 Author
129
221
 
130
- This project is developed and maintained by **TheSteelNinjaCode**, continuously pushing the boundaries of PHP development.
222
+ This project is developed and maintained by **The Steel Ninja Code**, continuously pushing the boundaries of PHP development.
131
223
 
132
224
  ---
133
225
 
134
- ## 📧 Contact
226
+ ## 📧 Contact
135
227
 
136
228
  Questions or feedback? Reach us at [thesteelninjacode@gmail.com](mailto:thesteelninjacode@gmail.com) — we’d love to hear from you!
package/dist/cli.js ADDED
@@ -0,0 +1 @@
1
+ import{readFile}from"node:fs/promises";import prompts from"prompts";import chalk from"chalk";import path from"path";import{kebabCase}from"change-case";import{installIcons}from"./commands/icons.js";import{generateComponent}from"./generators/php-component.js";import{generateAllComponents}from"./generators/php-components-bulk.js";import{ensurePackageInstalled}from"./generators/ensure-package.js";import{copyTailwindCss}from"./generators/copy-tailwind.js";import{loadPhpXUIConfig}from"./utils/load-config.js";import{refreshPhpXUIProjectContext}from"./utils/phpxui-ai-context.js";import{getInstalledComponents}from"./utils/scan-installed.js";const PPIconsPattern=/\\PPIcons\\([A-Za-z][A-Za-z0-9]*)\b/g;export async function collectComponentIcons(e,o){const t=new Set;for(const n of e){const e=path.isAbsolute(n)?n:path.resolve(o,n),a=await readFile(e,"utf8");for(const e of a.matchAll(PPIconsPattern))t.add(kebabCase(e[1]))}return[...t].sort((e,o)=>e.localeCompare(o))}const CORE_COMPONENTS=["Slot","Portal"],defaultDependencies={prompts,generateComponent,generateAllComponents,ensurePackageInstalled,copyTailwindCss,loadPhpXUIConfig,getInstalledComponents,collectComponentIcons,installIcons,refreshAiContext:refreshPhpXUIProjectContext,logger:console};export async function runCli(e=process.argv.slice(2),o={}){const t={...defaultDependencies,...o},[n,...a]=e;if("add"!==n&&"update"!==n)return t.logger.log(chalk.blue("Usage: phpxui <add|update> [--all] [--force] <component…>")),0;const s={all:!1,force:!1},r=[];for(let e=0;e<a.length;e++){const o=a[e];switch(o){case"--all":s.all=!0;break;case"--force":s.force=!0;break;default:r.push(o)}}const l=process.cwd(),{config:c,isFirstRun:p}=t.loadPhpXUIConfig();t.ensurePackageInstalled("tw-animate-css");if(t.copyTailwindCss(p)){const e=path.relative(l,"src/app/globals.css").replace(/\\/g,"/");t.logger.log(chalk.green(p?`✔ Installed base Tailwind CSS → ${e}`:`✔ Added Tailwind CSS (missing) → ${e}`))}const i=path.resolve(l,c.outputDir||"src/Lib/PHPXUI"),g=()=>t.refreshAiContext({projectRoot:l,targetDir:i,config:c}),m=async e=>{if(0===e.length)return;const o=await t.collectComponentIcons(e,l);await t.installIcons(o)};try{if("update"===n){t.logger.log(chalk.blue(`🔎 Scanning for installed components in ${c.outputDir}...`));const e=t.getInstalledComponents(i);if(0===e.length)return await g(),t.logger.log(chalk.yellow("⚠ No components found to update.")),0;t.logger.log(chalk.blue(`✨ Found ${e.length} components. Updating...`));const o=[],n=[],a=[];for(const s of e)try{const e=await t.generateComponent(s,i,!0),n=Array.isArray(e)?e:[e];a.push(...n),n.forEach(e=>o.push(path.basename(e))),t.logger.log(chalk.green(`✔ Updated ${s}`))}catch(e){n.push(`${s}: ${e.message}`),t.logger.log(chalk.red(`✖ Failed to update ${s}`))}return await m(a),await g(),t.logger.log(chalk.green(`\n✔ Successfully updated ${o.length} components.`)),n.length>0&&(t.logger.log(chalk.red(`✖ ${n.length} failures:`)),n.forEach(e=>t.logger.log(` - ${e}`))),0}const e=p||s.force;if(s.all){const{ok:o,fail:n}=await t.generateAllComponents(i,e);return await m(o),await g(),n.length?1:0}if(0===r.length){const e=(await t.prompts({type:"text",name:"componentList",message:"Which components do you want to add? (space- or comma-separated)",validate:e=>!!e.trim()||"Enter at least one name"})).componentList;if(!e)return 0;r.push(...e.split(/[\s,]+/))}if(p&&!s.all)for(const e of CORE_COMPONENTS){r.some(o=>o.toLowerCase()===e.toLowerCase())||r.unshift(e)}const o=[];for(const n of r){const a=await t.generateComponent(n,i,e),s=Array.isArray(a)?a:[a];o.push(...s);for(const e of s){const o=path.relative(process.cwd(),e).replace(/\\/g,"/");t.logger.log(chalk.green(`✔ ${n} → ${o}`))}}return await m(o),await g(),0}catch(e){return t.logger.error(chalk.red("✖ Error:"),e.message),1}}
@@ -1 +1 @@
1
- import{execSync}from"child_process";import chalk from"chalk";import{savePhpXUIConfig}from"../utils/load-config.js";export async function installIcons(o,n){try{console.log(chalk.blue("📦 Installing ppicons CLI...")),execSync("npm install -g ppicons",{stdio:"inherit"});const c=o.join(" ");console.log(chalk.blue(`✨ Installing default icons: ${c}`)),execSync(`npx ppicons add ${c}`,{stdio:"inherit"}),console.log(chalk.green("✔ Icons installed in src/Lib/PPIcons")),n.iconsInstalled=!0,savePhpXUIConfig(n)}catch(o){console.error(chalk.red("✖ Failed to install icons:"),o.message),process.exit(1)}}
1
+ import{execSync}from"child_process";import chalk from"chalk";export const REQUIRED_PHPXUI_ICONS=["chevron-down","x","chevron-right","ellipsis","chevron-left","arrow-left","arrow-right","check","chevrons-up-down","search","circle","calendar","minus","chevron-up","panel-left"];export function resolvePhpXUIIcons(o){const n=[],e=new Set;for(const c of[...REQUIRED_PHPXUI_ICONS,...o]){const o=c.trim().toLowerCase();o&&!e.has(o)&&(e.add(o),n.push(o))}return n}export async function installIcons(o){const n=resolvePhpXUIIcons(o);try{console.log(chalk.blue("📦 Installing ppicons CLI...")),execSync("npm install -g ppicons",{stdio:"inherit"});const o=n.join(" ");console.log(chalk.blue(`✨ Installing required icons: ${o}`)),execSync(`npx ppicons add ${o}`,{stdio:"inherit"}),console.log(chalk.green("✔ Icons installed in src/Lib/PPIcons"))}catch(o){console.error(chalk.red("✖ Failed to install icons:"),o.message),process.exit(1)}}
@@ -1 +1 @@
1
- import fetch from"node-fetch";import{writeComponent}from"./write-component.js";import path from"path";const SINGLE_URL="https://phpxui.tsnc.tech/cli";export async function generateComponent(t,o,n=!1){const e=`${SINGLE_URL}?component=${encodeURIComponent(t)}`,r=await fetch(e);if(!r.ok)throw new Error(`Could not fetch "${t}": ${r.status} – ${e}`);const c=await r.json(),p=Array.isArray(c)?c:[c],s=[];for(const t of p){const e=await writeComponent(t,o,n);s.push(path.relative(process.cwd(),e))}return s}
1
+ import fetch from"node-fetch";import{writeComponent}from"./write-component.js";import{parsePhpXUIComponentPayload}from"./phpxui-component-payload.js";import path from"path";const SINGLE_URL="https://phpxui.tsnc.tech/cli";export async function generateComponent(o,t,n=!1){const e=`${SINGLE_URL}?component=${encodeURIComponent(o)}`,p=await fetch(e);if(!p.ok)throw new Error(`Could not fetch "${o}": ${p.status} – ${e}`);const r=parsePhpXUIComponentPayload(await p.json(),`Component "${o}"`),a=await writeComponent(r,t,n);return[path.relative(process.cwd(),a)]}
@@ -1 +1 @@
1
- import fetch from"node-fetch";import{writeComponent}from"./write-component.js";const BULK_URL="https://phpxui.tsnc.tech/cli?component=all";export async function generateAllComponents(t,e=!1){const o=await fetch(BULK_URL);if(!o.ok)throw new Error(`Could not fetch component list: ${o.status} – ${BULK_URL}`);const n=await o.json();console.log(`➡ Received ${n.length} components. Generating…`);const s=[],c=[];let a=[];for(const o of n){const n=writeComponent(o,t,e).then(t=>s.push(t)).catch(t=>c.push(`${o.name}: ${t.message}`));a.push(n),a.length>=10&&(await Promise.allSettled(a),a=[])}return await Promise.allSettled(a),{ok:s,fail:c}}
1
+ import fetch from"node-fetch";import{parsePhpXUIComponentList}from"./phpxui-component-payload.js";import{writeComponent}from"./write-component.js";const BULK_URL="https://phpxui.tsnc.tech/cli?component=all";export async function generateAllComponents(t,o=!1){const e=await fetch(BULK_URL);if(!e.ok)throw new Error(`Could not fetch component list: ${e.status} – ${BULK_URL}`);const n=parsePhpXUIComponentList(await e.json(),"PHPXUI component catalog");console.log(`➡ Received ${n.length} components. Generating…`);const s=[],p=[];let c=[];for(const e of n){const n=writeComponent(e,t,o).then(t=>{s.push(t)}).catch(t=>{p.push(`${e.name}: ${t.message}`)});c.push(n),c.length>=10&&(await Promise.allSettled(c),c=[])}return await Promise.allSettled(c),{ok:s,fail:p}}
@@ -0,0 +1 @@
1
+ const COMPONENT_NAME_PATTERN=/^[A-Za-z][A-Za-z0-9]*$/;function isRecord(n){return"object"==typeof n&&null!==n&&!Array.isArray(n)}export function normalizePhpXUIComponentName(n){const r=n.trim();if(!COMPONENT_NAME_PATTERN.test(r))throw new Error(`Invalid component name "${n}" returned by API.`);return r}export function parsePhpXUIComponentPayload(n,r){if(!isRecord(n))throw new Error(`${r} returned an invalid component payload.`);if("string"!=typeof n.name)throw new Error(`${r} returned an invalid component name.`);if("string"!=typeof n.content)throw new Error(`${r} returned invalid component content.`);return{name:normalizePhpXUIComponentName(n.name),content:n.content}}export function parsePhpXUIComponentList(n,r){if(!Array.isArray(n))throw new Error(`${r} returned an invalid component list.`);return n.map((n,e)=>parsePhpXUIComponentPayload(n,`${r} item ${e+1}`))}
@@ -1 +1 @@
1
- import fs from"fs-extra";import path from"path";export async function writeComponent(t,a,i=!1){const n=path.join(a,`${t.name}.php`);return!i&&await fs.pathExists(n)||(await fs.ensureDir(path.dirname(n)),await fs.writeFile(n,t.content,"utf8")),n}
1
+ import fs from"fs-extra";import path from"path";import{normalizePhpXUIComponentName}from"./phpxui-component-payload.js";export async function writeComponent(t,e,o=!1){const a=normalizePhpXUIComponentName(t.name),r=path.resolve(e),p=path.resolve(r,`${a}.php`),n=path.relative(r,p);if(n.startsWith("..")||path.isAbsolute(n))throw new Error(`Resolved component path escaped target directory for "${a}".`);return!o&&await fs.pathExists(p)||(await fs.ensureDir(path.dirname(p)),await fs.writeFile(p,t.content,"utf8")),p}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import prompts from"prompts";import chalk from"chalk";import path from"path";import{generateComponent}from"./generators/php-component.js";import{generateAllComponents}from"./generators/php-components-bulk.js";import{ensurePackageInstalled}from"./generators/ensure-package.js";import{copyTailwindCss}from"./generators/copy-tailwind.js";import{loadPhpXUIConfig}from"./utils/load-config.js";import{installIcons}from"./commands/icons.js";import{getInstalledComponents}from"./utils/scan-installed.js";const CORE_COMPONENTS=["Slot","Portal"];(async()=>{const o=process.argv.slice(2),[e,...s]=o;"add"!==e&&"update"!==e&&(console.log(chalk.blue("Usage: phpxui <add|update> [--all] [--force] <component…>")),process.exit(0));const t={all:!1,force:!1},n=[];for(let o=0;o<s.length;o++){const e=s[o];switch(e){case"--all":t.all=!0;break;case"--force":t.force=!0;break;default:n.push(e)}}const{config:a,isFirstRun:l}=loadPhpXUIConfig();a.iconsInstalled||await installIcons(["chevron-down","x","chevron-right","ellipsis","chevron-left","arrow-left","arrow-right","check","chevrons-up-down","search","circle","calendar","minus","chevron-up","panel-left"],a),ensurePackageInstalled("tw-animate-css");if(copyTailwindCss(l)){const o=path.relative(process.cwd(),"src/app/globals.css").replace(/\\/g,"/");console.log(chalk.green(l?`✔ Installed base Tailwind CSS → ${o}`:`✔ Added Tailwind CSS (missing) → ${o}`))}const r=path.resolve(a.outputDir||"src/Lib/PHPXUI");try{if("update"===e){console.log(chalk.blue(`🔎 Scanning for installed components in ${a.outputDir}...`));const o=getInstalledComponents(r);0===o.length&&(console.log(chalk.yellow("⚠ No components found to update.")),process.exit(0)),console.log(chalk.blue(`✨ Found ${o.length} components. Updating...`));const e=[],s=[];for(const t of o)try{const o=await generateComponent(t,r,!0);(Array.isArray(o)?o:[o]).forEach(o=>e.push(path.basename(o))),console.log(chalk.green(`✔ Updated ${t}`))}catch(o){s.push(`${t}: ${o.message}`),console.log(chalk.red(`✖ Failed to update ${t}`))}console.log(chalk.green(`\n✔ Successfully updated ${e.length} components.`)),s.length>0&&(console.log(chalk.red(`✖ ${s.length} failures:`)),s.forEach(o=>console.log(` - ${o}`))),process.exit(0)}const o=l||t.force;if(t.all){const{fail:e}=await generateAllComponents(r,o);process.exit(e.length?1:0)}if(0===n.length){const{componentList:o}=await prompts({type:"text",name:"componentList",message:"Which components do you want to add? (space- or comma-separated)",validate:o=>!!o.trim()||"Enter at least one name"});n.push(...o.split(/[\s,]+/))}if(l&&!t.all)for(const o of CORE_COMPONENTS){n.some(e=>e.toLowerCase()===o.toLowerCase())||n.unshift(o)}for(const e of n){const s=await generateComponent(e,r,o),t=Array.isArray(s)?s:[s];for(const o of t){const s=path.relative(process.cwd(),o).replace(/\\/g,"/");console.log(chalk.green(`✔ ${e} → ${s}`))}}}catch(o){console.error(chalk.red("✖ Error:"),o.message),process.exit(1)}})();
2
+ import{runCli}from"./cli.js";(async()=>{const s=await runCli(process.argv.slice(2));process.exit(s)})();
@@ -1 +1 @@
1
- import fs from"fs";import path from"path";const defaultConfig={style:"new-york",force:!1,outputDir:"src/Lib/PHPXUI",iconsInstalled:!1,tailwind:{css:"src/app/globals.css",baseColor:"neutral",cssVariables:!0,prefix:""},psr4:{Components:"src/Lib/PHPXUI/",Icons:"src/Lib/PPIcons/"},iconLibrary:"ppicons"};export function loadPhpXUIConfig(){const s=path.resolve("phpxui.json"),o=fs.existsSync(s);o||(fs.writeFileSync(s,JSON.stringify(defaultConfig,null,2)),console.log("📦 Created default phpxui.json"));const n=fs.readFileSync(s,"utf-8");return{config:JSON.parse(n),isFirstRun:!o}}export function savePhpXUIConfig(s){const o=path.resolve("phpxui.json");fs.writeFileSync(o,JSON.stringify(s,null,2))}
1
+ import fs from"fs";import path from"path";const defaultConfig={style:"new-york",force:!1,outputDir:"src/Lib/PHPXUI",tailwind:{css:"src/app/globals.css",baseColor:"neutral",cssVariables:!0,prefix:""}};function normalizePhpXUIConfig(i){return{style:i.style??defaultConfig.style,force:i.force??defaultConfig.force,outputDir:i.outputDir??defaultConfig.outputDir,tailwind:{...defaultConfig.tailwind,...i.tailwind},...void 0!==i.manifest?{manifest:i.manifest}:{}}}export function loadPhpXUIConfig(){const i=path.resolve("phpxui.json"),t=fs.existsSync(i);t||(fs.writeFileSync(i,JSON.stringify(defaultConfig,null,2)),console.log("📦 Created default phpxui.json"));const o=fs.readFileSync(i,"utf-8");return{config:normalizePhpXUIConfig(JSON.parse(o)),isFirstRun:!t}}export function savePhpXUIConfig(i){const t=path.resolve("phpxui.json");fs.writeFileSync(t,JSON.stringify(normalizePhpXUIConfig(i),null,2))}
@@ -0,0 +1 @@
1
+ import fs from"fs-extra";import path from"node:path";import{PHPXUI_CONFIG_FILE,PHPXUI_INSTRUCTIONS_FILE,PHPXUI_MANIFEST_KEY,buildPhpXUIManifest,writePhpXUIManifestToConfig}from"./phpxui-manifest.js";const START_MARKER="\x3c!-- phpxui:start --\x3e",END_MARKER="\x3c!-- phpxui:end --\x3e";function getPhpXUIInstructionsPath(e){return path.join(e,...PHPXUI_INSTRUCTIONS_FILE.split("/"))}function escapeRegExp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildApplyTo(e){return[PHPXUI_CONFIG_FILE,e.project.hostFrameworkConfigFile,e.project.instructionsFile,`${e.project.componentsDirectory}/**`,e.tailwind.css].join(", ")}function buildInstructionScaffold(e){return["---",'description: "Use when working with PHPXUI, PHPXUI CLI-managed Prisma PHP components, phpxui.json manifest metadata, component installation, component updates, or generated component imports."','name: "PHPXUI AI Context"',`applyTo: "${buildApplyTo(e)}"`,"---","","# PHPXUI Instructions","","Keep PHPXUI-specific AI guidance here instead of in `.github/copilot-instructions.md` or `AGENTS.md`.","",`- Use \`${PHPXUI_CONFIG_FILE}\` under the \`${PHPXUI_MANIFEST_KEY}\` key as the source of truth for installed component inventory, configured paths, and registry metadata.`,"- Prefer `phpxui add ...` or `phpxui update` for registry-backed components instead of hand-writing generated PHPXUI classes.",`- Preserve manual content outside the managed PHPXUI block because refreshes replace only the \`${START_MARKER}\` ... \`${END_MARKER}\` section.`].join("\n")}function getPhpUsageExample(e){return["```php","<?php","",`use ${e}\\Alert;`,"","?>","","<Alert>"," <div>Saved changes</div>","</Alert>","```"].join("\n")}function getCatalogResponseExample(){return["```json","{",' "name": "Button",',' "content": "<?php\\n\\nnamespace Lib\\\\PHPXUI;\\n\\nuse PP\\\\PHPX\\\\PHPX;\\nuse Lib\\\\PHPXUI\\\\Slot;\\n\\nclass Button extends PHPX\\n{\\n ...generated PHP source...\\n}"',"}","```"].join("\n")}function buildManagedBlockContent(e){const n=e.components.length>0?e.components.map(e=>`- ${e.name}: \`${e.file}\``):["- No PHPXUI components are installed yet."];return["# PHPXUI AI Context","","This project uses `phpxui` to generate reusable PHPXUI components for Prisma PHP.","",`- Host framework: ${e.project.hostFramework}`,`- Prisma PHP root config: ${e.project.hostFrameworkConfigFile}`,`- PHPXUI instructions file: ${e.project.instructionsFile}`,`- Components directory: ${e.project.componentsDirectory}`,`- Installed component count: ${e.components.length}`,`- Component namespace: ${e.usage.entry}`,`- Tailwind CSS file: ${e.tailwind.css}`,`- Installed component inventory and project metadata: \`${PHPXUI_CONFIG_FILE}\` (under \`${PHPXUI_MANIFEST_KEY}\`)`,"","## Installing Components","","When a requested UI component does not exist yet, install it with `phpxui` instead of hand-writing generated PHPXUI classes.","",`- Add one component: \`${e.commands.addOne}\``,`- Add multiple components: \`${e.commands.addMany}\``,`- Add the full catalogue: \`${e.commands.addAll}\``,`- Refresh installed components: \`${e.commands.updateInstalled}\``,"","## Discovering Available Components","","Use the PHPXUI catalogue API to find component names before installing them.","",`- Fetch all available components: \`${e.catalogApi.listAll.method} ${e.catalogApi.listAll.url}\``,`- Fetch one component by name: \`${e.catalogApi.getOne.method} ${e.catalogApi.getOne.exampleUrl}\``,"- The `component=all` endpoint returns a JSON array of component objects.","- The single-component endpoint returns one JSON object with `name` and `content` fields.","","Single component response example:","",getCatalogResponseExample(),"","## Installed Components","",...n,"","## Using Installed Components","",`Import generated components from the \`${e.usage.entry}\` namespace and render them as PHPX tags.`,"",getPhpUsageExample(e.usage.entry),"","## Notes","",`- Reuse installed components from \`${PHPXUI_CONFIG_FILE}\` before generating new ones.`,`- Generated component files follow this pattern: \`${e.usage.filePattern}\``,"- Manual content outside this managed block is preserved."].join("\n")}function mergeManagedBlock(e,n){const t=e.replace(/\r\n/g,"\n"),o=new RegExp(`${escapeRegExp(START_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}`);return o.test(t)?t.replace(o,n).trimEnd()+"\n":0===t.trim().length?`${n}\n`:`${t.trimEnd()}\n\n${n}\n`}async function writePhpXUIInstructionFile(e,n){const t=[START_MARKER,buildManagedBlockContent(e),END_MARKER].join("\n");await fs.ensureDir(path.dirname(n));const o=await fs.pathExists(n)?await fs.readFile(n,"utf8"):null,s=null===o||0===o.trim().length?`${buildInstructionScaffold(e)}\n\n${t}\n`:mergeManagedBlock(o,t);return await fs.writeFile(n,s,"utf8"),n}async function writePhpXUIWorkspaceInstructions(e,n){return writePhpXUIInstructionFile(e,getPhpXUIInstructionsPath(n))}export async function writePhpXUIInstructions(e){return writePhpXUIWorkspaceInstructions(await buildPhpXUIManifest(e),e.projectRoot)}export async function writePhpXUICopilotInstructions(e){return writePhpXUIInstructions(e)}export async function writePhpXUIAgentsInstructions(e){return writePhpXUIInstructions(e)}export async function refreshPhpXUIProjectContext(e){const n=await buildPhpXUIManifest(e),t=await writePhpXUIManifestToConfig({projectRoot:e.projectRoot,config:e.config,manifest:n});return await writePhpXUIWorkspaceInstructions(n,e.projectRoot),t}
@@ -0,0 +1 @@
1
+ import fs from"fs-extra";import path from"node:path";import{getInstalledComponents}from"./scan-installed.js";export const PHPXUI_CONFIG_FILE="phpxui.json";export const PHPXUI_MANIFEST_KEY="manifest";export const PRISMA_PHP_CONFIG_FILE="prisma-php.json";export const PHPXUI_INSTRUCTIONS_FILE=".github/instructions/phpxui.instructions.md";const LEGACY_PHPXUI_MANIFEST_FILE="phpxui-manifest.json";function toRelativePath(t,e){const n=path.relative(t,e).replace(/\\/g,"/");return n.length>0?n:"."}function toRelativeConfigPath(t,e,n){const o=e&&e.trim().length>0?e:n;return toRelativePath(t,path.isAbsolute(o)?o:path.resolve(t,o)).replace(/\/+$/,"")||n}function inferNamespaceFromRelativePath(t){const e=t.split("/").filter(Boolean).filter(t=>"."!==t&&".."!==t);return"src"===e[0]?.toLowerCase()&&e.shift(),e.join("\\")||"App"}function getCommands(){return{addOne:"npx phpxui add <component-name>",addMany:"npx phpxui add <component-a> <component-b>",addAll:"npx phpxui add --all",updateInstalled:"npx phpxui update"}}function getCatalogApi(){return{listAll:{method:"GET",url:"https://phpxui.tsnc.tech/cli?component=all",returns:"PhpXUIComponentRecord[]",purpose:"List all available PHPXUI components that can be installed."},getOne:{method:"GET",urlTemplate:"https://phpxui.tsnc.tech/cli?component=<component-name>",exampleUrl:"https://phpxui.tsnc.tech/cli?component=Button",returns:"PhpXUIComponentRecord",purpose:"Fetch one component by name before installing or refreshing it."},responseFields:{name:"string",content:"string"}}}function getUsage(t,e){const n=toRelativePath(t,e);return{componentType:"class",entryStyle:"namespace",entry:inferNamespaceFromRelativePath(n),filePattern:`${n}/<ComponentName>.php`,syntax:"PHPX component tags"}}async function collectInstalledComponents(t,e){return await fs.pathExists(e)?getInstalledComponents(e).sort((t,e)=>t.localeCompare(e)).map(n=>({name:n,file:toRelativePath(t,path.join(e,`${n}.php`))})):[]}async function readConfigDocument(t,e){const n=path.join(t,"phpxui.json");return await fs.pathExists(n)?fs.readJson(n):{...e}}async function removeLegacyManifestFile(t){const e=path.join(t,"phpxui-manifest.json");await fs.pathExists(e)&&await fs.remove(e)}export async function writePhpXUIManifestToConfig({projectRoot:t,config:e,manifest:n}){const o=path.join(t,"phpxui.json"),a=await readConfigDocument(t,e);return await fs.writeJson(o,{...a,[PHPXUI_MANIFEST_KEY]:n},{spaces:2}),await removeLegacyManifestFile(t),o}export async function writePhpXUIManifest({projectRoot:t,targetDir:e,config:n}){return writePhpXUIManifestToConfig({projectRoot:t,config:n,manifest:await buildPhpXUIManifest({projectRoot:t,targetDir:e,config:n})})}export async function buildPhpXUIManifest({projectRoot:t,targetDir:e,config:n}){const o=await collectInstalledComponents(t,e),a=toRelativePath(t,e),i=toRelativeConfigPath(t,n.tailwind?.css,"src/app/globals.css");return{schemaVersion:1,generatedAt:(new Date).toISOString(),project:{framework:"phpxui",hostFramework:"prisma-php",rootDirectory:".",sourceDirectory:"src",hostFrameworkConfigFile:"prisma-php.json",configFile:"phpxui.json",componentsDirectory:a,instructionsFile:PHPXUI_INSTRUCTIONS_FILE},commands:getCommands(),catalogApi:getCatalogApi(),usage:getUsage(t,e),tailwind:{css:i},components:o}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phpxui",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A package for generating Prisma PHP components with a CLI interface.",
5
5
  "main": "index.js",
6
6
  "scripts": {