velyx 1.0.2 → 2.0.0
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/LICENSE.md +21 -0
- package/README.md +43 -5
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Velyx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Velyx can be executed without installation.
|
|
|
44
44
|
### Initialize Velyx in a project
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npx velyx init
|
|
47
|
+
npx velyx@latest init
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
This command:
|
|
@@ -57,7 +57,7 @@ This command:
|
|
|
57
57
|
### Add a component
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
npx velyx add button
|
|
60
|
+
npx velyx@latest add button
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Velyx will:
|
|
@@ -77,7 +77,31 @@ resources/views/components/ui
|
|
|
77
77
|
### List available components
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
npx velyx list
|
|
80
|
+
npx velyx@latest list
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Search for a component
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx velyx@latest search
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
Velyx can be used without installation via `npx`, but you can also install it globally for frequent use:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install -g velyx@latest
|
|
97
|
+
# or
|
|
98
|
+
pnpm add -g velyx@latest
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For development releases (beta/next tags):
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx velyx@latest
|
|
81
105
|
```
|
|
82
106
|
|
|
83
107
|
---
|
|
@@ -88,7 +112,7 @@ Velyx does **not** update your code automatically.
|
|
|
88
112
|
|
|
89
113
|
If a component changes in the registry and you want the new version:
|
|
90
114
|
|
|
91
|
-
- run `velyx add <component>` again
|
|
115
|
+
- run `npx velyx@latest add <component>` again
|
|
92
116
|
- review the changes
|
|
93
117
|
- decide what to keep
|
|
94
118
|
|
|
@@ -107,15 +131,29 @@ Velyx exists to help you move faster, not to take control away from you.
|
|
|
107
131
|
|
|
108
132
|
---
|
|
109
133
|
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
After running `npx velyx@latest init`, a `velyx.json` file is created in your project root. This file stores your Velyx configuration and can be customized to your needs.
|
|
137
|
+
|
|
110
138
|
## Documentation
|
|
111
139
|
|
|
112
|
-
Full documentation
|
|
140
|
+
Full documentation is available at [velyx.dev](https://velyx.dev):
|
|
113
141
|
|
|
114
142
|
- Introduction
|
|
115
143
|
- Getting started
|
|
116
144
|
- Component reference
|
|
117
145
|
- Project philosophy
|
|
118
146
|
|
|
147
|
+
Technical documentation for testing architecture:
|
|
148
|
+
|
|
149
|
+
- [docs/TESTING.md](docs/TESTING.md)
|
|
150
|
+
|
|
151
|
+
## Links
|
|
152
|
+
|
|
153
|
+
- **Registry**: [registry.velyx.dev](https://registry.velyx.dev)
|
|
154
|
+
- **Documentation**: [velyx.dev](https://velyx.dev)
|
|
155
|
+
- **GitHub**: [github.com/velyx-labs/cli](https://github.com/velyx-labs/cli)
|
|
156
|
+
|
|
119
157
|
---
|
|
120
158
|
|
|
121
159
|
## License
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import v from'chalk';import {Command}from'commander';import
|
|
1
|
+
import v from'chalk';import {Command}from'commander';import w from'path';import h from'fs-extra';import be from'prompts';import S,{promises,readFileSync}from'fs';import {cyan,green,yellow,red}from'kleur/colors';import {exec}from'child_process';import {promisify}from'util';import tt from'ora';import {z as z$1}from'zod';import It from'cli-table3';var Y=".bak";function ie(r){if(!h.existsSync(r))return null;let e=`${r}${Y}`;try{return h.renameSync(r,e),e}catch(t){return console.error(`Failed to create backup of ${r}: ${t}`),null}}function N(r){let e=`${r}${Y}`;if(!h.existsSync(e))return false;try{return h.renameSync(e,r),!0}catch(t){return console.error(`Warning: Could not restore backup file ${e}: ${t}`),false}}function T(r){let e=`${r}${Y}`;if(!h.existsSync(e))return false;try{return h.unlinkSync(e),!0}catch{return false}}var f=class{async fileExists(e){try{return await promises.access(e),!0}catch{return false}}async writeFile(e,t){await promises.mkdir(w.dirname(e),{recursive:true}),await promises.writeFile(e,t,"utf-8");}async readFile(e){return await promises.readFile(e,"utf-8")}async ensureDir(e){await promises.mkdir(e,{recursive:true});}};function ce(){return S.existsSync("composer.json")&&S.existsSync("artisan")}function le(){try{return JSON.parse(S.readFileSync("package.json","utf8"))}catch{return null}}function pe(r){let e={...r.dependencies,...r.devDependencies};if(e["@tailwindcss/vite"]||e["@tailwindcss/postcss"])return true;if(e.tailwindcss){let n=String(e.tailwindcss).match(/(\d+)/);return n?Number(n[1])>=4:false}return false}function Ue(){try{let r=JSON.parse(S.readFileSync("package.json","utf8")),e=r.dependencies&&Object.keys(r.dependencies).some(n=>n.toLowerCase().includes("alpine")),t=r.devDependencies&&Object.keys(r.devDependencies).some(n=>n.toLowerCase().includes("alpine"));return !!(e||t)}catch{return false}}function Je(){let r="resources/views/layouts";if(!S.existsSync(r))return false;try{let e=S.readdirSync(r,{recursive:!0});for(let t of e)if(t.endsWith(".blade.php")&&S.readFileSync(w.join(r,t),"utf8").toLowerCase().includes("alpine"))return !0}catch{return false}return false}function me(){return Ue()||Je()}function V(){return S.existsSync("pnpm-lock.yaml")||S.existsSync("pnpm-lock.yml")?"pnpm":S.existsSync("yarn.lock")?"yarn":S.existsSync("package-lock.json")?"npm":S.existsSync("bun.lock")?"bun":"npm"}var qe=["resources/css/app.css","resources/css/app.scss","resources/css/main.css","resources/css/style.css","resources/css/styles.css"];function de(){for(let r of qe)if(S.existsSync(r))return {path:r,content:S.readFileSync(r,"utf8")};return null}function H(r){return /@import\s+["']tailwindcss["']/.test(r)}function fe(r){let e=S.readFileSync(r,"utf8");e.includes('@import "./velyx.css"')||(H(e)?e=e.replace(/@import\s+["']tailwindcss["'];?/,t=>`${t}
|
|
2
2
|
@import "./velyx.css";`):e+=`
|
|
3
3
|
@import "./velyx.css";
|
|
4
|
-
`,
|
|
5
|
-
`),
|
|
6
|
-
`);let l=`Alpine.data('${
|
|
4
|
+
`,S.writeFileSync(r,e,"utf8"));}function Be(r){return r.replace(/^[^a-zA-Z_$]+/,"").replace(/[-_]+([a-zA-Z0-9])/g,(e,t)=>t.toUpperCase())}var We=["resources/js/app.js","resources/js/main.js","resources/js/index.js"];function ue(){for(let r of We)if(S.existsSync(r))return {path:r,content:S.readFileSync(r,"utf8")};return null}function ge(r,e,t){let n=S.readFileSync(r,"utf8"),s=Be(e),i=`import ${s} from '${t}'`;if(n.includes(i)||n.includes(`import ${s} from "${t}"`))return;let a=n.split(`
|
|
5
|
+
`),c=-1;for(let m=0;m<a.length;m++)a[m]?.startsWith("import ")&&(c=m);a.splice(c+1,0,i),n=a.join(`
|
|
6
|
+
`);let l=`Alpine.data('${s}', ${s});`;n.includes("document.addEventListener('alpine:init'")?n.includes(l)||(n=n.replace(/document\.addEventListener\('alpine:init',\s*\(\)\s*=>\s*\{/,m=>`${m}
|
|
7
7
|
${l}`)):n+=`
|
|
8
8
|
|
|
9
9
|
document.addEventListener('alpine:init', () => {
|
|
10
10
|
${l}
|
|
11
11
|
});
|
|
12
|
-
`,
|
|
13
|
-
`);
|
|
14
|
-
`,"utf8");}function
|
|
15
|
-
---`),
|
|
12
|
+
`,S.writeFileSync(r,n,"utf8");}function Ge(r){let e=r;for(let t=0;t<4;t+=1){let n=w.join(e,"colors");if(S.existsSync(n))return n;let s=w.join(e,"src/colors");if(S.existsSync(s))return s;let i=w.dirname(e);if(i===e)break;e=i;}return w.join(r,"colors")}var ze=process.argv[1]?w.dirname(w.resolve(process.argv[1])):process.cwd(),Z=Ge(ze);function he(){return S.existsSync(Z)?S.readdirSync(Z).filter(e=>e.endsWith(".json")).map(e=>{let t=w.join(Z,e),n=S.readFileSync(t,"utf-8");return JSON.parse(n)}).filter(e=>!!e?.name).sort((e,t)=>e.name.localeCompare(t.name)):[]}function A(){return he()}function Ye(r){return he().find(e=>e.name===r)}function ye(r){return Object.entries(r).map(([e,t])=>` --${e}: ${t};`)}function xe(r,e){let t=Ye(r);if(!t)throw new Error(`Theme "${r}" not found in colors registry.`);let n=ye(t.cssVars.light),s=ye(t.cssVars.dark),i=[":root {",...n,"}","",".dark {",...s,"}",""].join(`
|
|
13
|
+
`);S.writeFileSync(e,i,{encoding:"utf-8",flag:"wx"});}var d={error:red,warn:yellow,info:cyan,success:green};var o={error(...r){console.log(d.error(r.join(" ")));},warn(...r){console.log(d.warn(r.join(" ")));},info(...r){console.log(d.info(r.join(" ")));},success(...r){console.log(d.success(r.join(" ")));},log(...r){console.log(r.join(" "));},break(){console.log("");}};function ve(r){S.writeFileSync("velyx.json",JSON.stringify(r,null,2)+`
|
|
14
|
+
`,"utf8");}function we(){return S.existsSync("velyx.json")||(o.error("Velyx configuration not found."),process.exit(1)),JSON.parse(S.readFileSync("velyx.json","utf8"))}var $={version:"2.0.0"};var L=class{constructor(e){this.fileSystem=e;}validateEnvironment(){if(!ce())throw new Error("No Laravel project detected");let e=le();if(!e||!pe(e))throw new Error("Tailwind CSS v4 was not detected");let t=me(),n=V(),s=de(),i=ue(),a=s?H(s.content):false;return {isLaravel:true,hasTailwindV4:true,hasAlpine:t,detectedPackageManager:n,cssFile:s,jsFile:i,canInjectCss:a}}displayEnvironmentInfo(e){e.hasAlpine?o.success("Alpine.js detected - components will be fully interactive"):(o.warn("Alpine.js not detected"),o.log(`Install Alpine.js: ${e.detectedPackageManager} install alpinejs`)),e.cssFile?e.canInjectCss||(o.warn("Tailwind import not found in CSS"),o.log("Velyx styles will not be auto-imported")):(o.warn("No main CSS file found"),o.log("Styles will be created but not auto-imported")),e.jsFile||(o.warn("No main JS file found"),o.log("Component scripts will not be auto-imported"));}async createComponentsDirectory(e="resources/views/components/ui"){await this.fileSystem.ensureDir(e);}async createThemeFile(e,t="resources/css/velyx.css"){let n=t.split("/").slice(0,-1).join("/");if(await this.fileSystem.ensureDir(n),S.existsSync(t))o.info("velyx.css already exists");else try{xe(e,t),o.success("Velyx theme created"),o.info(t);}catch(s){throw new Error(`Failed to create theme file: ${s.message}`)}}async injectStylesImport(e){fe(e),o.success("Velyx styles imported"),o.info(e);}async generateConfig(e,t){let n={version:$.version,theme:e.theme,packageManager:e.packageManager,css:{entry:t.cssFile?.path??"",velyx:"resources/css/velyx.css"},js:{entry:t.jsFile?.path??""},components:{path:"resources/views/components/ui"}};ve(n),o.success("velyx.json config generated");}displaySummary(e,t,n){console.log(`
|
|
15
|
+
---`),o.success("Laravel project detected"),o.success("Tailwind CSS v4 detected"),o.success(`Theme selected: ${e.theme}`),o.success(`Package manager: ${e.packageManager}`),o.success("UI components directory ready"),t.jsFile&&o.success("Main JS file detected"),o.success(n?"Styles import complete":"Styles import pending"),o.success("velyx.json created"),console.log(`
|
|
16
16
|
Next steps:`),console.log(" velyx add button"),console.log(`
|
|
17
|
-
\u{1F4A1} Want to customize your Tailwind palette? Try https://tweakcn.com/ \u2014 a visual generator for Tailwind-compatible color scales.`);}};var
|
|
18
|
-
Velyx is designed to work with Laravel projects.`),
|
|
19
|
-
Message:`),
|
|
20
|
-
Suggestion:`),
|
|
21
|
-
\u{1F389} Happy coding! Enjoy building beautiful components!`);}};var
|
|
22
|
-
Available components:`)),console.log("");let a=new
|
|
17
|
+
\u{1F4A1} Want to customize your Tailwind palette? Try https://tweakcn.com/ \u2014 a visual generator for Tailwind-compatible color scales.`);}};var g={start(r){return tt(r).start()},async withTask(r,e,t,n){let s=this.start(r);try{let i=await e();return t?s.succeed(t):s.stop(),i}catch(i){throw s.fail(n||"Operation failed"),i}}};var st=promisify(exec);async function ot(r){try{let e=w.resolve(r,"composer.json"),t=w.resolve(r,"package.json");if(!h.existsSync(e))return null;let n=await h.readJson(e);if(!(n.require?.["laravel/framework"]||n.require?.["illuminate/foundation"]))return null;let i=process.cwd();process.chdir(r);let a=V();console.log(`Detected package manager: ${a}`),process.chdir(i);let c={name:n.name||w.basename(r),framework:{name:"laravel",label:"Laravel",version:n.require?.["laravel/framework"]||"unknown"},hasAlpine:!1,hasVite:!1,packageManager:a,paths:{views:"resources/views",assets:"resources/js",public:"public",config:"config"}};if(h.existsSync(t)){let u=await h.readJson(t);c.hasAlpine=!!(u.dependencies?.alpinejs||u.devDependencies?.alpinejs),c.hasVite=!!u.devDependencies?.vite;}let l=w.resolve(r,"resources/views"),m=w.resolve(r,"resources/js");return h.existsSync(l)&&(c.paths.views="resources/views"),h.existsSync(m)&&(c.paths.assets="resources/js"),c}catch{return null}}async function Se(r){let e={};if(!h.existsSync(r.cwd))return e.MISSING_DIR=true,{errors:e,projectInfo:null};let t=g.start("Checking project environment..."),n=w.resolve(r.cwd,"velyx.json");if(h.existsSync(n)&&!r.force){t.fail(),o.break();let{action:c}=await be({type:"select",name:"action",message:`A ${d.info("velyx.json")} file already exists. What would you like to do?`,choices:[{title:"Re-initialize Velyx configuration",value:"reinit"},{title:"Keep existing configuration",value:"keep"},{title:"Exit",value:"exit"}],initial:0});c==="exit"&&(o.log("Operation cancelled."),process.exit(0)),c==="keep"&&(o.log("Keeping existing configuration."),process.exit(0)),o.log("Re-initializing Velyx configuration...");}let s=await ot(r.cwd);(!s||s.framework.name!=="laravel")&&(e.UNSUPPORTED_PROJECT=true,t.fail(),o.break(),o.error(`We could not detect a supported Laravel project at ${d.info(r.cwd)}.
|
|
18
|
+
Velyx is designed to work with Laravel projects.`),o.break(),process.exit(1)),t.succeed(`Found ${d.info(s.framework.label)} project`);let i=g.start("Checking Alpine.js...");if(s.hasAlpine)i.succeed("Alpine.js found");else {i.fail(),o.break(),o.warn("Alpine.js is required but not found in your project.");let{installAlpine:c}=await be({type:"confirm",name:"installAlpine",message:"Would you like to install Alpine.js now?",initial:true});if(c){let l=s.packageManager,m=g.start(`Installing Alpine.js with ${l}...`);try{await st(`${l} install alpinejs`,{cwd:r.cwd}),m.succeed("Alpine.js installed successfully"),s.hasAlpine=!0;}catch(u){m.fail(`Failed to install Alpine.js: ${u.message}`),o.error(`Please install Alpine.js manually: ${d.info(`${l} install alpinejs`)}`),o.break(),process.exit(1);}}else {let l=s.packageManager;o.error(`Alpine.js is required. Install it with: ${d.info(`${l} install alpinejs`)}`),o.break(),process.exit(1);}}let a=g.start("Checking build tools...");return s.hasVite?a.succeed("Vite found"):(o.warn("Vite not found. Using Vite is recommended for better development experience."),a.warn("Vite not found (but optional)")),Object.keys(e).length>0&&(o.break(),process.exit(1)),{errors:e,projectInfo:s}}var Ce=z$1.object({baseColor:z$1.string().optional(),defaults:z$1.boolean(),force:z$1.boolean(),cwd:z$1.string()});async function it(){let r=A();r.length===0&&(o.error("No base colors available."),process.exit(1));let{theme:e}=await be({type:"select",name:"theme",message:"Choose a base color theme",choices:r.map(t=>({title:t.label,value:t.name}))},{onCancel:()=>{o.error("Theme selection aborted"),process.exit(1);}});return e}async function at(){let{shouldImport:r}=await be({type:"confirm",name:"shouldImport",message:"Import Velyx styles into your main CSS file?",initial:true},{onCancel:()=>false});return !!r}function ct(r){if(!r.baseColor)return;let t=A().find(n=>n.name===r.baseColor);if(t)return t.name;o.warn(`Unknown base color "${r.baseColor}".`);}async function ke(r,e){e||(e=(await Se(r)).projectInfo),process.chdir(r.cwd);let t=new f,n=new L(t);try{let s=n.validateEnvironment();n.displayEnvironmentInfo(s);let i=e.packageManager,a=A(),c=a.find(u=>u.name==="neutral")?.name??a[0]?.name,l=ct(r);l||(l=r.defaults&&c?c:await it()),l||(o.error("No base color available."),process.exit(1)),await n.createComponentsDirectory(),await n.createThemeFile(l);let m=!1;s.cssFile&&s.canInjectCss&&(r.defaults||await at())&&(await n.injectStylesImport(s.cssFile.path),m=!0),await n.generateConfig({packageManager:i,theme:l,importStyles:m},s),n.displaySummary({packageManager:i,theme:l,importStyles:m},s,m);}catch(s){o.error(s.message),s instanceof Error&&(s.message.includes("Laravel project")?o.log("Run velyx init at the root of a Laravel project"):s.message.includes("Tailwind")&&o.log(`Velyx requires ${d.info("Tailwind CSS v4+")}`)),process.exit(1);}}process.on("exit",r=>{let e=w.resolve(process.cwd(),"velyx.json");return r===0?T(e):N(e)});var Ee=new Command().name("init").description("initialize your project and install dependencies").option("-b, --base-color <base-color>","the base color to use. (neutral, gray, zinc, stone, slate)",void 0).option("-d, --defaults","use default configuration.",false).option("-f, --force","force overwrite of existing configuration.",false).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",process.cwd()).action(async r=>{let e=Ce.parse({baseColor:r.baseColor,defaults:!!r.defaults,force:!!r.force,cwd:w.resolve(r.cwd)});await ke(e);});var X=class extends Error{constructor(t,n,s){super(t);this.code=n;this.context=s;this.name="VelyxError";}},C=class{handle(e,t){e instanceof X?(console.error(`[${e.code}] ${e.message}`),e.context&&console.error("Context:",e.context)):console.error(`Unexpected error in ${t}: ${e.message}`);}};var pt={UNKNOWN_ERROR:"UNKNOWN_ERROR"},U=class extends Error{code;statusCode;context;suggestion;timestamp;cause;constructor(e,t={}){super(e),this.name="RegistryError",this.code=t.code||pt.UNKNOWN_ERROR,this.statusCode=t.statusCode,this.cause=t.cause,this.context=t.context,this.suggestion=t.suggestion,this.timestamp=new Date,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor);}toJSON(){return {name:this.name,message:this.message,code:this.code,statusCode:this.statusCode,context:this.context,suggestion:this.suggestion,timestamp:this.timestamp,stack:this.stack}}};function Q(r){if(o.break(),o.error("Something went wrong. Please check the error below for more details."),o.error("If the problem persists, please open an issue on GitHub."),o.error(""),typeof r=="string"&&(o.error(r),o.break(),process.exit(1)),r instanceof U&&(r.message&&(o.error(r.cause?"Error:":"Message:"),o.error(r.message)),r.cause&&(o.error(`
|
|
19
|
+
Message:`),o.error(r.cause)),r.suggestion&&(o.error(`
|
|
20
|
+
Suggestion:`),o.error(r.suggestion)),o.break(),process.exit(1)),r instanceof z$1.ZodError){o.error("Validation failed:");for(let[e,t]of Object.entries(r.flatten().fieldErrors))o.error(`- ${d.info(e)}: ${t}`);o.break(),process.exit(1);}r instanceof Error&&(o.error(r.message),o.break(),process.exit(1)),o.break(),process.exit(1);}var k=class{config;async load(){try{return this.config=we(),this.config||(o.error(""),Q(new Error("Configuration not found")),process.exit(1)),this.config}catch{o.error(""),Q(new Error("Something went wrong. Please try again.")),process.exit(1);}}getPackageManager(){if(!this.config)throw new Error("Configuration not loaded");return this.config.packageManager||"npm"}validate(){return !!this.config}getComponentsPath(){if(!this.config)throw new Error("Configuration not loaded");return this.config.components.path}getThemePath(){if(!this.config)throw new Error("Configuration not loaded");return this.config.css.velyx}getJsEntryPath(){if(!this.config)throw new Error("Configuration not loaded");return this.config.js?.entry??""}getTheme(){if(!this.config)throw new Error("Configuration not loaded");return this.config.theme}};var Fe=promisify(exec);function gt(r){if(r.startsWith("@")){let t=r.lastIndexOf("@");return t>r.indexOf("/")?{name:r.slice(0,t),version:r.slice(t+1)}:{name:r,version:null}}let e=r.lastIndexOf("@");return e>0?{name:r.slice(0,e),version:r.slice(e+1)}:{name:r,version:null}}function $e(r){let e=r.indexOf(":");if(e>0)return {name:r.slice(0,e),version:r.slice(e+1)};let t=r.lastIndexOf("@");return t>0?{name:r.slice(0,t),version:r.slice(t+1)}:{name:r,version:null}}function yt(r){let e=$e(r);return e.version?`${e.name}:${e.version}`:e.name}function je(r){try{return JSON.parse(readFileSync(r,"utf8"))}catch{return null}}var J=class{fileSystem;constructor(e){this.fileSystem=e??new f;}async installDependencies(e,t){let n=[],s=[];e.npm&&e.npm.length>0&&n.push(this.installNpmDependencies(e.npm,t)),e.composer&&e.composer.length>0&&s.push(this.installComposerDependencies(e.composer)),await Promise.allSettled([...n,...s]);}async installNpmDependencies(e,t){if(!await this.fileSystem.fileExists("package.json")){o.warn("No package.json found, skipping npm dependencies");return}let n=await this.filterMissingNpmDependencies(e);if(n.length===0){o.info("All npm dependencies already installed");return}let s=this.getNpmInstallCommand(t,n);try{o.info(`Installing npm dependencies: ${n.join(", ")}`);let{stdout:i,stderr:a}=await Fe(s,{cwd:process.cwd(),timeout:12e4});i&&process.env.NODE_ENV!=="test"&&console.log(i),a&&!a.includes("WARN")&&o.warn(`npm install warnings: ${a}`),o.success(`Installed ${n.length} npm dependencies`);}catch(i){throw o.error(`Failed to install npm dependencies: ${i.message}`),i}}async installComposerDependencies(e){if(!await this.fileSystem.fileExists("composer.json")){o.warn("No composer.json found, skipping composer dependencies");return}let t=await this.filterMissingComposerDependencies(e);if(t.length===0){o.info("All composer dependencies already installed");return}let n=t.map(yt);try{o.info(`Installing composer dependencies: ${n.join(", ")}`);let{stdout:s,stderr:i}=await Fe(`composer require ${n.join(" ")}`,{cwd:process.cwd(),timeout:3e5});s&&process.env.NODE_ENV!=="test"&&console.log(s),i&&o.warn(`composer require warnings: ${i}`),o.success(`Installed ${t.length} composer dependencies`);}catch(s){throw o.error(`Failed to install composer dependencies: ${s.message}`),s}}getNpmInstallCommand(e,t){switch(e){case "pnpm":return `pnpm add ${t.join(" ")}`;case "yarn":return `yarn add ${t.join(" ")}`;case "bun":return `bun add ${t.join(" ")}`;default:return `npm install ${t.join(" ")}`}}async filterMissingNpmDependencies(e){let t=je("package.json");if(!t)return [...e];let n={...t.dependencies,...t.devDependencies};return e.filter(s=>{let i=gt(s),a=n[i.name];return a?i.version?a!==i.version:false:true})}async filterMissingComposerDependencies(e){let t=je("composer.json");if(!t)return [...e];let n={...t.require,...t["require-dev"]};return e.filter(s=>{let i=$e(s),a=n[i.name];return a?i.version?a!==i.version:false:true})}};function xt(r){return r.endsWith(".blade.php")?"blade":r.endsWith(".js")?"js":r.endsWith(".css")?"css":null}var q=class{constructor(e,t,n){this.registryService=e;this.configManager=n;if(this.fileSystem=t??new f,this.dependencyService=new J(this.fileSystem),!this.configManager)throw new Error("ConfigManager is required")}fileSystem;dependencyService;async addComponents(e){let t={added:[],skipped:[],failed:[]};for(let n of e)try{let s=await this.addComponent(n);t.added.push(...s.added),t.skipped.push(...s.skipped),t.failed.push(...s.failed);}catch(s){t.failed.push({name:n,error:s.message});}return t}async addComponent(e){let t={added:[],skipped:[],failed:[]},n=await this.registryService.fetchComponent(e,{includeFiles:true});await this.registryService.resolveDependencies(n);let s=this.buildDependencies(n);if(s){let c=this.configManager.getPackageManager();try{await this.dependencyService.installDependencies(s,c);}catch(l){o.warn(`Failed to install dependencies for ${e}: ${l.message}`);}}let i=[],a=await this.fetchComponentWithFiles(e);for(let[c,l]of Object.entries(a.files)){let m=this.getDestinationPath(c),u=xt(c),se=await this.fileSystem.fileExists(m);if(se){let oe=await this.handleFileConflict(c);if(oe==="skip"){t.skipped.push(m);continue}else oe==="cancel"&&(o.error("Cancelled."),process.exit(0));}i.push({componentName:e,filePath:c,fileType:u,destPath:m,content:l,existedBefore:se});}if(i.length>0)try{await this.applyFileBatch(i),i.forEach(l=>t.added.push(l.destPath));let c=new Set(i.filter(l=>l.fileType==="js").map(l=>l.componentName));for(let l of Array.from(c))await this.autoImportJs(l);}catch(c){i.forEach(l=>t.failed.push({name:l.destPath,error:c.message}));}return t}buildDependencies(e){let t={};e.requires?.composer?.length&&(t.composer=Array.from(new Set(e.requires.composer)));let n=e.requires?.npm?[...e.requires.npm]:[];return e.requires_alpine&&!n.some(s=>s==="alpinejs"||s.startsWith("alpinejs@"))&&n.unshift("alpinejs"),n.length>0&&(t.npm=Array.from(new Set(n))),Object.keys(t).length>0?t:null}async fetchComponentWithFiles(e){let t=await this.registryService.fetchComponent(e,{includeFiles:true});return "files"in t?t:{files:{}}}async autoImportJs(e){try{let t=this.configManager?.getJsEntryPath();if(!t||!await this.fileSystem.fileExists(t))return;let n=`./ui/${e}`;ge(t,e,n),o.success(`Auto-imported ${e} into ${t}`);}catch(t){o.warn(`Failed to auto-import JS for ${e}: ${t.message}`);}}getDestinationPath(e){return e}async handleFileConflict(e){let{action:t}=await be({type:"select",name:"action",message:`File "${e}" already exists. What do you want to do?`,choices:[{title:"Skip",value:"skip"},{title:"Overwrite",value:"overwrite"},{title:"Cancel",value:"cancel"}],initial:0},{onCancel:()=>{o.error("Cancelled."),process.exit(0);}});return t}async applyFileBatch(e){let t=[],n=[];try{for(let s of e){let i=`${s.destPath}.velyx-tmp`;await this.fileSystem.writeFile(i,s.content),t.push(i);}for(let s of e){if(!s.existedBefore)continue;if(!ie(s.destPath))throw new Error(`Failed to create backup for ${s.destPath}`);n.push(s.destPath);}for(let s of e){let i=`${s.destPath}.velyx-tmp`;await h.move(i,s.destPath,{overwrite:!0});}n.forEach(s=>T(s));}catch(s){for(let i of e)await this.fileSystem.fileExists(i.destPath)&&await h.remove(i.destPath),i.existedBefore&&N(i.destPath);for(let i of t)await this.fileSystem.fileExists(i)&&await h.remove(i);throw s}}};var B=class{constructor(e,t,n){this.registryService=e;this.configManager=t;this.componentService=n??new q(e,new f,t);}componentService;validateInitialization(){if(!this.configManager.validate())throw new Error("Velyx is not initialized")}validateComponents(e,t){for(let n of e)if(!t.components.find(i=>i.name===n))throw new Error(`Component "${n}" not found`)}async getAvailableComponents(){return await this.registryService.fetchRegistry()}async addComponents(e){return await this.componentService.addComponents(e)}displayResults(e){e.added.forEach(t=>o.success(`Added ${t}`)),e.skipped.forEach(t=>o.warn(`Skipped ${t}`)),e.failed.forEach(({name:t,error:n})=>o.error(`Failed to add ${t}: ${n}`));}displayNextSteps(e){e.added.length!==0&&console.log(`
|
|
21
|
+
\u{1F389} Happy coding! Enjoy building beautiful components!`);}};var W=class extends Error{constructor(t,n){super(t);this.cause=n;this.name="RegistryError";}},p=class extends W{constructor(e,t){super(e,t),this.name="NetworkError";}},y=class extends W{constructor(e,t){super(`Component "${e}" not found`,t),this.name="ComponentNotFoundError";}};var x={maxRetries:3,initialDelay:1e3,backoffFactor:2,maxDelay:1e4,retryableStatusCodes:[408,429,500,502,503,504],timeout:3e4,headers:{}};function te(r){return new Promise(e=>setTimeout(e,r))}function re(r,e,t,n){let s=e*Math.pow(t,r);return Math.min(s,n)}function vt(r,e){return e.includes(r)}async function wt(r,e){let{timeout:t=x.timeout,headers:n={}}=e,s=new AbortController,i=setTimeout(()=>s.abort(),t);try{return await fetch(r,{signal:s.signal,headers:{"User-Agent":"Velyx-CLI",...n}})}catch(a){throw a instanceof Error&&a.name==="AbortError"?new p(`Request timeout after ${t}ms for ${r}`,a):a}finally{clearTimeout(i);}}var G=class{async fetch(e,t={}){let n={maxRetries:t.maxRetries??x.maxRetries,initialDelay:t.initialDelay??x.initialDelay,backoffFactor:t.backoffFactor??x.backoffFactor,maxDelay:t.maxDelay??x.maxDelay,retryableStatusCodes:t.retryableStatusCodes??x.retryableStatusCodes,timeout:t.timeout??x.timeout,headers:t.headers??x.headers},s=null;for(let i=0;i<=n.maxRetries;i++)try{let a=await wt(e,{...t,timeout:n.timeout,headers:n.headers});if(a.ok||!vt(a.status,n.retryableStatusCodes))return a;if(console.log("Attempt: ",i),i===n.maxRetries)throw new p(`Request failed after ${n.maxRetries+1} attempts: ${a.status} ${a.statusText}`);let c=re(i,n.initialDelay,n.backoffFactor,n.maxDelay);await te(c);}catch(a){if(s=a,a instanceof p&&a.message.includes("timeout")){if(i===n.maxRetries)throw a;let l=re(i,n.initialDelay,n.backoffFactor,n.maxDelay);await te(l);continue}if(i===n.maxRetries)throw s instanceof p?s:new p(`Request failed after ${n.maxRetries+1} attempts: ${s.message}`,s);let c=re(i,n.initialDelay,n.backoffFactor,n.maxDelay);await te(c);}throw new p(`Request failed after ${n.maxRetries+1} attempts: ${s?.message??"Unknown error"}`,s??void 0)}async fetchJson(e,t){let n=await this.fetch(e,t);if(!n.ok)throw new p(`Failed to fetch JSON from ${e}: ${n.status} ${n.statusText}`);try{return await n.json()}catch(s){throw new p(`Failed to parse JSON from ${e}: ${s.message}`,s)}}async fetchText(e,t){let n=await this.fetch(e,t);if(!n.ok)throw new p(`Failed to fetch text from ${e}: ${n.status} ${n.statusText}`);try{return await n.text()}catch(s){throw new p(`Failed to read text from ${e}: ${s.message}`,s)}}};var Oe=()=>process.env.VELYX_REGISTRY_URL?process.env.VELYX_REGISTRY_URL.replace(/\/$/,""):"http://velyx.test/api/v1";var z=class{httpService;baseUrl;constructor(e){this.httpService=e??new G,this.baseUrl=Oe();}async fetchRegistry(){try{let e=await this.httpService.fetch(`${this.baseUrl}/components`);if(!e.ok)throw new p(`Failed to fetch registry: ${e.status} ${e.statusText}`);let t=await this.httpService.fetchJson(`${this.baseUrl}/components`);return {components:Object.values(t.data),count:t.count}}catch(e){throw e instanceof p?e:new p(`Failed to fetch registry: ${e.message}`)}}async fetchComponent(e,t){try{let n=this.buildComponentUrl(e,t),s=await this.httpService.fetch(n);if(s.status===404)throw new y(e);if(!s.ok)throw new p(`Failed to fetch component: ${s.status} ${s.statusText}`);let i=await this.httpService.fetchJson(n);return t?.includeFiles?i.data:this.convertRegistryComponentToMeta(i.data)}catch(n){throw n instanceof y||n instanceof p?n:new p(`Failed to fetch component "${e}": ${n.message}`)}}async fetchFile(e,t){try{let n=e.split("/").pop()||e,s=await this.httpService.fetch(`${this.baseUrl}/components/${n}`);if(s.status===404)throw new y(n);if(!s.ok)throw new p(`Failed to fetch component: ${s.status} ${s.statusText}`);let a=(await this.httpService.fetchJson(`${this.baseUrl}/components/${n}`)).files[t];if(a===void 0)throw new p(`File "${t}" not found in component "${n}"`);return a}catch(n){throw n instanceof y||n instanceof p?n:new p(`Failed to fetch file "${t}": ${n.message}`)}}async resolveDependencies(e){let t=new Set,n=[];return await(async i=>{t.has(i.name)||(t.add(i.name),n.push(i));})(e),n}convertRegistryComponentToMeta(e){return {name:e.name,description:e.description,latest:e.latest,versions:e.versions,requires_alpine:e.requires_alpine,requires:e.requires,categories:e.categories,laravel:e.laravel}}buildComponentUrl(e,t){let n=new URLSearchParams;t?.version&&n.append("version",t.version),t?.includeFiles&&n.append("include","files");let s=n.toString();return `${this.baseUrl}/components/${e}${s?`?${s}`:""}`}async getComponentVersions(e){try{let t=await this.httpService.fetch(`${this.baseUrl}/components/${e}/versions`);if(t.status===404)throw new y(e);if(!t.ok)throw new p(`Failed to fetch component versions: ${t.status} ${t.statusText}`);return await this.httpService.fetchJson(`${this.baseUrl}/components/${e}/versions`)}catch(t){throw t instanceof y||t instanceof p?t:new p(`Failed to fetch versions for "${e}": ${t.message}`)}}};var I=class{velyxService;constructor(){this.velyxService=new z;}async fetchRegistry(){return await g.withTask("Fetching registry...",()=>this.velyxService.fetchRegistry(),void 0,"Failed to fetch registry")}async fetchComponent(e,t){let n=t?.includeFiles?`Fetching component "${e}" with files...`:`Fetching component "${e}" metadata...`;return await g.withTask(n,()=>this.velyxService.fetchComponent(e,t),void 0,`Failed to fetch component "${e}"`)}async resolveDependencies(e){return await g.withTask("Resolving dependencies...",()=>this.velyxService.resolveDependencies(e),void 0,"Failed to resolve dependencies")}};async function St(r,e){if(r.all)return [...e];if(r.components?.length)return r.components;o.info("Use arrow keys and space to select, then press enter.");let{components:t}=await be({type:"multiselect",name:"components",message:"Which components would you like to add?",hint:"Space to select. A to toggle all. Enter to submit.",instructions:false,choices:e.map(s=>({title:s,value:s,selected:r.all?true:r.components?.includes(s)}))});t?.length||(o.warn("No components selected. Exiting."),o.info(""),process.exit(1));let n=z$1.array(z$1.string()).safeParse(t);return n.success||(o.error("Something went wrong. Please try again."),process.exit(1)),n.data}async function Ne(r){process.chdir(r.cwd);let e=new k;await e.load();let t=new I,n=new B(t,e);try{n.validateInitialization();}catch{o.error("Velyx is not initialized"),o.log("Run velyx init first"),process.exit(1);}let s=await n.getAvailableComponents(),i=s.components.map(l=>l.name).sort((l,m)=>l.localeCompare(m)),a=await St(r,i);try{n.validateComponents(a,s);}catch(l){o.error(l.message),o.log("Run velyx list to see available components"),process.exit(1);}let c=await n.addComponents(a);n.displayResults(c),n.displayNextSteps(c);}var kt=z$1.object({components:z$1.array(z$1.string()).optional(),cwd:z$1.string(),all:z$1.boolean()}),Te=new Command().name("add").argument("[components...]","Names of components to add").option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",process.cwd()).option("-a, --all","add all available components",false).description("Add one or more UI components to your Laravel project").action(async(r,e)=>{let t=new C;try{let n={components:r,cwd:w.resolve(e.cwd),all:!!e.all},s=kt.parse(n);await Ne(s);}catch(n){t.handle(n,"add command"),process.exit(1);}});function Et(r,e){if(!e)return [...r];let t=e.toLowerCase();return r.filter(n=>{let s=n.name.toLowerCase().includes(t),i=n.description?n.description.toLowerCase().includes(t):false,a=n.categories?n.categories.some(c=>c.toLowerCase().includes(t)):false;return s||i||a})}function Ft(r,e,t){let n=Math.max(0,e??0);return t===void 0?r.slice(n):r.slice(n,n+Math.max(0,t))}async function De(r){let n=[...(await new I().fetchRegistry()).components].sort((c,l)=>c.name.localeCompare(l.name)),s=Et(n,r.query),i=Ft(s,r.offset,r.limit);if(r.json){console.log(JSON.stringify({total:s.length,count:i.length,offset:r.offset??0,limit:r.limit??null,components:i},null,2));return}if(i.length===0){o.warn("No components found.");return}console.log(v.bold(`
|
|
22
|
+
Available components:`)),console.log("");let a=new It({head:[v.bold("Component"),v.bold("Description"),v.bold("Categories")],colWidths:[24,50,24],wordWrap:true,style:{head:[],border:[]}});for(let c of i)a.push([v.cyan(c.name),v.white(c.description||"No description"),v.gray(c.categories?.join(", ")||"-")]);console.log(a.toString()),console.log(""),o.info(`Run ${v.green("velyx add <component>")} to add one.`);}var Ot=z$1.object({cwd:z$1.string(),query:z$1.string().optional(),limit:z$1.number().optional(),offset:z$1.number().optional(),json:z$1.boolean()}),Ve=new Command().name("list").alias("search").description("List or search components from the registry").option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",process.cwd()).option("-q, --query <query>","query string").option("-l, --limit <number>","maximum number of items to display",void 0).option("-o, --offset <number>","number of items to skip",void 0).option("--json","output JSON",false).action(async r=>{let e=new C;try{let t=Ot.parse({cwd:w.resolve(r.cwd),query:r.query,limit:r.limit?Number.parseInt(r.limit,10):void 0,offset:r.offset?Number.parseInt(r.offset,10):void 0,json:!!r.json});process.chdir(t.cwd);let n=new k;try{await n.load();}catch{o.error("Velyx is not initialized"),o.log("Run velyx init first"),process.exit(1);}await De(t);}catch(t){e.handle(t,"list command"),process.exit(1);}});function Nt(){console.log(""),console.log(v.bold.cyan(` \u25BC VELYX CLI v${$.version}`)),console.log(v.gray(" Tailwind CSS v4+ components for Laravel")),console.log("");}var ne=new Command;ne.name("velyx").description("Velyx CLI: Copy UI components into your Laravel project").version($.version,"-v, --version","display the version number").hook("preAction",()=>{Nt();});ne.addCommand(Te).addCommand(Ee).addCommand(Ve);ne.parse(process.argv);//# sourceMappingURL=index.js.map
|
|
23
23
|
//# sourceMappingURL=index.js.map
|