velyx 1.0.0 → 1.0.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/README.md +16 -16
- package/dist/index.js +17 -17
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Velyx CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Velyx CLI is a command-line tool for adding UI components to Laravel projects.
|
|
4
4
|
|
|
5
5
|
It delivers composable UI components built with **Blade**, **Alpine.js**, and **Tailwind CSS v4**.
|
|
6
|
-
Inspired by shadcn,
|
|
6
|
+
Inspired by shadcn, Velyx gives you the code, not a dependency.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## What
|
|
10
|
+
## What Velyx is
|
|
11
11
|
|
|
12
12
|
- A **code delivery tool** for Laravel UI components
|
|
13
13
|
- A way to **copy components into your project**
|
|
14
14
|
- A workflow that keeps **you in control of your code**
|
|
15
15
|
|
|
16
|
-
## What
|
|
16
|
+
## What Velyx is not
|
|
17
17
|
|
|
18
18
|
- Not a UI framework
|
|
19
19
|
- Not a runtime dependency
|
|
@@ -26,7 +26,7 @@ Once components are added, they belong to your project.
|
|
|
26
26
|
|
|
27
27
|
## Requirements
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Velyx assumes a modern Laravel setup:
|
|
30
30
|
|
|
31
31
|
- Laravel
|
|
32
32
|
- Blade
|
|
@@ -39,12 +39,12 @@ Tailwind v3 is not supported.
|
|
|
39
39
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Velyx can be executed without installation.
|
|
43
43
|
|
|
44
|
-
### Initialize
|
|
44
|
+
### Initialize Velyx in a project
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npx
|
|
47
|
+
npx velyx init
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
This command:
|
|
@@ -57,10 +57,10 @@ This command:
|
|
|
57
57
|
### Add a component
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
npx
|
|
60
|
+
npx velyx add button
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
Velyx will:
|
|
64
64
|
|
|
65
65
|
- fetch the component from the registry
|
|
66
66
|
- resolve its dependencies
|
|
@@ -77,18 +77,18 @@ resources/views/components/ui
|
|
|
77
77
|
### List available components
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
npx
|
|
80
|
+
npx velyx list
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
---
|
|
84
84
|
|
|
85
85
|
## How updates work
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Velyx does **not** update your code automatically.
|
|
88
88
|
|
|
89
89
|
If a component changes in the registry and you want the new version:
|
|
90
90
|
|
|
91
|
-
- run `
|
|
91
|
+
- run `velyx add <component>` again
|
|
92
92
|
- review the changes
|
|
93
93
|
- decide what to keep
|
|
94
94
|
|
|
@@ -98,12 +98,12 @@ This is intentional.
|
|
|
98
98
|
|
|
99
99
|
## Philosophy
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
Velyx follows a simple principle:
|
|
102
102
|
|
|
103
103
|
> You own your UI code.
|
|
104
104
|
|
|
105
105
|
There are no hidden abstractions and no vendor lock-in.
|
|
106
|
-
|
|
106
|
+
Velyx exists to help you move faster, not to take control away from you.
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
@import "./
|
|
3
|
-
@import "./
|
|
4
|
-
`,R.writeFileSync(r,e,"utf8"));}var
|
|
5
|
-
`),a=-1;for(let c=0;c<i.length;c++)i[c]?.startsWith("import ")&&(a=c);i.splice(a+1,0,
|
|
6
|
-
`);let l=`Alpine.data('${e}', ${e});`;
|
|
7
|
-
${l}`)):
|
|
1
|
+
import v from'chalk';import {Command}from'commander';import b from'path';import w from'fs-extra';import Se from'prompts';import R,{promises}from'fs';import {cyan,green,yellow,red}from'kleur/colors';import {exec}from'child_process';import {promisify}from'util';import Ze from'ora';import {z as z$1}from'zod';import bt from'cli-table3';var H=".bak";function ae(r){if(!w.existsSync(r))return null;let e=`${r}${H}`;try{return w.renameSync(r,e),e}catch(t){return console.error(`Failed to create backup of ${r}: ${t}`),null}}function N(r){let e=`${r}${H}`;if(!w.existsSync(e))return false;try{return w.renameSync(e,r),!0}catch(t){return console.error(`Warning: Could not restore backup file ${e}: ${t}`),false}}function T(r){let e=`${r}${H}`;if(!w.existsSync(e))return false;try{return w.unlinkSync(e),!0}catch{return false}}var u=class{async fileExists(e){try{return await promises.access(e),!0}catch{return false}}async writeFile(e,t){await promises.mkdir(b.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 le(){return R.existsSync("composer.json")&&R.existsSync("artisan")}function pe(){try{return JSON.parse(R.readFileSync("package.json","utf8"))}catch{return null}}function me(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 Ae(){try{let r=JSON.parse(R.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 Le(){let r="resources/views/layouts";if(!R.existsSync(r))return false;try{let e=R.readdirSync(r,{recursive:!0});for(let t of e)if(t.endsWith(".blade.php")&&R.readFileSync(b.join(r,t),"utf8").toLowerCase().includes("alpine"))return !0}catch{return false}return false}function de(){return Ae()||Le()}function D(){return R.existsSync("pnpm-lock.yaml")||R.existsSync("pnpm-lock.yml")?"pnpm":R.existsSync("yarn.lock")?"yarn":R.existsSync("package-lock.json")?"npm":R.existsSync("bun.lock")?"bun":"npm"}var Ue=["resources/css/app.css","resources/css/app.scss","resources/css/main.css","resources/css/style.css","resources/css/styles.css"];function fe(){for(let r of Ue)if(R.existsSync(r))return {path:r,content:R.readFileSync(r,"utf8")};return null}function K(r){return /@import\s+["']tailwindcss["']/.test(r)}function ue(r){let e=R.readFileSync(r,"utf8");e.includes('@import "./velyx.css"')||(K(e)?e=e.replace(/@import\s+["']tailwindcss["'];?/,t=>`${t}
|
|
2
|
+
@import "./velyx.css";`):e+=`
|
|
3
|
+
@import "./velyx.css";
|
|
4
|
+
`,R.writeFileSync(r,e,"utf8"));}var Je=["resources/js/app.js","resources/js/main.js","resources/js/index.js"];function ge(){for(let r of Je)if(R.existsSync(r))return {path:r,content:R.readFileSync(r,"utf8")};return null}function ye(r,e,t){let n=R.readFileSync(r,"utf8"),o=`import ${e} from '${t}'`;if(n.includes(o)||n.includes(`import ${e} from "${t}"`))return;let i=n.split(`
|
|
5
|
+
`),a=-1;for(let c=0;c<i.length;c++)i[c]?.startsWith("import ")&&(a=c);i.splice(a+1,0,o),n=i.join(`
|
|
6
|
+
`);let l=`Alpine.data('${e}', ${e});`;n.includes("document.addEventListener('alpine:init'")?n.includes(l)||(n=n.replace(/document\.addEventListener\('alpine:init',\s*\(\)\s*=>\s*\{/,c=>`${c}
|
|
7
|
+
${l}`)):n+=`
|
|
8
8
|
|
|
9
9
|
document.addEventListener('alpine:init', () => {
|
|
10
10
|
${l}
|
|
11
11
|
});
|
|
12
|
-
`,R.writeFileSync(r,
|
|
13
|
-
`);R.writeFileSync(e,i,{encoding:"utf-8",flag:"wx"});}function
|
|
14
|
-
`,"utf8");}function
|
|
15
|
-
---`),s.success("Laravel project detected"),s.success("Tailwind CSS v4 detected"),s.success(`Theme selected: ${e.theme}`),s.success(`Package manager: ${e.packageManager}`),s.success("UI components directory ready"),t.jsFile&&s.success("Main JS file detected"),s.success(
|
|
16
|
-
Next steps:`),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
|
-
|
|
12
|
+
`,R.writeFileSync(r,n,"utf8");}function qe(r){let e=r;for(let t=0;t<4;t+=1){let n=b.join(e,"colors");if(R.existsSync(n))return n;let o=b.join(e,"src/colors");if(R.existsSync(o))return o;let i=b.dirname(e);if(i===e)break;e=i;}return b.join(r,"colors")}var Be=process.argv[1]?b.dirname(b.resolve(process.argv[1])):process.cwd(),X=qe(Be);function we(){return R.existsSync(X)?R.readdirSync(X).filter(e=>e.endsWith(".json")).map(e=>{let t=b.join(X,e),n=R.readFileSync(t,"utf-8");return JSON.parse(n)}).filter(e=>!!e?.name).sort((e,t)=>e.name.localeCompare(t.name)):[]}function A(){return we()}function We(r){return we().find(e=>e.name===r)}function he(r){return Object.entries(r).map(([e,t])=>` --${e}: ${t};`)}function xe(r,e){let t=We(r);if(!t)throw new Error(`Theme "${r}" not found in colors registry.`);let n=he(t.cssVars.light),o=he(t.cssVars.dark),i=[":root {",...n,"}","",".dark {",...o,"}",""].join(`
|
|
13
|
+
`);R.writeFileSync(e,i,{encoding:"utf-8",flag:"wx"});}var m={error:red,warn:yellow,info:cyan,success:green};var s={error(...r){console.log(m.error(r.join(" ")));},warn(...r){console.log(m.warn(r.join(" ")));},info(...r){console.log(m.info(r.join(" ")));},success(...r){console.log(m.success(r.join(" ")));},log(...r){console.log(r.join(" "));},break(){console.log("");}};function ve(r){R.writeFileSync("velyx.json",JSON.stringify(r,null,2)+`
|
|
14
|
+
`,"utf8");}function be(){return R.existsSync("velyx.json")||(s.error("Velyx configuration not found."),process.exit(1)),JSON.parse(R.readFileSync("velyx.json","utf8"))}var P={version:"1.0.3"};var L=class{constructor(e){this.fileSystem=e;}validateEnvironment(){if(!le())throw new Error("No Laravel project detected");let e=pe();if(!e||!me(e))throw new Error("Tailwind CSS v4 was not detected");let t=de(),n=D(),o=fe(),i=ge(),a=o?K(o.content):false;return {isLaravel:true,hasTailwindV4:true,hasAlpine:t,detectedPackageManager:n,cssFile:o,jsFile:i,canInjectCss:a}}displayEnvironmentInfo(e){e.hasAlpine?s.success("Alpine.js detected - components will be fully interactive"):(s.warn("Alpine.js not detected"),s.log(`Install Alpine.js: ${e.detectedPackageManager} install alpinejs`)),e.cssFile?e.canInjectCss||(s.warn("Tailwind import not found in CSS"),s.log("Velyx styles will not be auto-imported")):(s.warn("No main CSS file found"),s.log("Styles will be created but not auto-imported")),e.jsFile||(s.warn("No main JS file found"),s.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),R.existsSync(t))s.info("velyx.css already exists");else try{xe(e,t),s.success("Velyx theme created"),s.info(t);}catch(o){throw new Error(`Failed to create theme file: ${o.message}`)}}async injectStylesImport(e){ue(e),s.success("Velyx styles imported"),s.info(e);}async generateConfig(e,t){let n={version:P.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),s.success("velyx.json config generated");}displaySummary(e,t,n){console.log(`
|
|
15
|
+
---`),s.success("Laravel project detected"),s.success("Tailwind CSS v4 detected"),s.success(`Theme selected: ${e.theme}`),s.success(`Package manager: ${e.packageManager}`),s.success("UI components directory ready"),t.jsFile&&s.success("Main JS file detected"),s.success(n?"Styles import complete":"Styles import pending"),s.success("velyx.json created"),console.log(`
|
|
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 y={start(r){return Ze(r).start()},async withTask(r,e,t,n){let o=this.start(r);try{let i=await e();return t?o.succeed(t):o.stop(),i}catch(i){throw o.fail(n||"Operation failed"),i}}};var tt=promisify(exec);async function rt(r){try{let e=b.resolve(r,"composer.json"),t=b.resolve(r,"package.json");if(!w.existsSync(e))return null;let n=await w.readJson(e);if(!(n.require?.["laravel/framework"]||n.require?.["illuminate/foundation"]))return null;let i=process.cwd();process.chdir(r);let a=D();console.log(`Detected package manager: ${a}`),process.chdir(i);let l={name:n.name||b.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(w.existsSync(t)){let g=await w.readJson(t);l.hasAlpine=!!(g.dependencies?.alpinejs||g.devDependencies?.alpinejs),l.hasVite=!!g.devDependencies?.vite;}let c=b.resolve(r,"resources/views"),d=b.resolve(r,"resources/js");return w.existsSync(c)&&(l.paths.views="resources/views"),w.existsSync(d)&&(l.paths.assets="resources/js"),l}catch{return null}}async function Ce(r){let e={};if(!w.existsSync(r.cwd))return e.MISSING_DIR=true,{errors:e,projectInfo:null};let t=y.start("Checking project environment..."),n=b.resolve(r.cwd,"velyx.json");if(w.existsSync(n)&&!r.force){t.fail(),s.break();let{action:l}=await Se({type:"select",name:"action",message:`A ${m.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});l==="exit"&&(s.log("Operation cancelled."),process.exit(0)),l==="keep"&&(s.log("Keeping existing configuration."),process.exit(0)),s.log("Re-initializing Velyx configuration...");}let o=await rt(r.cwd);(!o||o.framework.name!=="laravel")&&(e.UNSUPPORTED_PROJECT=true,t.fail(),s.break(),s.error(`We could not detect a supported Laravel project at ${m.info(r.cwd)}.
|
|
18
|
+
Velyx is designed to work with Laravel projects.`),s.break(),process.exit(1)),t.succeed(`Found ${m.info(o.framework.label)} project`);let i=y.start("Checking Alpine.js...");if(o.hasAlpine)i.succeed("Alpine.js found");else {i.fail(),s.break(),s.warn("Alpine.js is required but not found in your project.");let{installAlpine:l}=await Se({type:"confirm",name:"installAlpine",message:"Would you like to install Alpine.js now?",initial:true});if(l){let c=o.packageManager,d=y.start(`Installing Alpine.js with ${c}...`);try{await tt(`${c} install alpinejs`,{cwd:r.cwd}),d.succeed("Alpine.js installed successfully"),o.hasAlpine=!0;}catch(g){d.fail(`Failed to install Alpine.js: ${g.message}`),s.error(`Please install Alpine.js manually: ${m.info(`${c} install alpinejs`)}`),s.break(),process.exit(1);}}else {let c=o.packageManager;s.error(`Alpine.js is required. Install it with: ${m.info(`${c} install alpinejs`)}`),s.break(),process.exit(1);}}let a=y.start("Checking build tools...");return o.hasVite?a.succeed("Vite found"):(s.warn("Vite not found. Using Vite is recommended for better development experience."),a.warn("Vite not found (but optional)")),Object.keys(e).length>0&&(s.break(),process.exit(1)),{errors:e,projectInfo:o}}var ke=z$1.object({baseColor:z$1.string().optional(),yes:z$1.boolean(),defaults:z$1.boolean(),force:z$1.boolean(),cwd:z$1.string(),silent:z$1.boolean()});async function nt(){let r=A();r.length===0&&(s.error("No base colors available."),process.exit(1));let{theme:e}=await Se({type:"select",name:"theme",message:"Choose a base color theme",choices:r.map(t=>({title:t.label,value:t.name}))},{onCancel:()=>{s.error("Theme selection aborted"),process.exit(1);}});return e}async function ot(){let{shouldImport:r}=await Se({type:"confirm",name:"shouldImport",message:"Import Velyx styles into your main CSS file?",initial:true},{onCancel:()=>false});return !!r}function st(r){if(!r.baseColor)return;let t=A().find(n=>n.name===r.baseColor);if(t)return t.name;s.warn(`Unknown base color "${r.baseColor}".`);}async function Ee(r,e){e||(e=(await Ce(r)).projectInfo),process.chdir(r.cwd);let t=new u,n=new L(t);try{let o=n.validateEnvironment();n.displayEnvironmentInfo(o);let i=e.packageManager,a=A(),l=a.find(g=>g.name==="neutral")?.name??a[0]?.name,c=st(r);c||(c=r.defaults&&l?l:await nt()),c||(s.error("No base color available."),process.exit(1)),await n.createComponentsDirectory(),await n.createThemeFile(c);let d=!1;o.cssFile&&o.canInjectCss&&(r.defaults||await ot())&&(await n.injectStylesImport(o.cssFile.path),d=!0),await n.generateConfig({packageManager:i,theme:c,importStyles:d},o),n.displaySummary({packageManager:i,theme:c,importStyles:d},o,d);}catch(o){s.error(o.message),o instanceof Error&&(o.message.includes("Laravel project")?s.log("Run velyx init at the root of a Laravel project"):o.message.includes("Tailwind")&&s.log(`Velyx requires ${m.info("Tailwind CSS v4+")}`)),process.exit(1);}}process.on("exit",r=>{let e=b.resolve(process.cwd(),"velyx.json");return r===0?T(e):N(e)});var Fe=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("-y, --yes","skip confirmation prompt.",true).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()).option("-s, --silent","mute output.",false).action(async r=>{let e=ke.parse({baseColor:r.baseColor,yes:!!r.yes,defaults:!!r.defaults,force:!!r.force,cwd:b.resolve(r.cwd),silent:!!r.silent});await Ee(e);});var Q=class extends Error{constructor(t,n,o){super(t);this.code=n;this.context=o;this.name="VelyxError";}},E=class{handle(e,t){e instanceof Q?(console.error(`[${e.code}] ${e.message}`),e.context&&console.error("Context:",e.context)):console.error(`Unexpected error in ${t}: ${e.message}`);}};var at={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||at.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 ee(r){if(s.break(),s.error("Something went wrong. Please check the error below for more details."),s.error("If the problem persists, please open an issue on GitHub."),s.error(""),typeof r=="string"&&(s.error(r),s.break(),process.exit(1)),r instanceof U&&(r.message&&(s.error(r.cause?"Error:":"Message:"),s.error(r.message)),r.cause&&(s.error(`
|
|
19
19
|
Message:`),s.error(r.cause)),r.suggestion&&(s.error(`
|
|
20
|
-
Suggestion:`),s.error(r.suggestion)),s.break(),process.exit(1)),r instanceof z$1.ZodError){s.error("Validation failed:");for(let[e,t]of Object.entries(r.flatten().fieldErrors))s.error(`- ${m.info(e)}: ${t}`);s.break(),process.exit(1);}r instanceof Error&&(s.error(r.message),s.break(),process.exit(1)),s.break(),process.exit(1);}var
|
|
21
|
-
\u{1F389} Happy coding! Enjoy building beautiful components!`);
|
|
22
|
-
Available components:`)),console.log("");let a=new
|
|
20
|
+
Suggestion:`),s.error(r.suggestion)),s.break(),process.exit(1)),r instanceof z$1.ZodError){s.error("Validation failed:");for(let[e,t]of Object.entries(r.flatten().fieldErrors))s.error(`- ${m.info(e)}: ${t}`);s.break(),process.exit(1);}r instanceof Error&&(s.error(r.message),s.break(),process.exit(1)),s.break(),process.exit(1);}var I=class{config;async load(){try{return this.config=be(),this.config||(s.error(""),ee(new Error("Configuration not found")),process.exit(1)),this.config}catch{s.error(""),ee(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 J=promisify(exec);function mt(r){return r.startsWith("@")?r:r.replace("@",":")}var q=class{fileSystem;constructor(e){this.fileSystem=e??new u;}async installDependencies(e,t){let n=[],o=[];e.npm&&e.npm.length>0&&n.push(this.installNpmDependencies(e.npm,t)),e.composer&&e.composer.length>0&&o.push(this.installComposerDependencies(e.composer)),await Promise.allSettled([...n,...o]);}async installNpmDependencies(e,t){if(!this.fileSystem.fileExists("package.json")){s.warn("No package.json found, skipping npm dependencies");return}let n=await this.filterMissingNpmDependencies(e);if(n.length===0){s.info("All npm dependencies already installed");return}let o=this.getNpmInstallCommand(t,n);try{s.info(`Installing npm dependencies: ${n.join(", ")}`);let{stdout:i,stderr:a}=await J(o,{cwd:process.cwd(),timeout:12e4});i&&process.env.NODE_ENV!=="test"&&console.log(i),a&&!a.includes("WARN")&&s.warn(`npm install warnings: ${a}`),s.success(`Installed ${n.length} npm dependencies`);}catch(i){throw s.error(`Failed to install npm dependencies: ${i.message}`),i}}async installComposerDependencies(e){if(!this.fileSystem.fileExists("composer.json")){s.warn("No composer.json found, skipping composer dependencies");return}let t=e.map(mt),n=await this.filterMissingComposerDependencies(t);if(n.length===0){s.info("All composer dependencies already installed");return}try{s.info(`Installing composer dependencies: ${n.join(", ")}`);let{stdout:o,stderr:i}=await J(`composer require ${n.join(" ")}`,{cwd:process.cwd(),timeout:3e5});o&&process.env.NODE_ENV!=="test"&&console.log(o),i&&s.warn(`composer require warnings: ${i}`),s.success(`Installed ${n.length} composer dependencies`);}catch(o){throw s.error(`Failed to install composer dependencies: ${o.message}`),o}}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){try{let{stdout:t}=await J("npm list --json --depth=0",{cwd:process.cwd(),timeout:3e4}),n=JSON.parse(t),o=Object.keys({...n.dependencies,...n.devDependencies});return e.filter(i=>{let a=i.split("@")[0];return !o.includes(a)})}catch{return e}}async filterMissingComposerDependencies(e){try{let{stdout:t}=await J("composer show --installed --format=json",{cwd:process.cwd(),timeout:3e4}),o=JSON.parse(t).installed.map(i=>i.name);return e.filter(i=>{let a=i.split(":")[0];return !o.includes(a)})}catch{return e}}};function ft(r){return r.endsWith(".blade.php")?"blade":r.endsWith(".js")?"js":r.endsWith(".css")?"css":null}var B=class{constructor(e,t,n){this.registryService=e;this.configManager=n;if(this.fileSystem=t??new u,this.dependencyService=new q(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 o=await this.addComponent(n);t.added.push(...o.added),t.skipped.push(...o.skipped),t.failed.push(...o.failed);}catch(o){t.failed.push({name:n,error:o.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 o=this.buildDependencies(n);if(o){let l=this.configManager.getPackageManager();try{await this.dependencyService.installDependencies(o,l);}catch(c){s.warn(`Failed to install dependencies for ${e}: ${c.message}`);}}let i=[],a=await this.fetchComponentWithFiles(e);for(let[l,c]of Object.entries(a.files)){let d=this.getDestinationPath(l),g=ft(l),se=await this.fileSystem.fileExists(d);if(se){let ie=await this.handleFileConflict(l);if(ie==="skip"){t.skipped.push(`${e}/${l}`);continue}else ie==="cancel"&&(s.error("Cancelled."),process.exit(0));}i.push({componentName:e,filePath:l,fileType:g,destPath:d,content:c,existedBefore:se});}if(i.length>0)try{await this.applyFileBatch(i),i.forEach(c=>t.added.push(`${c.componentName}/${c.filePath}`));let l=new Set(i.filter(c=>c.fileType==="js").map(c=>c.componentName));for(let c of Array.from(l))await this.autoImportJs(c);}catch(l){i.forEach(c=>t.failed.push({name:`${c.componentName}/${c.filePath}`,error:l.message}));}return t}buildDependencies(e){let t={};return e.requires&&e.requires.length>0&&(t.composer=[...e.requires]),e.requires_alpine&&(t.npm=["alpinejs"]),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}`;ye(t,e,n),s.success(`Auto-imported ${e} into ${t}`);}catch(t){s.warn(`Failed to auto-import JS for ${e}: ${t.message}`);}}getDestinationPath(e){return e}async handleFileConflict(e){let{action:t}=await Se({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:()=>{s.error("Cancelled."),process.exit(0);}});return t}async applyFileBatch(e){let t=[],n=[];try{for(let o of e){let i=`${o.destPath}.velyx-tmp`;await this.fileSystem.writeFile(i,o.content),t.push(i);}for(let o of e){if(!o.existedBefore)continue;if(!ae(o.destPath))throw new Error(`Failed to create backup for ${o.destPath}`);n.push(o.destPath);}for(let o of e){let i=`${o.destPath}.velyx-tmp`;await w.move(i,o.destPath,{overwrite:!0});}n.forEach(o=>T(o));}catch(o){for(let i of e)await this.fileSystem.fileExists(i.destPath)&&await w.remove(i.destPath),i.existedBefore&&N(i.destPath);for(let i of t)await this.fileSystem.fileExists(i)&&await w.remove(i);throw o}}};var W=class{constructor(e,t,n){this.registryService=e;this.configManager=t;this.componentService=n??new B(e,new u,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=>s.success(`Added ${t}`)),e.skipped.forEach(t=>s.warn(`Skipped ${t}`)),e.failed.forEach(({name:t,error:n})=>s.error(`Failed to add ${t}: ${n}`));}displayNextSteps(e){e.added.length!==0&&console.log(`
|
|
21
|
+
\u{1F389} Happy coding! Enjoy building beautiful components!`);}};var G=class extends Error{constructor(t,n){super(t);this.cause=n;this.name="RegistryError";}},p=class extends G{constructor(e,t){super(e,t),this.name="NetworkError";}},h=class extends G{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 re(r){return new Promise(e=>setTimeout(e,r))}function ne(r,e,t,n){let o=e*Math.pow(t,r);return Math.min(o,n)}function ut(r,e){return e.includes(r)}async function gt(r,e){let{timeout:t=x.timeout,headers:n={}}=e,o=new AbortController,i=setTimeout(()=>o.abort(),t);try{return await fetch(r,{signal:o.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 z=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},o=null;for(let i=0;i<=n.maxRetries;i++)try{let a=await gt(e,{...t,timeout:n.timeout,headers:n.headers});if(a.ok||!ut(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 l=ne(i,n.initialDelay,n.backoffFactor,n.maxDelay);await re(l);}catch(a){if(o=a,a instanceof p&&a.message.includes("timeout")){if(i===n.maxRetries)throw a;let c=ne(i,n.initialDelay,n.backoffFactor,n.maxDelay);await re(c);continue}if(i===n.maxRetries)throw o instanceof p?o:new p(`Request failed after ${n.maxRetries+1} attempts: ${o.message}`,o);let l=ne(i,n.initialDelay,n.backoffFactor,n.maxDelay);await re(l);}throw new p(`Request failed after ${n.maxRetries+1} attempts: ${o?.message??"Unknown error"}`,o??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(o){throw new p(`Failed to parse JSON from ${e}: ${o.message}`,o)}}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(o){throw new p(`Failed to read text from ${e}: ${o.message}`,o)}}};var je=()=>process.env.VELYX_REGISTRY_URL?process.env.VELYX_REGISTRY_URL.replace(/\/$/,""):"http://velyx.test/api/v1";var Y=class{httpService;baseUrl;constructor(e){this.httpService=e??new z,this.baseUrl=je();}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),o=await this.httpService.fetch(n);if(o.status===404)throw new h(e);if(!o.ok)throw new p(`Failed to fetch component: ${o.status} ${o.statusText}`);let i=await this.httpService.fetchJson(n);return t?.includeFiles?i.data:this.convertRegistryComponentToMeta(i.data)}catch(n){throw n instanceof h||n instanceof p?n:new p(`Failed to fetch component "${e}": ${n.message}`)}}async fetchFile(e,t){try{let n=e.split("/").pop()||e,o=await this.httpService.fetch(`${this.baseUrl}/components/${n}`);if(o.status===404)throw new h(n);if(!o.ok)throw new p(`Failed to fetch component: ${o.status} ${o.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 h||n instanceof p?n:new p(`Failed to fetch file "${t}": ${n.message}`)}}async resolveDependencies(e){let t=new Set,n=[],o=async i=>{if(!t.has(i.name)&&(t.add(i.name),n.push(i),i.requires&&i.requires.length>0))for(let a of i.requires)try{let l=await this.fetchComponent(a);await o(l);}catch{}};return await o(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 o=n.toString();return `${this.baseUrl}/components/${e}${o?`?${o}`:""}`}async getComponentVersions(e){try{let t=await this.httpService.fetch(`${this.baseUrl}/components/${e}/versions`);if(t.status===404)throw new h(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 h||t instanceof p?t:new p(`Failed to fetch versions for "${e}": ${t.message}`)}}};var F=class{velyxService;constructor(){this.velyxService=new Y;}async fetchRegistry(){return await y.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 y.withTask(n,()=>this.velyxService.fetchComponent(e,t),void 0,`Failed to fetch component "${e}"`)}async resolveDependencies(e){return await y.withTask("Resolving dependencies...",()=>this.velyxService.resolveDependencies(e),void 0,"Failed to resolve dependencies")}};async function ht(r,e){if(r.all)return [...e];if(r.components?.length)return r.components;s.info("Use arrow keys and space to select, then press enter.");let{components:t}=await Se({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(o=>({title:o,value:o,selected:r.all?true:r.components?.includes(o)}))});t?.length||(s.warn("No components selected. Exiting."),s.info(""),process.exit(1));let n=z$1.array(z$1.string()).safeParse(t);return n.success||(s.error("Something went wrong. Please try again."),process.exit(1)),n.data}async function Oe(r){let e=new I;await e.load();let t=new F,n=new W(t,e);try{n.validateInitialization();}catch{s.error("Velyx is not initialized"),s.log("Run velyx init first"),process.exit(1);}let o=await n.getAvailableComponents(),i=o.components.map(c=>c.name).sort((c,d)=>c.localeCompare(d)),a=await ht(r,i);try{n.validateComponents(a,o);}catch(c){s.error(c.message),s.log("Run velyx list to see available components"),process.exit(1);}let l=await n.addComponents(a);n.displayResults(l),n.displayNextSteps(l);}var vt=z$1.object({components:z$1.array(z$1.string()).optional(),yes:z$1.boolean(),overwrite:z$1.boolean(),cwd:z$1.string(),all:z$1.boolean(),path:z$1.string().optional(),silent:z$1.boolean(),srcDir:z$1.boolean().optional(),cssVariables:z$1.boolean().optional()}),Pe=new Command().name("add").argument("[components...]","Names of components to add").option("-y, --yes","skip confirmation prompt.",false).option("-o, --overwrite","overwrite existing files.",false).option("-c, --cwd <cwd>","the working directory. defaults to the current directory.",process.cwd()).option("-a, --all","add all available components",false).option("-p, --path <path>","the path to add the component to.").option("-s, --silent","mute output.",false).option("--src-dir","use the src directory when creating a new project.",false).option("--no-src-dir","do not use the src directory when creating a new project.").option("--css-variables","use CSS variables for theming.",true).option("--no-css-variables","do not use CSS variables for theming.").description("Add one or more UI components to your Laravel project").action(async(r,e)=>{let t=new E;try{let n={components:r,cwd:b.resolve(e.cwd),...e,cssVariables:e.cssVariables??!0},o=vt.parse(n);await Oe(o);}catch(n){t.handle(n,"add command"),process.exit(1);}});function St(r,e){if(!e)return [...r];let t=e.toLowerCase();return r.filter(n=>{let o=n.name.toLowerCase().includes(t),i=n.description?n.description.toLowerCase().includes(t):false,a=n.categories?n.categories.some(l=>l.toLowerCase().includes(t)):false;return o||i||a})}function Ct(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 Ne(r){let n=[...(await new F().fetchRegistry()).components].sort((l,c)=>l.name.localeCompare(c.name)),o=St(n,r.query),i=Ct(o,r.offset,r.limit);if(r.json){console.log(JSON.stringify({total:o.length,count:i.length,offset:r.offset??0,limit:r.limit??null,components:i},null,2));return}if(i.length===0){s.warn("No components found.");return}console.log(v.bold(`
|
|
22
|
+
Available components:`)),console.log("");let a=new bt({head:[v.bold("Component"),v.bold("Description"),v.bold("Categories")],colWidths:[24,50,24],wordWrap:true,style:{head:[],border:[]}});for(let l of i)a.push([v.cyan(l.name),v.white(l.description||"No description"),v.gray(l.categories?.join(", ")||"-")]);console.log(a.toString()),console.log(""),s.info(`Run ${v.green("velyx add <component>")} to add one.`);}var Et=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()}),Te=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 E;try{let t=Et.parse({cwd:b.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 I;try{await n.load();}catch{s.error("Velyx is not initialized"),s.log("Run velyx init first"),process.exit(1);}await Ne(t);}catch(t){e.handle(t,"list command"),process.exit(1);}});function Ft(){console.log(""),console.log(v.bold.cyan(` \u25BC VELYX CLI v${P.version}`)),console.log(v.gray(" Tailwind CSS v4+ components for Laravel")),console.log("");}var oe=new Command;oe.name("velyx").description("Velyx CLI: Copy UI components into your Laravel project").version(P.version,"-v, --version","display the version number").hook("preAction",()=>{Ft();});oe.addCommand(Pe).addCommand(Fe).addCommand(Te);oe.parse(process.argv);//# sourceMappingURL=index.js.map
|
|
23
23
|
//# sourceMappingURL=index.js.map
|