diff --git a/extensions/laravel-toolkit/.gitignore b/extensions/laravel-toolkit/.gitignore new file mode 100644 index 000000000000..84fc7188f513 --- /dev/null +++ b/extensions/laravel-toolkit/.gitignore @@ -0,0 +1,14 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# Raycast specific files +raycast-env.d.ts +.raycast-swift-build +.swiftpm +compiled_raycast_swift +compiled_raycast_rust + +# misc +.DS_Store diff --git a/extensions/laravel-toolkit/.prettierrc b/extensions/laravel-toolkit/.prettierrc new file mode 100644 index 000000000000..fc0f5030683f --- /dev/null +++ b/extensions/laravel-toolkit/.prettierrc @@ -0,0 +1,4 @@ +{ + "printWidth": 120, + "singleQuote": false +} diff --git a/extensions/laravel-toolkit/CHANGELOG.md b/extensions/laravel-toolkit/CHANGELOG.md new file mode 100644 index 000000000000..c20d2d86822b --- /dev/null +++ b/extensions/laravel-toolkit/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +## [Initial Release] - {PR_MERGE_DATE} + +### Added + +- **Project Management:** + - `manage-projects` command to track and open local projects. + - Import existing projects from any directory. + - "Open in Editor" with strict path configuration. + - "Open External Terminal" to launch PowerShell/Terminal at project path with auto-focus. +- **Project Creation:** + - `create-project` wizard with support for Breeze, Jetstream, and custom stacks. + - Support for SQLite, MySQL, PostgreSQL, MariaDB, SQL Server. + - Testing framework selection (Pest/PHPUnit). + - One-click setup for Sail, Telescope, Horizon, Pulse. +- **Custom Packages:** + - `manage-custom-packages` command to create reusable package presets. + - Support for saving both Composer and NPM packages. + - Integrated into project creation flow. +- **Documentation & Snippets:** + - Comprehensive documentation search for all Laravel versions. + - Collection of common code snippets (Routes, Models, etc.). + - Artisan command reference with search. +- **Internal Tools:** + - Robust `exec`-based editor launching for Windows reliability. + - LocalStorage persistence for projects and packages. diff --git a/extensions/laravel-toolkit/README.md b/extensions/laravel-toolkit/README.md new file mode 100644 index 000000000000..8704d2e172d5 --- /dev/null +++ b/extensions/laravel-toolkit/README.md @@ -0,0 +1,57 @@ +# Laravel Toolkit + +Boost your productivity with the ultimate Laravel companion for Raycast. Search documentation, manage local projects, run Artisan commands, and scaffold new applications instantly. + +## Features + +### 📚 Documentation + +- **Instant Search:** Browse documentation across all categories for Laravel 5.x to 12.x. +- **Smart Actions:** Quickly copy links (Markdown/URL) or open pages in your browser. +- **Favorites:** Pin your most frequently accessed guides. + +### 🚀 Project Management + +- **Dashboard:** View and manage all your local Laravel projects in one place. +- **Quick Launch:** Open projects in your favorite editor (VS Code, PhpStorm, etc.) or terminal. +- **External Terminal:** Instantly spawn a PowerShell/Terminal window at your project's root. +- **Import:** Add existing projects to your dashboard for easy access. + +### ⚡ Artisan Commands + +- **Command Reference:** Searchable catalog of 50+ Artisan commands with usage examples. +- **Execute Directly:** Run commands like `migrate`, `serve`, or `tinker` straight from Raycast. +- **Live Output:** Monitor command execution in real-time. + +### 🛠️ Project Creator + +- **Wizard:** Step-by-step project creation with official starter kits (Breeze, Jetstream). +- **Custom Stacks:** Choose your frontend (Vue, React, Livewire) and database (MySQL, SQLite, etc.). +- **One-Click Tools:** Pre-install Telescope, Horizon, and Debugbar. + +### 🧰 Development Tools + +- **Version Manager:** Manage versions of PHP, Composer, and NPM. +- **Deep Clean:** "Clean Install" mode lets you edit the uninstall command (e.g. `rm -rf`, `--force`) for total removal. +- **Smart Detection:** Automatically detects installation method (Chocolatey, Scoop, etc.). +- **Custom Tools:** Add your own CLI tools to the manager. + +### 📦 Package Manager + +- **Custom Presets:** Create and save your frequent package combinations. +- **Mixed Support:** Add both Composer and NPM packages to your presets. +- **Seamless Install:** Apply your custom presets automatically when creating new projects. + +### ✂️ Snippets + +- **Ready-to-Use:** Collection of essential code snippets for Routes, Models, and Migrations. +- **Configurable:** Copy to clipboard or paste directly into your active window. + +## Configuration + +- **Code Editor:** Select the application used for "Open in Editor". +- **Laravel Version:** Choose your preferred documentation version. + +## License + +MIT diff --git a/extensions/laravel-toolkit/assets/extension-icon.png b/extensions/laravel-toolkit/assets/extension-icon.png new file mode 100644 index 000000000000..1de4af5933e5 Binary files /dev/null and b/extensions/laravel-toolkit/assets/extension-icon.png differ diff --git a/extensions/laravel-toolkit/assets/laravel-icon.png b/extensions/laravel-toolkit/assets/laravel-icon.png new file mode 100644 index 000000000000..1de4af5933e5 Binary files /dev/null and b/extensions/laravel-toolkit/assets/laravel-icon.png differ diff --git a/extensions/laravel-toolkit/eslint.config.js b/extensions/laravel-toolkit/eslint.config.js new file mode 100644 index 000000000000..50f86489f3b2 --- /dev/null +++ b/extensions/laravel-toolkit/eslint.config.js @@ -0,0 +1,6 @@ +const { defineConfig } = require("eslint/config"); +const raycastConfig = require("@raycast/eslint-config"); + +module.exports = defineConfig([ + ...raycastConfig, +]); diff --git a/extensions/laravel-toolkit/metadata/artisan-commands-1.png b/extensions/laravel-toolkit/metadata/artisan-commands-1.png new file mode 100644 index 000000000000..1e59fd415507 Binary files /dev/null and b/extensions/laravel-toolkit/metadata/artisan-commands-1.png differ diff --git a/extensions/laravel-toolkit/metadata/create-project-1.png b/extensions/laravel-toolkit/metadata/create-project-1.png new file mode 100644 index 000000000000..24a9069c6c22 Binary files /dev/null and b/extensions/laravel-toolkit/metadata/create-project-1.png differ diff --git a/extensions/laravel-toolkit/metadata/extension-overview-1.png b/extensions/laravel-toolkit/metadata/extension-overview-1.png new file mode 100644 index 000000000000..ce09635c149e Binary files /dev/null and b/extensions/laravel-toolkit/metadata/extension-overview-1.png differ diff --git a/extensions/laravel-toolkit/metadata/laravel-docs-1.png b/extensions/laravel-toolkit/metadata/laravel-docs-1.png new file mode 100644 index 000000000000..86370fed4523 Binary files /dev/null and b/extensions/laravel-toolkit/metadata/laravel-docs-1.png differ diff --git a/extensions/laravel-toolkit/metadata/snippets-1.png b/extensions/laravel-toolkit/metadata/snippets-1.png new file mode 100644 index 000000000000..625f409f2f8a Binary files /dev/null and b/extensions/laravel-toolkit/metadata/snippets-1.png differ diff --git a/extensions/laravel-toolkit/package-lock.json b/extensions/laravel-toolkit/package-lock.json new file mode 100644 index 000000000000..fc7052cc0d7d --- /dev/null +++ b/extensions/laravel-toolkit/package-lock.json @@ -0,0 +1,2948 @@ +{ + "name": "laravel-documentation", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "laravel-documentation", + "license": "MIT", + "dependencies": { + "@raycast/api": "^1.103.0", + "@raycast/utils": "^2.2.1" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.0.4", + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "eslint": "^9.22.0", + "prettier": "^3.5.3", + "typescript": "^5.8.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@oclif/core": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.8.0.tgz", + "integrity": "sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete": { + "version": "3.2.39", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-3.2.39.tgz", + "integrity": "sha512-OwAZNnSpuDjKyhAwoOJkFWxGswPFKBB4hpNIMsj6PUtbKwGBPmD+2wGGPgTsDioVwLmUELSb2bZ+1dxHfvXmvg==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4", + "ansis": "^3.16.0", + "debug": "^4.4.1", + "ejs": "^3.1.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-help": { + "version": "6.2.36", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.36.tgz", + "integrity": "sha512-NBQIg5hEMhvdbi4mSrdqRGl5XJ0bqTAHq6vDCCCDXUcfVtdk3ZJbSxtRVWyVvo9E28vwqu6MZyHOJylevqcHbA==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/plugin-not-found": { + "version": "3.2.73", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.73.tgz", + "integrity": "sha512-2bQieTGI9XNFe9hKmXQjJmHV5rZw+yn7Rud1+C5uLEo8GaT89KZbiLTJgL35tGILahy/cB6+WAs812wjw7TK6w==", + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^7.10.1", + "@oclif/core": "^4.8.0", + "ansis": "^3.17.0", + "fast-levenshtein": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@raycast/api": { + "version": "1.104.1", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.104.1.tgz", + "integrity": "sha512-5v52JDzAAoCA6/JOoBfsgCXK4fzIY02lvMH0IA3ghuxZuk8i2ID2rtPkTuIAbebpkILADB7gP4yaBphkMLiCJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@oclif/core": "^4.5.4", + "@oclif/plugin-autocomplete": "^3.2.35", + "@oclif/plugin-help": "^6.2.33", + "@oclif/plugin-not-found": "^3.2.68", + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "esbuild": "^0.25.10", + "react": "19.0.0" + }, + "bin": { + "ray": "bin/run.js" + }, + "engines": { + "node": ">=22.14.0" + }, + "peerDependencies": { + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "react-devtools": "6.1.1" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react-devtools": { + "optional": true + } + } + }, + "node_modules/@raycast/eslint-config": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@raycast/eslint-config/-/eslint-config-2.1.1.tgz", + "integrity": "sha512-W0kxF+FJ+BYQn0EKIV739j2ZrHEtjo/LclsoZgUWg3t364Dq75XKcjqYFYx+59/DBaamY0amdajlfuDAf6veAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/js": "^9.36.0", + "@raycast/eslint-plugin": "^2.1.1", + "eslint-config-prettier": "^10.1.8", + "globals": "^16.4.0", + "typescript-eslint": "^8.45.0" + }, + "peerDependencies": { + "eslint": ">=8.23.0", + "prettier": ">=2", + "typescript": ">=4" + } + }, + "node_modules/@raycast/eslint-plugin": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@raycast/eslint-plugin/-/eslint-plugin-2.1.1.tgz", + "integrity": "sha512-r2gs8uIlNp6I2mLOyN/kReGlvigzEeuyQPl4yw7nwLy8Zxjfjhg8txMViaBux8juBWBxbSWq/IfW6ZA50oeOHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.26.1" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/@raycast/utils": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-2.2.2.tgz", + "integrity": "sha512-tZcyWCHZvz4L/i1CGEnSZkBoK6wwX1pzlTKjcWWugbrQyG0QCMOxjKJfRC/iNkD+hHaqhMWUj4Y0LNo/NknvFw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "@raycast/api": ">=1.99.4", + "react": ">=19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.51.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optionator/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", + "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz", + "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/extensions/laravel-toolkit/package.json b/extensions/laravel-toolkit/package.json new file mode 100644 index 000000000000..ab26ee524190 --- /dev/null +++ b/extensions/laravel-toolkit/package.json @@ -0,0 +1,187 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "laravel-toolkit", + "title": "Laravel Toolkit", + "description": "The ultimate Laravel toolkit for Raycast. Manage projects, search docs, run Artisan commands, and scaffold apps with custom presets.", + "icon": "extension-icon.png", + "license": "MIT", + "commands": [ + { + "name": "laravel-docs", + "title": "Search Laravel Docs", + "description": "Search and browse Laravel documentation with favorites, recent pages, and version switching", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "artisan-commands", + "title": "Artisan Commands", + "description": "Browse and copy all Laravel Artisan commands organized by category", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "snippets", + "title": "Laravel Snippets", + "description": "Copy common Laravel code snippets for routes, models, migrations, and more", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "create-project", + "title": "Create Laravel Project", + "description": "Create a new Laravel project with starter kit and database options", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "run-artisan", + "title": "Run Artisan Command", + "description": "Execute Artisan commands in your Laravel project directory", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "manage-projects", + "title": "Manage Laravel Projects", + "description": "View, manage, and run commands in your Laravel projects", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "manage-custom-packages", + "title": "Manage Custom Packages", + "description": "Add, edit, and remove custom package presets", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "manage-tools", + "title": "Manage Development Tools", + "description": "Update, downgrade, and manage PHP, Composer, NPM, and custom tools", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "route-explorer", + "title": "Route Explorer", + "description": "Visualize and search Laravel routes", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "log-viewer", + "title": "Log Viewer", + "description": "View and manage Laravel application logs", + "mode": "view", + "subtitle": "Laravel" + }, + { + "name": "model-factory", + "title": "Generate Model Factory", + "description": "Create a new model factory for an existing model", + "mode": "view", + "subtitle": "Laravel" + } + ], + "preferences": [ + { + "name": "laravelVersion", + "type": "dropdown", + "required": false, + "title": "Laravel Version", + "description": "Choose which Laravel version's documentation to browse. Defaults to the latest stable release.", + "default": "12.x", + "data": [ + { "title": "Laravel Master (Beta)", "value": "master" }, + { "title": "Laravel 12.x (Latest)", "value": "12.x" }, + { "title": "Laravel 11.x (LTS)", "value": "11.x" }, + { "title": "Laravel 10.x", "value": "10.x" }, + { "title": "Laravel 9.x", "value": "9.x" }, + { "title": "Laravel 8.x", "value": "8.x" }, + { "title": "Laravel 7.x", "value": "7.x" }, + { "title": "Laravel 6.x", "value": "6.x" }, + { "title": "Laravel 5.8", "value": "5.8" }, + { "title": "Laravel 5.7", "value": "5.7" }, + { "title": "Laravel 5.6", "value": "5.6" }, + { "title": "Laravel 5.5", "value": "5.5" }, + { "title": "Laravel 5.4", "value": "5.4" }, + { "title": "Laravel 5.3", "value": "5.3" }, + { "title": "Laravel 5.2", "value": "5.2" }, + { "title": "Laravel 5.1", "value": "5.1" }, + { "title": "Laravel 5.0", "value": "5.0" } + ] + }, + { + "name": "openInNewTab", + "type": "checkbox", + "required": false, + "title": "Browser Behavior", + "label": "Always open in new tab", + "description": "When enabled, documentation opens in a new browser tab. When disabled, copies URL to clipboard.", + "default": true + }, + { + "name": "snippetPrimaryAction", + "type": "dropdown", + "required": false, + "title": "Snippets: Primary Action", + "description": "Choose the default action when pressing Enter on a snippet", + "default": "paste", + "data": [ + { "title": "Paste Snippet", "value": "paste" }, + { "title": "Copy to Clipboard", "value": "copy" } + ] + }, + { + "name": "snippetSecondaryModifier", + "type": "dropdown", + "required": false, + "title": "Snippets: Secondary Action Modifier", + "description": "Modifier key combination for the secondary snippet action", + "default": "cmd+shift", + "data": [ + { "title": "⌘ + ⇧ (Cmd + Shift)", "value": "cmd+shift" }, + { "title": "⌘ (Cmd)", "value": "cmd" }, + { "title": "⌃ (Ctrl)", "value": "ctrl" }, + { "title": "⌥ (Opt/Alt)", "value": "opt" }, + { "title": "⇧ (Shift)", "value": "shift" } + ] + }, + { + "name": "editorApplication", + "type": "appPicker", + "required": false, + "title": "Code Editor", + "description": "Select your preferred code editor for opening projects (leave empty for system default)" + } + ], + "dependencies": { + "@raycast/api": "^1.103.0", + "@raycast/utils": "^2.2.1" + }, + "devDependencies": { + "@raycast/eslint-config": "^2.0.4", + "@types/node": "22.13.10", + "@types/react": "19.0.10", + "eslint": "^9.22.0", + "prettier": "^3.5.3", + "typescript": "^5.8.2" + }, + "scripts": { + "dev": "ray develop", + "lint": "ray lint", + "fix-lint": "ray lint --fix", + "build": "ray build", + "publish": "npx @raycast/api@latest publish", + "prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1" + }, + "author": "jasper_huppertz", + "platforms": [ + "Windows" + ], + "categories": [ + "Documentation", + "Developer Tools" + ] +} \ No newline at end of file diff --git a/extensions/laravel-toolkit/src/artisan-commands.tsx b/extensions/laravel-toolkit/src/artisan-commands.tsx new file mode 100644 index 000000000000..2fba6b1fa8d6 --- /dev/null +++ b/extensions/laravel-toolkit/src/artisan-commands.tsx @@ -0,0 +1,424 @@ +import { ActionPanel, List, Action, Icon } from "@raycast/api"; + +interface ArtisanCommand { + name: string; + description: string; + usage?: string; + category: string; +} + +const ARTISAN_COMMANDS: ArtisanCommand[] = [ + // Make Commands + { + name: "make:controller", + description: "Create a new controller class", + usage: "php artisan make:controller UserController", + category: "Make", + }, + { + name: "make:model", + description: "Create a new Eloquent model class", + usage: "php artisan make:model User -mfc", + category: "Make", + }, + { + name: "make:migration", + description: "Create a new migration file", + usage: "php artisan make:migration create_users_table", + category: "Make", + }, + { + name: "make:seeder", + description: "Create a new seeder class", + usage: "php artisan make:seeder UserSeeder", + category: "Make", + }, + { + name: "make:factory", + description: "Create a new model factory", + usage: "php artisan make:factory UserFactory", + category: "Make", + }, + { + name: "make:middleware", + description: "Create a new middleware class", + usage: "php artisan make:middleware CheckAge", + category: "Make", + }, + { + name: "make:request", + description: "Create a new form request class", + usage: "php artisan make:request StoreUserRequest", + category: "Make", + }, + { + name: "make:resource", + description: "Create a new resource class", + usage: "php artisan make:resource UserResource", + category: "Make", + }, + { + name: "make:command", + description: "Create a new Artisan command", + usage: "php artisan make:command SendEmails", + category: "Make", + }, + { + name: "make:event", + description: "Create a new event class", + usage: "php artisan make:event OrderShipped", + category: "Make", + }, + { + name: "make:listener", + description: "Create a new event listener class", + usage: "php artisan make:listener SendShipmentNotification", + category: "Make", + }, + { + name: "make:job", + description: "Create a new job class", + usage: "php artisan make:job ProcessPodcast", + category: "Make", + }, + { + name: "make:mail", + description: "Create a new email class", + usage: "php artisan make:mail OrderShipped", + category: "Make", + }, + { + name: "make:notification", + description: "Create a new notification class", + usage: "php artisan make:notification InvoicePaid", + category: "Make", + }, + { + name: "make:policy", + description: "Create a new policy class", + usage: "php artisan make:policy PostPolicy --model=Post", + category: "Make", + }, + { + name: "make:provider", + description: "Create a new service provider class", + usage: "php artisan make:provider RiakServiceProvider", + category: "Make", + }, + { + name: "make:test", + description: "Create a new test class", + usage: "php artisan make:test UserTest", + category: "Make", + }, + { + name: "make:component", + description: "Create a new Blade component", + usage: "php artisan make:component Alert", + category: "Make", + }, + { + name: "make:livewire", + description: "Create a new Livewire component", + usage: "php artisan make:livewire Counter", + category: "Make", + }, + { + name: "make:cast", + description: "Create a new custom Eloquent cast class", + usage: "php artisan make:cast Json", + category: "Make", + }, + { + name: "make:channel", + description: "Create a new channel class", + usage: "php artisan make:channel OrderChannel", + category: "Make", + }, + { + name: "make:exception", + description: "Create a new custom exception class", + usage: "php artisan make:exception InvalidOrderException", + category: "Make", + }, + { + name: "make:observer", + description: "Create a new observer class", + usage: "php artisan make:observer UserObserver --model=User", + category: "Make", + }, + { + name: "make:rule", + description: "Create a new validation rule", + usage: "php artisan make:rule Uppercase", + category: "Make", + }, + { + name: "make:scope", + description: "Create a new Eloquent scope", + usage: "php artisan make:scope AncientScope", + category: "Make", + }, + + // Database + { name: "migrate", description: "Run the database migrations", usage: "php artisan migrate", category: "Database" }, + { + name: "migrate:fresh", + description: "Drop all tables and re-run all migrations", + usage: "php artisan migrate:fresh --seed", + category: "Database", + }, + { + name: "migrate:rollback", + description: "Rollback the last database migration", + usage: "php artisan migrate:rollback", + category: "Database", + }, + { + name: "migrate:reset", + description: "Rollback all database migrations", + usage: "php artisan migrate:reset", + category: "Database", + }, + { + name: "migrate:status", + description: "Show the status of each migration", + usage: "php artisan migrate:status", + category: "Database", + }, + { + name: "db:seed", + description: "Seed the database with records", + usage: "php artisan db:seed", + category: "Database", + }, + { + name: "db:wipe", + description: "Drop all tables, views, and types", + usage: "php artisan db:wipe", + category: "Database", + }, + + // Cache & Config + { + name: "cache:clear", + description: "Flush the application cache", + usage: "php artisan cache:clear", + category: "Cache", + }, + { + name: "config:cache", + description: "Create a cache file for faster configuration loading", + usage: "php artisan config:cache", + category: "Cache", + }, + { + name: "config:clear", + description: "Remove the configuration cache file", + usage: "php artisan config:clear", + category: "Cache", + }, + { + name: "route:cache", + description: "Create a route cache file for faster route registration", + usage: "php artisan route:cache", + category: "Cache", + }, + { + name: "route:clear", + description: "Remove the route cache file", + usage: "php artisan route:clear", + category: "Cache", + }, + { + name: "view:cache", + description: "Compile all of the application's Blade templates", + usage: "php artisan view:cache", + category: "Cache", + }, + { + name: "view:clear", + description: "Clear all compiled view files", + usage: "php artisan view:clear", + category: "Cache", + }, + { + name: "event:cache", + description: "Discover and cache the application's events and listeners", + usage: "php artisan event:cache", + category: "Cache", + }, + { + name: "optimize", + description: "Cache the framework bootstrap files", + usage: "php artisan optimize", + category: "Cache", + }, + { + name: "optimize:clear", + description: "Remove the cached bootstrap files", + usage: "php artisan optimize:clear", + category: "Cache", + }, + + // Development + { + name: "serve", + description: "Serve the application on the PHP development server", + usage: "php artisan serve", + category: "Development", + }, + { + name: "tinker", + description: "Interact with your application (REPL)", + usage: "php artisan tinker", + category: "Development", + }, + { + name: "route:list", + description: "List all registered routes", + usage: "php artisan route:list", + category: "Development", + }, + { + name: "env", + description: "Display the current framework environment", + usage: "php artisan env", + category: "Development", + }, + { + name: "about", + description: "Display basic information about your application", + usage: "php artisan about", + category: "Development", + }, + + // Queue + { + name: "queue:work", + description: "Start processing jobs on the queue", + usage: "php artisan queue:work", + category: "Queue", + }, + { + name: "queue:listen", + description: "Listen to a given queue", + usage: "php artisan queue:listen", + category: "Queue", + }, + { + name: "queue:restart", + description: "Restart queue worker daemons after their current job", + usage: "php artisan queue:restart", + category: "Queue", + }, + { + name: "queue:failed", + description: "List all of the failed queue jobs", + usage: "php artisan queue:failed", + category: "Queue", + }, + { + name: "queue:retry", + description: "Retry a failed queue job", + usage: "php artisan queue:retry all", + category: "Queue", + }, + { + name: "queue:flush", + description: "Flush all of the failed queue jobs", + usage: "php artisan queue:flush", + category: "Queue", + }, + + // Schedule + { + name: "schedule:run", + description: "Run the scheduled commands", + usage: "php artisan schedule:run", + category: "Schedule", + }, + { + name: "schedule:list", + description: "List all scheduled tasks", + usage: "php artisan schedule:list", + category: "Schedule", + }, + { + name: "schedule:work", + description: "Start the schedule worker", + usage: "php artisan schedule:work", + category: "Schedule", + }, + + // Auth & Keys + { name: "key:generate", description: "Set the application key", usage: "php artisan key:generate", category: "Auth" }, + { + name: "storage:link", + description: "Create the symbolic links configured for the application", + usage: "php artisan storage:link", + category: "Auth", + }, + + // Packages + { + name: "vendor:publish", + description: "Publish any publishable assets from vendor packages", + usage: "php artisan vendor:publish --provider=...", + category: "Packages", + }, + { + name: "package:discover", + description: "Rebuild the cached package manifest", + usage: "php artisan package:discover", + category: "Packages", + }, +]; + +// Group commands by category +function groupByCategory(commands: ArtisanCommand[]): Record { + return commands.reduce( + (acc, cmd) => { + if (!acc[cmd.category]) { + acc[cmd.category] = []; + } + acc[cmd.category].push(cmd); + return acc; + }, + {} as Record, + ); +} + +export default function Command() { + const grouped = groupByCategory(ARTISAN_COMMANDS); + const categories = Object.keys(grouped); + + return ( + + {categories.map((category) => ( + + {grouped[category].map((cmd) => ( + + + + + + + + + + } + /> + ))} + + ))} + + ); +} diff --git a/extensions/laravel-toolkit/src/create-project.tsx b/extensions/laravel-toolkit/src/create-project.tsx new file mode 100644 index 000000000000..5702ae987ee8 --- /dev/null +++ b/extensions/laravel-toolkit/src/create-project.tsx @@ -0,0 +1,543 @@ +import { + ActionPanel, + Action, + Form, + showToast, + Toast, + getPreferenceValues, + Icon, + confirmAlert, + Alert, +} from "@raycast/api"; +import { useState, useEffect } from "react"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { openInEditor } from "./utils/editor"; +import { saveProject } from "./utils/projects"; +import { getCustomPackages, CustomPackage } from "./utils/custom-packages"; + +const execAsync = promisify(exec); + +// ============================================================================ +// Configuration +// ============================================================================ + +// Base auth kits (only one can be selected) +const BASE_KITS = [ + { title: "None (Bare Laravel)", value: "none", description: "Fresh Laravel without auth scaffolding" }, + { title: "Laravel Breeze (Blade)", value: "breeze-blade", description: "Simple auth with Blade templates" }, + { title: "Laravel Breeze (Vue)", value: "breeze-vue", description: "Simple auth with Vue + Inertia" }, + { title: "Laravel Breeze (React)", value: "breeze-react", description: "Simple auth with React + Inertia" }, + { title: "Laravel Breeze (Livewire)", value: "breeze-livewire", description: "Simple auth with Livewire" }, + { title: "Laravel Breeze (API)", value: "breeze-api", description: "API-only auth scaffolding" }, + { + title: "Laravel Jetstream (Livewire)", + value: "jetstream-livewire", + description: "Full auth with teams using Livewire", + }, + { + title: "Laravel Jetstream (Inertia)", + value: "jetstream-inertia", + description: "Full auth with teams using Inertia", + }, +]; + +// Stackable packages (multiple can be selected) +interface StackablePackage { + id: string; + title: string; + package: string; + category: string; + setup?: string; + type?: "composer" | "npm"; +} + +const STACKABLE_PACKAGES: StackablePackage[] = [ + // Admin Panels + { + id: "filament", + title: "Filament", + package: "filament/filament", + category: "Admin", + setup: "php artisan filament:install --panels", + }, + { id: "orchid", title: "Orchid", package: "orchid/platform", category: "Admin", setup: "php artisan orchid:install" }, + { id: "voyager", title: "Voyager", package: "tcg/voyager", category: "Admin", setup: "php artisan voyager:install" }, + { id: "backpack", title: "Backpack", package: "backpack/crud", category: "Admin" }, + + // Frontend + { id: "livewire", title: "Livewire", package: "livewire/livewire", category: "Frontend" }, + { id: "inertia", title: "Inertia.js", package: "inertiajs/inertia-laravel", category: "Frontend" }, + + // API & Auth + { id: "sanctum", title: "Laravel Sanctum", package: "laravel/sanctum", category: "API" }, + { id: "passport", title: "Laravel Passport", package: "laravel/passport", category: "API" }, + { id: "socialite", title: "Laravel Socialite", package: "laravel/socialite", category: "API" }, + + // Utilities + { id: "scout", title: "Laravel Scout", package: "laravel/scout", category: "Utilities" }, + { id: "cashier", title: "Laravel Cashier", package: "laravel/cashier", category: "Utilities" }, + { id: "spatie-permission", title: "Spatie Permission", package: "spatie/laravel-permission", category: "Utilities" }, + { id: "spatie-media", title: "Spatie Media Library", package: "spatie/laravel-medialibrary", category: "Utilities" }, + { id: "spatie-backup", title: "Spatie Backup", package: "spatie/laravel-backup", category: "Utilities" }, +]; + +// Packages included with each auth kit (for smart filtering) +const KIT_INCLUDED_PACKAGES: Record = { + "breeze-blade": [], + "breeze-vue": ["inertia"], + "breeze-react": ["inertia"], + "breeze-livewire": ["livewire"], + "breeze-api": ["sanctum"], + "jetstream-livewire": ["livewire", "sanctum"], + "jetstream-inertia": ["inertia", "sanctum"], + none: [], +}; + +const DEV_TOOLS = [ + { id: "debugbar", title: "Laravel Debugbar", package: "barryvdh/laravel-debugbar", dev: true }, + { + id: "idehelper", + title: "IDE Helper", + package: "barryvdh/laravel-ide-helper", + dev: true, + setup: "php artisan ide-helper:generate", + }, + { id: "pint", title: "Laravel Pint", package: "laravel/pint", dev: true }, + { + id: "telescope", + title: "Laravel Telescope", + package: "laravel/telescope", + dev: false, + setup: "php artisan telescope:install", + }, + { + id: "horizon", + title: "Laravel Horizon", + package: "laravel/horizon", + dev: false, + setup: "php artisan horizon:install", + }, + { + id: "pulse", + title: "Laravel Pulse", + package: "laravel/pulse", + dev: false, + setup: 'php artisan vendor:publish --provider="Laravel\\Pulse\\PulseServiceProvider"', + }, +]; + +const DATABASES = [ + { title: "SQLite", value: "sqlite" }, + { title: "MySQL", value: "mysql" }, + { title: "PostgreSQL", value: "pgsql" }, + { title: "MariaDB", value: "mariadb" }, + { title: "SQL Server", value: "sqlsrv" }, +]; + +const TESTING_FRAMEWORKS = [ + { title: "PHPUnit (Default)", value: "phpunit" }, + { title: "Pest", value: "pest" }, +]; + +// ============================================================================ +// Helpers +// ============================================================================ + +function groupPackages(packages: StackablePackage[]): Record { + return packages.reduce( + (acc, pkg) => { + const cat = pkg.category || "Other"; + if (!acc[cat]) acc[cat] = []; + acc[cat].push(pkg); + return acc; + }, + {} as Record, + ); +} + +// ============================================================================ +// Component +// ============================================================================ + +export default function Command() { + const preferences = getPreferenceValues(); + const [isLoading, setIsLoading] = useState(true); + const [selectedBaseKit, setSelectedBaseKit] = useState("none"); + const [selectedPackages, setSelectedPackages] = useState([]); + const [selectedDevTools, setSelectedDevTools] = useState([]); + const [customPackages, setCustomPackages] = useState([]); + + useEffect(() => { + loadCustomPackages(); + }, []); + + async function loadCustomPackages() { + setIsLoading(true); + const pkgs = await getCustomPackages(); + setCustomPackages(pkgs); + setIsLoading(false); + } + + const storedPackages: StackablePackage[] = customPackages.map((p) => ({ + id: p.id, + title: p.title, + package: p.package, + category: "Custom", + type: p.type, + })); + + const allPackages = [...STACKABLE_PACKAGES, ...storedPackages]; + + // Smart filter: hide packages already included with selected auth kit + const includedWithKit = KIT_INCLUDED_PACKAGES[selectedBaseKit] || []; + const filteredPackages = allPackages.filter((pkg) => !includedWithKit.includes(pkg.id)); + const groupedPackages = groupPackages(filteredPackages); + + // Remove any selected packages that are now hidden due to kit change + function handleBaseKitChange(kit: string) { + setSelectedBaseKit(kit); + const newIncluded = KIT_INCLUDED_PACKAGES[kit] || []; + setSelectedPackages((prev) => prev.filter((id) => !newIncluded.includes(id))); + } + + // ============================================================================ + // Create Project + // ============================================================================ + + async function handleSubmit(values: { + projectName: string; + directory: string; + baseKit: string; + packages: string[]; + database: string; + testing: string; + git: boolean; + sail: boolean; + devTools: string[]; + }) { + // Use state values for controlled form fields + values.baseKit = selectedBaseKit; + values.packages = selectedPackages; + values.devTools = selectedDevTools; + if (!values.projectName) { + showToast({ style: Toast.Style.Failure, title: "Project name is required" }); + return; + } + + if (!/^[a-zA-Z0-9_-]+$/.test(values.projectName)) { + showToast({ + style: Toast.Style.Failure, + title: "Invalid Project Name", + message: "Only letters, numbers, dashes, and underscores are allowed.", + }); + return; + } + + setIsLoading(true); + const sanitizedDirectory = values.directory.replace(/"/g, '\\"'); + const projectPath = `${values.directory}/${values.projectName}`; + + try { + const toast = await showToast({ + style: Toast.Style.Animated, + title: "Creating Laravel project...", + message: "Step 1: Base installation", + }); + + // Step 1: Create base Laravel project with auth kit + let command = `cd "${sanitizedDirectory}" && laravel new ${values.projectName}`; + + if (values.database && values.database !== "sqlite") { + command += ` --database=${values.database}`; + } + + if (!values.git) { + command += " --no-git"; + } + + if (values.testing === "pest") { + command += " --pest"; + } + + // Handle base auth kit + if (values.baseKit.startsWith("breeze")) { + command += " --breeze"; + if (values.baseKit === "breeze-vue") command += " --stack=vue"; + else if (values.baseKit === "breeze-react") command += " --stack=react"; + else if (values.baseKit === "breeze-livewire") command += " --stack=livewire"; + else if (values.baseKit === "breeze-api") command += " --stack=api"; + } else if (values.baseKit.startsWith("jetstream")) { + command += " --jet"; + if (values.baseKit === "jetstream-inertia") command += " --stack=inertia"; + else command += " --stack=livewire"; + } + + await execAsync(command, { timeout: 300000 }); // 5 min timeout + + // Step 2: Install selected packages + if (values.packages && values.packages.length > 0) { + toast.message = "Step 2: Installing packages..."; + + for (const pkgId of values.packages) { + const pkg = allPackages.find((p) => p.id === pkgId); + if (pkg && pkg.package) { + toast.message = `Installing ${pkg.title}...`; + + // Detect if npm or composer + if (pkg.type === "npm") { + await execAsync(`cd "${projectPath.replace(/"/g, '\\"')}" && npm install ${pkg.package}`); + } else { + // Default to composer + await execAsync(`cd "${projectPath.replace(/"/g, '\\"')}" && composer require ${pkg.package}`); + } + + if (pkg.setup) { + await execAsync(`cd "${projectPath}" && ${pkg.setup}`); + } + } + } + } + + // Step 3: Install dev tools + if (values.devTools && values.devTools.length > 0) { + toast.message = "Step 3: Installing dev tools..."; + + for (const toolId of values.devTools) { + const tool = DEV_TOOLS.find((t) => t.id === toolId); + if (tool) { + toast.message = `Installing ${tool.title}...`; + const devFlag = tool.dev ? " --dev" : ""; + const safePath = projectPath.replace(/"/g, '\\"'); + await execAsync(`cd "${safePath}" && composer require ${tool.package}${devFlag}`); + + if (tool.setup) { + await execAsync(`cd "${safePath}" && ${tool.setup}`); + } + } + } + } + + // Step 4: Initialize Sail if selected + if (values.sail) { + toast.message = "Step 4: Setting up Laravel Sail..."; + const safePath = projectPath.replace(/"/g, '\\"'); + await execAsync(`cd "${safePath}" && composer require laravel/sail --dev`); + await execAsync(`cd "${safePath}" && php artisan sail:install --with=mysql,redis,mailpit`); + } + + toast.style = Toast.Style.Success; + toast.title = "🎉 Project created!"; + toast.message = values.projectName; + + // Save project to list for Manage Projects + await saveProject({ + path: projectPath, + name: values.projectName, + createdAt: new Date().toISOString(), + baseKit: values.baseKit, + packages: values.packages, + }); + + await openInEditor(projectPath); + } catch (error) { + showToast({ + style: Toast.Style.Failure, + title: "Failed to create project", + message: error instanceof Error ? error.message : "Unknown error", + }); + } finally { + setIsLoading(false); + } + } + + // ============================================================================ + // Install Actions + // ============================================================================ + + async function installComposer() { + if ( + !(await confirmAlert({ + title: "Install Composer", + message: "This will download and install Composer. Requires PHP to be installed. Continue?", + primaryAction: { title: "Install" }, + })) + ) + return; + + try { + const toast = await showToast({ style: Toast.Style.Animated, title: "Installing Composer..." }); + + await execAsync(`php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"`); + await execAsync(`php composer-setup.php`); + await execAsync(`php -r "unlink('composer-setup.php');"`); + + toast.style = Toast.Style.Success; + toast.title = "Composer installed!"; + toast.message = "Move composer.phar to your PATH"; + } catch { + showToast({ + style: Toast.Style.Failure, + title: "Failed to install Composer", + message: "Visit getcomposer.org for manual installation", + }); + } + } + + async function installLaravelInstaller() { + try { + const toast = await showToast({ style: Toast.Style.Animated, title: "Installing Laravel Installer..." }); + await execAsync("composer global require laravel/installer"); + toast.style = Toast.Style.Success; + toast.title = "Laravel Installer ready!"; + } catch { + showToast({ + style: Toast.Style.Failure, + title: "Failed to install", + message: "Make sure Composer is installed first", + }); + } + } + + async function checkRequirements() { + const checks: string[] = []; + + try { + const { stdout } = await execAsync("php -v"); + const match = stdout.match(/PHP (\d+\.\d+)/); + checks.push(`✅ PHP ${match ? match[1] : "installed"}`); + } catch { + checks.push("❌ PHP not found"); + } + + try { + await execAsync("composer --version"); + checks.push("✅ Composer"); + } catch { + checks.push("❌ Composer not found"); + } + + try { + await execAsync("laravel --version"); + checks.push("✅ Laravel Installer"); + } catch { + checks.push("⚠️ Laravel Installer (optional)"); + } + + try { + await execAsync("git --version"); + checks.push("✅ Git"); + } catch { + checks.push("⚠️ Git not found"); + } + + try { + await execAsync("node --version"); + checks.push("✅ Node.js"); + } catch { + checks.push("⚠️ Node.js (needed for frontend)"); + } + + await confirmAlert({ + title: "Requirements Check", + message: checks.join("\n"), + primaryAction: { title: "OK", style: Alert.ActionStyle.Default }, + }); + } + + // ============================================================================ + // Render + // ============================================================================ + + return ( +
+ + + + + + + + + + } + > + {/* Project Basics */} + + + + + + {/* Base Auth Kit */} + + {BASE_KITS.map((kit) => ( + + ))} + + + {/* Stackable Packages */} + + {Object.entries(groupedPackages).map(([category, pkgs]) => + pkgs.map((pkg) => ), + )} + + + + + {/* Database & Testing */} + + {DATABASES.map((db) => ( + + ))} + + + + {TESTING_FRAMEWORKS.map((t) => ( + + ))} + + + {/* Dev Tools */} + + {DEV_TOOLS.map((tool) => ( + + ))} + + + + + {/* Options */} + + + + + + ); +} diff --git a/extensions/laravel-toolkit/src/laravel-docs.tsx b/extensions/laravel-toolkit/src/laravel-docs.tsx new file mode 100644 index 000000000000..98d42080d4e4 --- /dev/null +++ b/extensions/laravel-toolkit/src/laravel-docs.tsx @@ -0,0 +1,354 @@ +import { ActionPanel, List, Action, getPreferenceValues, LocalStorage, Icon, showToast, Toast } from "@raycast/api"; +import { useState, useEffect } from "react"; + +interface DocItem { + title: string; + path: string; + keywords?: string[]; +} + +interface DocSection { + title: string; + items: DocItem[]; +} + +const VERSIONS = [ + { title: "Laravel Master (Beta)", value: "master" }, + { title: "Laravel 12.x (Latest)", value: "12.x" }, + { title: "Laravel 11.x (LTS)", value: "11.x" }, + { title: "Laravel 10.x", value: "10.x" }, + { title: "Laravel 9.x", value: "9.x" }, + { title: "Laravel 8.x", value: "8.x" }, + { title: "Laravel 7.x", value: "7.x" }, + { title: "Laravel 6.x", value: "6.x" }, + { title: "Laravel 5.8", value: "5.8" }, + { title: "Laravel 5.7", value: "5.7" }, + { title: "Laravel 5.6", value: "5.6" }, + { title: "Laravel 5.5", value: "5.5" }, + { title: "Laravel 5.4", value: "5.4" }, + { title: "Laravel 5.3", value: "5.3" }, + { title: "Laravel 5.2", value: "5.2" }, + { title: "Laravel 5.1", value: "5.1" }, + { title: "Laravel 5.0", value: "5.0" }, +]; + +const LARAVEL_DOCS: DocSection[] = [ + { + title: "Prologue", + items: [ + { title: "Release Notes", path: "releases" }, + { title: "Upgrade Guide", path: "upgrade" }, + { title: "Contribution Guide", path: "contributions" }, + ], + }, + { + title: "Getting Started", + items: [ + { title: "Installation", path: "installation", keywords: ["setup", "install"] }, + { title: "Configuration", path: "configuration", keywords: ["env", "environment"] }, + { title: "Directory Structure", path: "structure", keywords: ["folders", "files"] }, + { title: "Frontend", path: "frontend", keywords: ["js", "css", "assets"] }, + { title: "Starter Kits", path: "starter-kits", keywords: ["breeze", "jetstream"] }, + { title: "Deployment", path: "deployment", keywords: ["production", "server"] }, + ], + }, + { + title: "Architecture Concepts", + items: [ + { title: "Request Lifecycle", path: "lifecycle", keywords: ["request", "boot"] }, + { title: "Service Container", path: "container", keywords: ["ioc", "dependency injection", "di"] }, + { title: "Service Providers", path: "providers", keywords: ["boot", "register"] }, + { title: "Facades", path: "facades", keywords: ["static", "proxy"] }, + ], + }, + { + title: "The Basics", + items: [ + { title: "Routing", path: "routing", keywords: ["routes", "url", "web", "api"] }, + { title: "Middleware", path: "middleware", keywords: ["filter", "request"] }, + { title: "CSRF Protection", path: "csrf", keywords: ["token", "security"] }, + { title: "Controllers", path: "controllers", keywords: ["handler", "action"] }, + { title: "Requests", path: "requests", keywords: ["input", "form"] }, + { title: "Responses", path: "responses", keywords: ["output", "json", "view"] }, + { title: "Views", path: "views", keywords: ["template", "html"] }, + { title: "Blade Templates", path: "blade", keywords: ["template", "components", "slots"] }, + { title: "Asset Bundling", path: "vite", keywords: ["vite", "css", "js", "build"] }, + { title: "URL Generation", path: "urls", keywords: ["links", "route"] }, + { title: "Session", path: "session", keywords: ["storage", "flash"] }, + { title: "Validation", path: "validation", keywords: ["rules", "form", "input"] }, + { title: "Error Handling", path: "errors", keywords: ["exceptions", "debug"] }, + { title: "Logging", path: "logging", keywords: ["log", "debug", "monolog"] }, + ], + }, + { + title: "Digging Deeper", + items: [ + { title: "Artisan Console", path: "artisan", keywords: ["cli", "commands"] }, + { title: "Broadcasting", path: "broadcasting", keywords: ["websocket", "pusher", "realtime"] }, + { title: "Cache", path: "cache", keywords: ["redis", "memcached", "storage"] }, + { title: "Collections", path: "collections", keywords: ["array", "map", "filter"] }, + { title: "Concurrency", path: "concurrency", keywords: ["parallel", "async"] }, + { title: "Context", path: "context", keywords: ["request context"] }, + { title: "Contracts", path: "contracts", keywords: ["interfaces"] }, + { title: "Events", path: "events", keywords: ["listeners", "dispatch"] }, + { title: "File Storage", path: "filesystem", keywords: ["files", "s3", "disk"] }, + { title: "Helpers", path: "helpers", keywords: ["functions", "utilities"] }, + { title: "HTTP Client", path: "http-client", keywords: ["guzzle", "api", "fetch"] }, + { title: "Localization", path: "localization", keywords: ["translation", "lang", "i18n"] }, + { title: "Mail", path: "mail", keywords: ["email", "smtp", "mailables"] }, + { title: "Notifications", path: "notifications", keywords: ["email", "sms", "slack"] }, + { title: "Package Development", path: "packages", keywords: ["library", "composer"] }, + { title: "Processes", path: "processes", keywords: ["shell", "exec", "command"] }, + { title: "Queues", path: "queues", keywords: ["jobs", "workers", "async"] }, + { title: "Rate Limiting", path: "rate-limiting", keywords: ["throttle", "limit"] }, + { title: "Strings", path: "strings", keywords: ["str", "text", "fluent"] }, + { title: "Task Scheduling", path: "scheduling", keywords: ["cron", "schedule", "jobs"] }, + ], + }, + { + title: "Security", + items: [ + { title: "Authentication", path: "authentication", keywords: ["login", "auth", "user"] }, + { title: "Authorization", path: "authorization", keywords: ["gates", "policies", "permissions"] }, + { title: "Email Verification", path: "verification", keywords: ["verify", "email"] }, + { title: "Encryption", path: "encryption", keywords: ["encrypt", "decrypt", "secure"] }, + { title: "Hashing", path: "hashing", keywords: ["bcrypt", "password", "hash"] }, + { title: "Password Reset", path: "passwords", keywords: ["forgot", "reset", "email"] }, + ], + }, + { + title: "Database", + items: [ + { title: "Getting Started", path: "database", keywords: ["db", "mysql", "postgres"] }, + { title: "Query Builder", path: "queries", keywords: ["sql", "select", "where", "join"] }, + { title: "Pagination", path: "pagination", keywords: ["pages", "limit", "offset"] }, + { title: "Migrations", path: "migrations", keywords: ["schema", "table", "columns"] }, + { title: "Seeding", path: "seeding", keywords: ["seed", "data", "factory"] }, + { title: "Redis", path: "redis", keywords: ["cache", "pubsub"] }, + { title: "MongoDB", path: "mongodb", keywords: ["nosql", "document"] }, + ], + }, + { + title: "Eloquent ORM", + items: [ + { title: "Getting Started", path: "eloquent", keywords: ["model", "orm", "active record"] }, + { title: "Relationships", path: "eloquent-relationships", keywords: ["hasMany", "belongsTo", "pivot"] }, + { title: "Collections", path: "eloquent-collections", keywords: ["array", "model collection"] }, + { title: "Mutators / Casts", path: "eloquent-mutators", keywords: ["accessors", "mutators", "cast"] }, + { title: "API Resources", path: "eloquent-resources", keywords: ["json", "transform", "api"] }, + { title: "Serialization", path: "eloquent-serialization", keywords: ["json", "toArray"] }, + { title: "Factories", path: "eloquent-factories", keywords: ["testing", "fake", "seed"] }, + ], + }, + { + title: "Testing", + items: [ + { title: "Getting Started", path: "testing", keywords: ["phpunit", "pest", "test"] }, + { title: "HTTP Tests", path: "http-tests", keywords: ["feature", "integration", "request"] }, + { title: "Console Tests", path: "console-tests", keywords: ["artisan", "command"] }, + { title: "Browser Tests", path: "dusk", keywords: ["dusk", "selenium", "e2e"] }, + { title: "Database Testing", path: "database-testing", keywords: ["refresh", "factory"] }, + { title: "Mocking", path: "mocking", keywords: ["mock", "fake", "spy"] }, + ], + }, + { + title: "Packages", + items: [ + { title: "Breeze", path: "starter-kits#laravel-breeze", keywords: ["auth", "starter"] }, + { title: "Cashier (Stripe)", path: "billing", keywords: ["payments", "subscriptions", "stripe"] }, + { title: "Cashier (Paddle)", path: "cashier-paddle", keywords: ["payments", "subscriptions"] }, + { title: "Dusk", path: "dusk", keywords: ["browser", "testing", "selenium"] }, + { title: "Envoy", path: "envoy", keywords: ["deploy", "ssh", "tasks"] }, + { title: "Fortify", path: "fortify", keywords: ["auth", "backend"] }, + { title: "Folio", path: "folio", keywords: ["pages", "file-based routing"] }, + { title: "Homestead", path: "homestead", keywords: ["vagrant", "vm", "development"] }, + { title: "Horizon", path: "horizon", keywords: ["queue", "dashboard", "redis"] }, + { title: "MCP", path: "mcp", keywords: ["model context protocol", "ai"] }, + { title: "Mix", path: "mix", keywords: ["webpack", "assets", "legacy"] }, + { title: "Octane", path: "octane", keywords: ["swoole", "roadrunner", "performance"] }, + { title: "Passport", path: "passport", keywords: ["oauth", "api", "tokens"] }, + { title: "Pennant", path: "pennant", keywords: ["feature flags", "toggles"] }, + { title: "Pint", path: "pint", keywords: ["code style", "formatter", "php-cs-fixer"] }, + { title: "Precognition", path: "precognition", keywords: ["validation", "live", "frontend"] }, + { title: "Prompts", path: "prompts", keywords: ["cli", "interactive", "console"] }, + { title: "Pulse", path: "pulse", keywords: ["monitoring", "dashboard", "performance"] }, + { title: "Reverb", path: "reverb", keywords: ["websocket", "realtime", "server"] }, + { title: "Sail", path: "sail", keywords: ["docker", "development", "container"] }, + { title: "Sanctum", path: "sanctum", keywords: ["api", "tokens", "spa", "auth"] }, + { title: "Scout", path: "scout", keywords: ["search", "algolia", "meilisearch"] }, + { title: "Socialite", path: "socialite", keywords: ["oauth", "social login", "google", "github"] }, + { title: "Telescope", path: "telescope", keywords: ["debug", "dashboard", "monitoring"] }, + { title: "Valet", path: "valet", keywords: ["macos", "development", "nginx"] }, + ], + }, +]; + +function getDocUrl(path: string, version: string): string { + return `https://laravel.com/docs/${version}/${path}`; +} + +function getLaracastsUrl(topic: string): string { + return `https://laracasts.com/search?q=${encodeURIComponent(topic)}`; +} + +const MAX_RECENT = 10; +const MAX_FAVORITES = 20; + +export default function Command() { + const preferences = getPreferenceValues(); + const [version, setVersion] = useState(preferences.laravelVersion || "12.x"); + const openInNewTab = preferences.openInNewTab !== false; + const [favorites, setFavorites] = useState([]); + const [recent, setRecent] = useState([]); + + // Load saved data from local storage + useEffect(() => { + LocalStorage.getItem("selectedVersion").then((savedVersion) => { + if (savedVersion) { + setVersion(savedVersion); + } + }); + LocalStorage.getItem("favorites").then((savedFavorites) => { + if (savedFavorites) { + setFavorites(JSON.parse(savedFavorites)); + } + }); + LocalStorage.getItem("recentPages").then((savedRecent) => { + if (savedRecent) { + setRecent(JSON.parse(savedRecent)); + } + }); + }, []); + + // Save version to local storage when changed + const handleVersionChange = async (newVersion: string) => { + setVersion(newVersion); + await LocalStorage.setItem("selectedVersion", newVersion); + }; + + // Toggle favorite + const toggleFavorite = async (path: string) => { + let newFavorites: string[]; + if (favorites.includes(path)) { + newFavorites = favorites.filter((f) => f !== path); + showToast({ style: Toast.Style.Success, title: "Removed from favorites" }); + } else { + if (favorites.length >= MAX_FAVORITES) { + showToast({ style: Toast.Style.Failure, title: "Maximum favorites reached" }); + return; + } + newFavorites = [...favorites, path]; + showToast({ style: Toast.Style.Success, title: "Added to favorites" }); + } + setFavorites(newFavorites); + await LocalStorage.setItem("favorites", JSON.stringify(newFavorites)); + }; + + // Add to recent + const addToRecent = async (path: string) => { + const newRecent = [path, ...recent.filter((r) => r !== path)].slice(0, MAX_RECENT); + setRecent(newRecent); + await LocalStorage.setItem("recentPages", JSON.stringify(newRecent)); + }; + + // Get doc item by path + const getDocByPath = (path: string): { item: DocItem; section: string } | null => { + for (const section of LARAVEL_DOCS) { + const item = section.items.find((i) => i.path === path); + if (item) { + return { item, section: section.title }; + } + } + return null; + }; + + // Render a doc item + const renderDocItem = (item: DocItem, sectionTitle: string, showFavoriteIcon = false) => { + const url = getDocUrl(item.path, version); + const isFavorite = favorites.includes(item.path); + + return ( + + + {openInNewTab ? ( + <> + addToRecent(item.path)} /> + + + ) : ( + <> + addToRecent(item.path)} /> + + + )} + + + toggleFavorite(item.path)} + /> + + + + + + {VERSIONS.map((v) => ( + handleVersionChange(v.value)} + /> + ))} + + + } + /> + ); + }; + + // Get favorite items + const favoriteItems = favorites + .map((path) => getDocByPath(path)) + .filter((item): item is { item: DocItem; section: string } => item !== null); + + // Get recent items (excluding favorites to avoid duplication) + const recentItems = recent + .filter((path) => !favorites.includes(path)) + .map((path) => getDocByPath(path)) + .filter((item): item is { item: DocItem; section: string } => item !== null); + + return ( + + {/* Favorites Section */} + {favoriteItems.length > 0 && ( + + {favoriteItems.map(({ item, section }) => renderDocItem(item, section, true))} + + )} + + {/* Recent Section */} + {recentItems.length > 0 && ( + + {recentItems.map(({ item, section }) => renderDocItem(item, section))} + + )} + + {/* All Documentation */} + {LARAVEL_DOCS.map((section) => ( + + {section.items.map((item) => renderDocItem(item, section.title))} + + ))} + + ); +} diff --git a/extensions/laravel-toolkit/src/log-viewer.tsx b/extensions/laravel-toolkit/src/log-viewer.tsx new file mode 100644 index 000000000000..fbce1cd5dc4a --- /dev/null +++ b/extensions/laravel-toolkit/src/log-viewer.tsx @@ -0,0 +1,147 @@ +import { ActionPanel, Action, showToast, Toast, Icon, LocalStorage, Detail, confirmAlert, Alert } from "@raycast/api"; +import { useState, useEffect } from "react"; +import { getProjects, Project } from "./utils/projects"; +import * as fs from "fs"; +import * as path from "path"; + +const LAST_PROJECT_KEY = "last-active-project-logs"; + +export default function Command() { + const [projects, setProjects] = useState([]); + const [currentProject, setCurrentProject] = useState(null); + const [logContent, setLogContent] = useState(""); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + loadData(); + }, []); + + useEffect(() => { + if (currentProject) { + readLog(currentProject.path); + } + }, [currentProject]); + + async function loadData() { + setIsLoading(true); + const savedProjects = await getProjects(); + setProjects(savedProjects); + + const lastPath = await LocalStorage.getItem(LAST_PROJECT_KEY); + let selected = savedProjects.length > 0 ? savedProjects[0] : null; + + if (lastPath) { + const match = savedProjects.find((p) => p.path === lastPath); + if (match) selected = match; + } + + setCurrentProject(selected); + setIsLoading(false); + } + + async function handleProjectChange(projectId: string) { + const project = projects.find((p) => p.path === projectId); + if (project) { + setCurrentProject(project); + await LocalStorage.setItem(LAST_PROJECT_KEY, project.path); + } + } + + async function readLog(projectPath: string) { + setIsLoading(true); + const logPath = path.join(projectPath, "storage", "logs", "laravel.log"); + if (fs.existsSync(logPath)) { + try { + // Read last 10KB + const stats = fs.statSync(logPath); + const size = stats.size; + const maxSize = 10000; + const start = Math.max(0, size - maxSize); + const stream = fs.createReadStream(logPath, { start, end: size, encoding: "utf-8" }); + + let data = ""; + for await (const chunk of stream) { + data += chunk; + } + setLogContent(data); + } catch (e) { + setLogContent(`Error reading log: ${e}`); + } + } else { + setLogContent("No laravel.log found in storage/logs/"); + } + setIsLoading(false); + } + + async function clearLog() { + if (!currentProject) return; + + const confirmed = await confirmAlert({ + title: "Clear Log", + message: "Are you sure you want to clear laravel.log?", + primaryAction: { title: "Clear", style: Alert.ActionStyle.Destructive }, + }); + + if (confirmed) { + const logPath = path.join(currentProject.path, "storage", "logs", "laravel.log"); + if (fs.existsSync(logPath)) { + fs.writeFileSync(logPath, ""); + readLog(currentProject.path); + showToast({ style: Toast.Style.Success, title: "Log cleared" }); + } + } + } + + // If no project, show list to select one? + // Consistence: Show Details view with specific Dropdown for project. + + return ( + + + + {currentProject && ( + { + try { + const p = path.join(currentProject.path, "storage", "logs", "laravel.log"); + if (fs.existsSync(p)) return (fs.statSync(p).size / 1024).toFixed(2) + " KB"; + } catch { + return "N/A"; + } + return "0 KB"; + })()} + /> + )} + + } + actions={ + + + currentProject && readLog(currentProject.path)} + /> + + + + {projects.map((p) => ( + handleProjectChange(p.path)} + /> + ))} + + + } + /> + ); +} diff --git a/extensions/laravel-toolkit/src/manage-custom-packages.tsx b/extensions/laravel-toolkit/src/manage-custom-packages.tsx new file mode 100644 index 000000000000..4bc9d47b9b78 --- /dev/null +++ b/extensions/laravel-toolkit/src/manage-custom-packages.tsx @@ -0,0 +1,164 @@ +import { + ActionPanel, + Action, + List, + Icon, + showToast, + Toast, + Form, + useNavigation, + confirmAlert, + Alert, +} from "@raycast/api"; +import { useState, useEffect } from "react"; +import { + getCustomPackages, + addCustomPackage, + removeCustomPackage, + updateCustomPackage, + CustomPackage, +} from "./utils/custom-packages"; + +export default function Command() { + const [packages, setPackages] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + loadPackages(); + }, []); + + async function loadPackages() { + setIsLoading(true); + const data = await getCustomPackages(); + setPackages(data); + setIsLoading(false); + } + + async function handleDelete(id: string) { + if ( + await confirmAlert({ + title: "Delete Package?", + message: "This cannot be undone.", + primaryAction: { title: "Delete", style: Alert.ActionStyle.Destructive }, + }) + ) { + await removeCustomPackage(id); + await loadPackages(); + showToast({ style: Toast.Style.Success, title: "Package Deleted" }); + } + } + + return ( + + {packages.length === 0 && !isLoading ? ( + + } /> + + } + /> + ) : ( + packages.map((pkg) => ( + + + } + /> + handleDelete(pkg.id)} + /> + + + } + /> + + + } + /> + )) + )} + + ); +} + +function PackageForm({ pkg, onSuccess }: { pkg?: CustomPackage; onSuccess: () => void }) { + const { pop } = useNavigation(); + + async function handleSubmit(values: { title: string; package: string; type: string; description: string }) { + if (!values.title || !values.package) { + showToast({ style: Toast.Style.Failure, title: "Title and Package Name required" }); + return; + } + + try { + if (pkg) { + await updateCustomPackage(pkg.id, { + title: values.title, + package: values.package, + type: values.type as "composer" | "npm", + description: values.description, + }); + showToast({ style: Toast.Style.Success, title: "Package Updated" }); + } else { + await addCustomPackage({ + title: values.title, + package: values.package, + type: values.type as "composer" | "npm", + description: values.description, + }); + showToast({ style: Toast.Style.Success, title: "Package Added" }); + } + onSuccess(); + pop(); + } catch { + showToast({ style: Toast.Style.Failure, title: "Failed to save package" }); + } + } + + return ( +
+ + + } + > + + + + + + + + + ); +} diff --git a/extensions/laravel-toolkit/src/manage-projects.tsx b/extensions/laravel-toolkit/src/manage-projects.tsx new file mode 100644 index 000000000000..6e03778b0242 --- /dev/null +++ b/extensions/laravel-toolkit/src/manage-projects.tsx @@ -0,0 +1,239 @@ +import { + ActionPanel, + Action, + List, + Icon, + confirmAlert, + Alert, + showToast, + Toast, + Form, + useNavigation, + closeMainWindow, +} from "@raycast/api"; +import { useState, useEffect } from "react"; +import { getProjects, removeProject, saveProject, Project } from "./utils/projects"; +import { openInEditor } from "./utils/editor"; +import * as fs from "fs"; +import * as path from "path"; +import { exec } from "child_process"; + +export default function Command() { + const [projects, setProjects] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + loadProjects(); + }, []); + + async function loadProjects() { + setIsLoading(true); + const projectList = await getProjects(); + setProjects(projectList); + setIsLoading(false); + } + + async function handleRemove(project: Project) { + const confirmed = await confirmAlert({ + title: "Remove Project", + message: `Remove "${project.name}" from the list? (This won't delete any files)`, + primaryAction: { title: "Remove", style: Alert.ActionStyle.Destructive }, + }); + + if (confirmed) { + await removeProject(project.path); + await loadProjects(); + showToast({ style: Toast.Style.Success, title: "Project removed" }); + } + } + + async function handleOpenInEditor(project: Project) { + await openInEditor(project.path); + } + + async function openExternalTerminal(projectPath: string) { + const command = `start powershell -NoExit -Command "Set-Location '${projectPath.replace(/'/g, "''")}'"`; + exec(command, (error) => { + if (error) { + showToast({ + style: Toast.Style.Failure, + title: "Failed to Open Terminal", + message: error.message, + }); + } + }); + await closeMainWindow(); + } + + function getKitLabel(kit?: string): string { + if (!kit || kit === "imported") return "Imported Project"; + if (kit === "none") return "Bare Laravel"; + return kit.replace("-", " ").replace(/\b\w/g, (c) => c.toUpperCase()); + } + + async function handleImport(folderPath: string) { + if (!fs.existsSync(path.join(folderPath, "artisan"))) { + showToast({ + style: Toast.Style.Failure, + title: "Not a Laravel Project", + message: "The selected folder doesn't appear to be a Laravel project (no artisan file found)", + }); + return false; + } + + const projectName = path.basename(folderPath); + await saveProject({ + path: folderPath, + name: projectName, + createdAt: new Date().toISOString(), + baseKit: "imported", + }); + + await loadProjects(); + showToast({ style: Toast.Style.Success, title: "Project Imported", message: projectName }); + return true; + } + + return ( + + {projects.length === 0 && !isLoading ? ( + + } + /> + + } + /> + ) : ( + projects.map((project) => ( + } + actions={ + + + handleOpenInEditor(project)} /> + + openExternalTerminal(project.path)} + /> + + + } + /> + + handleRemove(project)} + /> + + + } + /> + )) + )} + + ); +} + +import { getProjectDetails, ProjectDetails } from "./utils/project-details"; + +function ProjectDetailView({ project, kitLabel }: { project: Project; kitLabel: string }) { + const [details, setDetails] = useState(null); + + useEffect(() => { + let isMounted = true; + getProjectDetails(project.path).then((d) => { + if (isMounted) setDetails(d); + }); + return () => { + isMounted = false; + }; + }, [project.path]); + + return ( + `- ${d}`).join("\n")}` : ""} +`} + metadata={ + + + + + + + + {details?.starterKit === "Standard" && details?.detectedDependencies && ( + + )} + + } + /> + ); +} + +function ImportProjectForm({ onImport }: { onImport: (path: string) => Promise }) { + const { pop } = useNavigation(); + + async function handleSubmit(values: { folder: string[] }) { + if (values.folder && values.folder.length > 0) { + const success = await onImport(values.folder[0]); + if (success) { + pop(); + } + } else { + showToast({ style: Toast.Style.Failure, title: "No folder selected" }); + } + } + + return ( +
+ + + } + > + + + ); +} diff --git a/extensions/laravel-toolkit/src/manage-tools.tsx b/extensions/laravel-toolkit/src/manage-tools.tsx new file mode 100644 index 000000000000..6cec350a83c4 --- /dev/null +++ b/extensions/laravel-toolkit/src/manage-tools.tsx @@ -0,0 +1,317 @@ +import { + ActionPanel, + List, + Action, + Icon, + Color, + showToast, + Toast, + Form, + useNavigation, + confirmAlert, + Alert, +} from "@raycast/api"; +import { useState, useEffect } from "react"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { getPresets, getCustomTools, saveCustomTool, removeCustomTool, getVersion, DevTool } from "./utils/tools"; + +const execAsync = promisify(exec); + +export default function Command() { + const [tools, setTools] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + refreshTools(); + }, []); + + async function refreshTools() { + setIsLoading(true); + const presets = await getPresets(); + const custom = await getCustomTools(); + const all = [...presets, ...custom]; + + // Parallel version fetching + await Promise.all( + all.map(async (t) => { + t.detectedVersion = await getVersion(t.versionCmd); + }), + ); + + setTools(all); + setIsLoading(false); + } + + async function runUpdate(tool: DevTool) { + if (!tool.updateCmd) { + showToast({ style: Toast.Style.Failure, title: "No update command defined" }); + return; + } + + const toast = await showToast({ style: Toast.Style.Animated, title: `Updating ${tool.name}...` }); + try { + if (tool.name === "PHP" && tool.manager === "chocolatey") { + // Choco often asks for confirmation, so we add -y in the command generation logic, + // but strictly speaking exec might fail if elevated rights are needed. + // On Windows, Raycast usually runs as user. + } + + await execAsync(tool.updateCmd); + toast.style = Toast.Style.Success; + toast.title = "Update Completed"; + await refreshTools(); + } catch (error) { + toast.style = Toast.Style.Failure; + toast.title = "Update Failed"; + toast.message = error instanceof Error ? error.message : String(error); + } + } + + async function runBatchUpdate() { + if ( + await confirmAlert({ + title: "Update All Tools", + message: "This will attempt to update all listed tools sequentially. Continue?", + }) + ) { + const toast = await showToast({ style: Toast.Style.Animated, title: "Starting Batch Update..." }); + + for (const tool of tools) { + if (tool.updateCmd) { + toast.message = `Updating ${tool.name}...`; + try { + await execAsync(tool.updateCmd); + } catch (e) { + console.error(`Failed to update ${tool.name}`, e); + } + } + } + + toast.style = Toast.Style.Success; + toast.title = "Batch Update Finished"; + await refreshTools(); + } + } + + async function handleDelete(id: string) { + if ( + await confirmAlert({ + title: "Remove Tool", + message: "Remove this custom tool from the list?", + primaryAction: { title: "Remove", style: Alert.ActionStyle.Destructive }, + }) + ) { + await removeCustomTool(id); + await refreshTools(); + } + } + + return ( + + + + } /> + + } + > + {tools.map((tool) => ( + + + runUpdate(tool)} /> + } + /> + + + {tool.isCustom && ( + } + /> + )} + {tool.isCustom && ( + handleDelete(tool.id)} + /> + )} + + + + + + } + /> + ))} + + ); +} + +function VersionManagerForm({ tool, onSuccess }: { tool: DevTool; onSuccess: () => void }) { + const { pop } = useNavigation(); + const [cleanInstall, setCleanInstall] = useState(false); + const [uninstallCommand, setUninstallCommand] = useState(tool.uninstallCmd || ""); + + useEffect(() => { + setUninstallCommand(tool.uninstallCmd || ""); + }, [tool]); + + async function handleSubmit(values: { version: string }) { + const toast = await showToast({ style: Toast.Style.Animated, title: "Processing..." }); + + try { + // 1. Clean Install (Uninstall first) + if (cleanInstall) { + if (!uninstallCommand) { + throw new Error("No uninstall command defined for this tool."); + } + toast.message = `Running: ${uninstallCommand}`; + await execAsync(uninstallCommand); + } + + // 2. Install Version + if (!tool.installVersionCmd) { + throw new Error("No version installation command available."); + } + + const cmd = tool.installVersionCmd.replace("{version}", values.version); + toast.message = `Installing version ${values.version}...`; + await execAsync(cmd); + + toast.style = Toast.Style.Success; + toast.title = "Operation Successful"; + onSuccess(); + pop(); + } catch (error) { + toast.style = Toast.Style.Failure; + toast.title = "Failed"; + toast.message = error instanceof Error ? error.message : String(error); + } + } + + return ( +
+ + + } + > + + + + + + + {cleanInstall && ( + + )} + + + + ); +} + +function EditToolForm({ tool, onSuccess }: { tool?: DevTool; onSuccess: () => void }) { + const { pop } = useNavigation(); + + async function handleSubmit(values: { + name: string; + versionCmd: string; + updateCmd: string; + installVersionCmd: string; + uninstallCmd: string; + }) { + const newTool: DevTool = { + id: tool?.id || crypto.randomUUID(), + name: values.name, + manager: "manual", // Custom tools are always manual/custom manager + versionCmd: values.versionCmd, + updateCmd: values.updateCmd, + installVersionCmd: values.installVersionCmd, + uninstallCmd: values.uninstallCmd, + isCustom: true, + }; + + await saveCustomTool(newTool); + showToast({ style: Toast.Style.Success, title: "Tool Saved" }); + onSuccess(); + pop(); + } + + return ( +
+ + + } + > + + + + + + + + + + + ); +} diff --git a/extensions/laravel-toolkit/src/model-factory.tsx b/extensions/laravel-toolkit/src/model-factory.tsx new file mode 100644 index 000000000000..bf5b2cfeb499 --- /dev/null +++ b/extensions/laravel-toolkit/src/model-factory.tsx @@ -0,0 +1,126 @@ +import { ActionPanel, Action, showToast, Toast, Icon, LocalStorage, useNavigation, Form } from "@raycast/api"; +import { useState, useEffect } from "react"; +import { getProjects, Project } from "./utils/projects"; +import * as fs from "fs"; +import * as path from "path"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { openInEditor } from "./utils/editor"; + +const execAsync = promisify(exec); +const LAST_PROJECT_KEY = "last-active-project-factory"; + +export default function Command() { + const [projects, setProjects] = useState([]); + const [currentProject, setCurrentProject] = useState(null); + const [models, setModels] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + const { pop } = useNavigation(); + + useEffect(() => { + loadData(); + }, []); + + useEffect(() => { + if (currentProject) { + scanModels(currentProject.path); + } + }, [currentProject]); + + async function loadData() { + setIsLoading(true); + const savedProjects = await getProjects(); + setProjects(savedProjects); + + const lastPath = await LocalStorage.getItem(LAST_PROJECT_KEY); + let selected = savedProjects.length > 0 ? savedProjects[0] : null; + + if (lastPath) { + const match = savedProjects.find((p) => p.path === lastPath); + if (match) selected = match; + } + + setCurrentProject(selected); + setIsLoading(false); + } + + async function handleProjectChange(projectId: string) { + const project = projects.find((p) => p.path === projectId); + if (project) { + setCurrentProject(project); + await LocalStorage.setItem(LAST_PROJECT_KEY, project.path); + } + } + + function scanModels(projectPath: string) { + const modelsPath = path.join(projectPath, "app", "Models"); + if (fs.existsSync(modelsPath)) { + const files = fs.readdirSync(modelsPath); + const modelFiles = files.filter((f) => f.endsWith(".php")).map((f) => f.replace(".php", "")); + setModels(modelFiles); + } else { + setModels([]); + } + } + + async function handleSubmit(values: { model: string; name: string; open: boolean }) { + if (!currentProject) return; + + setIsLoading(true); + try { + const command = `php artisan make:factory ${values.name} --model=${values.model}`; + await execAsync(`cd "${currentProject.path}" && ${command}`); + + showToast({ style: Toast.Style.Success, title: "Factory Created", message: values.name }); + + if (values.open) { + // Guess the path + const factoryPath = path.join(currentProject.path, "database", "factories", values.name + ".php"); + await openInEditor(factoryPath); + } + pop(); + } catch (error) { + showToast({ style: Toast.Style.Failure, title: "Failed to create factory", message: String(error) }); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + + } + > + + {projects.map((p) => ( + + ))} + + + { + // We can't easily auto-update the 'name' field based on this selection without controlled state for 'name', + // but Raycast Form typically handles values via ref or onChange events updating state. + // For simplicity, we assume user types name or we rely on default naming convention of artisan if argument provided? + // Actually, `values` in handleSubmit will have the selected model. + // To auto-fill Name: we need controlled component. + }} + > + {models.map((m) => ( + + ))} + + + + + + + ); +} diff --git a/extensions/laravel-toolkit/src/route-explorer.tsx b/extensions/laravel-toolkit/src/route-explorer.tsx new file mode 100644 index 000000000000..b4a42ef4d183 --- /dev/null +++ b/extensions/laravel-toolkit/src/route-explorer.tsx @@ -0,0 +1,156 @@ +import { ActionPanel, List, Action, Icon, LocalStorage, Color } from "@raycast/api"; +import { useState, useEffect } from "react"; +import { getProjects, Project } from "./utils/projects"; +import { getRoutes, Route } from "./utils/routes"; +import { openInEditor } from "./utils/editor"; + +const LAST_PROJECT_KEY = "last-active-project-routes"; + +export default function Command() { + const [projects, setProjects] = useState([]); + const [currentProject, setCurrentProject] = useState(null); + const [routes, setRoutes] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + loadData(); + }, []); + + useEffect(() => { + if (currentProject) { + fetchRoutes(currentProject.path); + } + }, [currentProject]); + + async function loadData() { + setIsLoading(true); + const savedProjects = await getProjects(); + setProjects(savedProjects); + + const lastPath = await LocalStorage.getItem(LAST_PROJECT_KEY); + let selected = savedProjects.length > 0 ? savedProjects[0] : null; + + if (lastPath) { + const match = savedProjects.find((p) => p.path === lastPath); + if (match) selected = match; + } + + setCurrentProject(selected); + setIsLoading(false); + } + + async function fetchRoutes(path: string) { + setIsLoading(true); + const data = await getRoutes(path); + setRoutes(data); + setIsLoading(false); + } + + async function handleProjectChange(projectId: string) { + const project = projects.find((p) => p.path === projectId); + if (project) { + setCurrentProject(project); + await LocalStorage.setItem(LAST_PROJECT_KEY, project.path); + } + } + + return ( + { + // Check if it's a project ID or a method + // Actually, we can't easily mix project selection and method filter in one accessory unless we use two logic paths or one dropdown + // Limitation: Raycast only allows one searchBarAccessory? + // Actually, we can nest them or swap them? No. + // Best pattern: Project selection is primary. Method filter can be a separate dropdown if we could have 2, but we can't. + // Solution: Add project selection to the dropdown ONLY if we merge them, OR put project selection in a separate Step? + // Existing pattern in run-artisan uses project selection in dropdown. + // I will put Method filter as a separate dropdown if possible? No. + // I will stick to Project Selection in dropdown. Method filtering can be done via search text "GET /api" or just text. + // OR I can use the same dropdown for project selection, but that blocks filtering. + + // BETTER: Use `List.Dropdown` for Method Filter, and `Action` to switch project? + // OR: Standardize on Project Selection in dropdown, and just search. + handleProjectChange(val); + }} + value={currentProject?.path} + > + + {projects.map((p) => ( + + ))} + + + } + > + {projects.length === 0 && !isLoading ? ( + + ) : ( + routes.map((route, index) => ( + `- ${m}`).join("\n")} +`} + metadata={ + + + + + + } + /> + } + actions={ + + + + + + + currentProject && openInEditor(currentProject.path)} + /> + + + } + /> + )) + )} + + ); +} diff --git a/extensions/laravel-toolkit/src/run-artisan.tsx b/extensions/laravel-toolkit/src/run-artisan.tsx new file mode 100644 index 000000000000..42937ffbb1ec --- /dev/null +++ b/extensions/laravel-toolkit/src/run-artisan.tsx @@ -0,0 +1,235 @@ +import { + ActionPanel, + List, + Action, + showToast, + Toast, + Icon, + LocalStorage, + useNavigation, + Form, + Color, +} from "@raycast/api"; +import { useState, useEffect } from "react"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { getProjects, saveProject, Project } from "./utils/projects"; +import { getEditorApp } from "./utils/editor"; +import { getArtisanCommands, ArtisanCommand } from "./utils/artisan"; + +const execAsync = promisify(exec); +const LAST_PROJECT_KEY = "last-active-project"; + +export default function Command() { + const [projects, setProjects] = useState([]); + const [currentProject, setCurrentProject] = useState(null); + const [commands, setCommands] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + loadData(); + }, []); + + // When project changes, fetch its commands + useEffect(() => { + if (currentProject) { + fetchCommands(currentProject.path); + } + }, [currentProject]); + + async function loadData() { + setIsLoading(true); + const savedProjects = await getProjects(); + setProjects(savedProjects); + + const lastPath = await LocalStorage.getItem(LAST_PROJECT_KEY); + let selected = savedProjects.length > 0 ? savedProjects[0] : null; + + if (lastPath) { + const match = savedProjects.find((p) => p.path === lastPath); + if (match) selected = match; + } + + setCurrentProject(selected); + setIsLoading(false); + } + + async function fetchCommands(path: string) { + setIsLoading(true); + const cmds = await getArtisanCommands(path); + setCommands(cmds); + setIsLoading(false); + } + + async function handleProjectChange(projectId: string) { + const project = projects.find((p) => p.path === projectId); + if (project) { + setCurrentProject(project); + await LocalStorage.setItem(LAST_PROJECT_KEY, project.path); + } + } + + async function runArtisan(command: string) { + if (!currentProject) return; + + setIsLoading(true); + try { + const toast = await showToast({ + style: Toast.Style.Animated, + title: `Running: php artisan ${command}`, + }); + + const { stdout } = await execAsync(`cd "${currentProject.path.replace(/"/g, '\\"')}" && php artisan ${command}`); + + toast.style = Toast.Style.Success; + toast.title = "Command completed"; + toast.message = stdout.slice(0, 100); + + if (command === "serve") { + toast.message = "Server likely running (check terminal)"; + } + } catch (error) { + showToast({ + style: Toast.Style.Failure, + title: "Command failed", + message: error instanceof Error ? error.message : "Unknown error", + }); + } finally { + setIsLoading(false); + } + } + + async function importProject(path: string) { + const name = path.split("\\").pop() || path.split("/").pop() || "Untitled"; + const newProject: Project = { + name, + path, + createdAt: new Date().toISOString(), + }; + await saveProject(newProject); + setProjects(await getProjects()); + setCurrentProject(newProject); + await LocalStorage.setItem(LAST_PROJECT_KEY, newProject.path); + showToast({ style: Toast.Style.Success, title: "Project Imported", message: name }); + return true; + } + + const editorApp = getEditorApp(); + + // Group commands by namespace + const groupedCommands = commands.reduce( + (acc, cmd) => { + const parts = cmd.name.split(":"); + const namespace = parts.length > 1 ? parts[0] : "General"; + if (!acc[namespace]) acc[namespace] = []; + acc[namespace].push(cmd); + return acc; + }, + {} as Record, + ); + + const categories = Object.keys(groupedCommands).sort(); + + return ( + 0 ? ( + + {projects.map((p) => ( + + ))} + + ) : undefined + } + > + {projects.length === 0 && !isLoading ? ( + + } + /> + + } + /> + ) : ( + categories.map((category) => ( + + {groupedCommands[category].map((cmd) => ( + + + runArtisan(cmd.name)} /> + + + + } + /> + {currentProject && editorApp && ( + + )} + + + } + /> + ))} + + )) + )} + + ); +} + +function ImportProjectForm({ onImport }: { onImport: (path: string) => Promise }) { + const { pop } = useNavigation(); + + async function handleSubmit(values: { folder: string[] }) { + if (values.folder && values.folder.length > 0) { + const success = await onImport(values.folder[0]); + if (success) { + pop(); + } + } else { + showToast({ style: Toast.Style.Failure, title: "No folder selected" }); + } + } + + return ( +
+ + + } + > + + + ); +} diff --git a/extensions/laravel-toolkit/src/snippets.tsx b/extensions/laravel-toolkit/src/snippets.tsx new file mode 100644 index 000000000000..44af44f80ac4 --- /dev/null +++ b/extensions/laravel-toolkit/src/snippets.tsx @@ -0,0 +1,449 @@ +import { ActionPanel, List, Action, Icon, getPreferenceValues, Keyboard } from "@raycast/api"; + +// ============================================================================ +// Types +// ============================================================================ + +interface Snippet { + title: string; + code: string; + description: string; + category: string; +} + +// ============================================================================ +// Preferences Helper +// ============================================================================ + +function getSecondaryShortcut(modifier: Preferences.Snippets["snippetSecondaryModifier"]): Keyboard.Shortcut { + const modifierMap: Record = { + "cmd+shift": ["cmd", "shift"], + cmd: ["cmd"], + ctrl: ["ctrl"], + opt: ["opt"], + shift: ["shift"], + }; + return { modifiers: modifierMap[modifier] || ["cmd", "shift"], key: "enter" }; +} + +// ============================================================================ +// Snippet Actions Component +// ============================================================================ + +function SnippetActions({ code }: { code: string }) { + const { snippetPrimaryAction, snippetSecondaryModifier } = getPreferenceValues(); + const secondaryShortcut = getSecondaryShortcut(snippetSecondaryModifier); + + const isPasteFirst = snippetPrimaryAction === "paste"; + + return ( + + + {isPasteFirst ? ( + <> + + + + ) : ( + <> + + + + )} + + + ); +} + +// ============================================================================ +// Snippet Data +// ============================================================================ + +const SNIPPETS: Snippet[] = [ + // Routes + { + title: "Basic Route", + code: `Route::get('/welcome', function () { + return view('welcome'); +});`, + description: "Define a basic GET route", + category: "Routes", + }, + { + title: "Route with Controller", + code: `Route::get('/users', [UserController::class, 'index']);`, + description: "Route pointing to a controller method", + category: "Routes", + }, + { + title: "Resource Route", + code: `Route::resource('posts', PostController::class);`, + description: "RESTful resource routes", + category: "Routes", + }, + { + title: "API Resource Route", + code: `Route::apiResource('posts', PostController::class);`, + description: "API resource routes (no create/edit)", + category: "Routes", + }, + { + title: "Route Group with Middleware", + code: `Route::middleware(['auth'])->group(function () { + Route::get('/dashboard', [DashboardController::class, 'index']); + Route::get('/profile', [ProfileController::class, 'show']); +});`, + description: "Group routes with middleware", + category: "Routes", + }, + { + title: "Route with Parameters", + code: `Route::get('/users/{id}', function (string $id) { + return 'User ' . $id; +});`, + description: "Route with URL parameters", + category: "Routes", + }, + + // Controllers + { + title: "Controller Method", + code: `public function index() +{ + $users = User::all(); + return view('users.index', compact('users')); +}`, + description: "Basic controller method returning a view", + category: "Controllers", + }, + { + title: "Store Method with Validation", + code: `public function store(Request $request) +{ + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|email|unique:users', + 'password' => 'required|min:8|confirmed', + ]); + + $user = User::create($validated); + + return redirect()->route('users.show', $user); +}`, + description: "Controller store method with validation", + category: "Controllers", + }, + { + title: "API Response", + code: `public function index() +{ + return response()->json([ + 'data' => User::all(), + 'message' => 'Users retrieved successfully' + ]); +}`, + description: "Return JSON response in API controller", + category: "Controllers", + }, + + // Models + { + title: "Model with Fillable", + code: `class User extends Model +{ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + protected $hidden = [ + 'password', + 'remember_token', + ]; +}`, + description: "Basic Eloquent model with fillable attributes", + category: "Models", + }, + { + title: "HasMany Relationship", + code: `public function posts(): HasMany +{ + return $this->hasMany(Post::class); +}`, + description: "One-to-Many relationship", + category: "Models", + }, + { + title: "BelongsTo Relationship", + code: `public function user(): BelongsTo +{ + return $this->belongsTo(User::class); +}`, + description: "Inverse One-to-Many relationship", + category: "Models", + }, + { + title: "BelongsToMany Relationship", + code: `public function roles(): BelongsToMany +{ + return $this->belongsToMany(Role::class); +}`, + description: "Many-to-Many relationship", + category: "Models", + }, + { + title: "Model Scope", + code: `public function scopeActive(Builder $query): void +{ + $query->where('active', true); +} + +// Usage: User::active()->get();`, + description: "Local query scope", + category: "Models", + }, + { + title: "Model Accessor", + code: `protected function fullName(): Attribute +{ + return Attribute::make( + get: fn () => "{$this->first_name} {$this->last_name}", + ); +}`, + description: "Eloquent accessor (Laravel 9+)", + category: "Models", + }, + + // Migrations + { + title: "Create Table Migration", + code: `Schema::create('posts', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('title'); + $table->text('body'); + $table->boolean('published')->default(false); + $table->timestamps(); +});`, + description: "Create table with foreign key", + category: "Migrations", + }, + { + title: "Add Column Migration", + code: `Schema::table('users', function (Blueprint $table) { + $table->string('phone')->nullable()->after('email'); +});`, + description: "Add column to existing table", + category: "Migrations", + }, + { + title: "Drop Column Migration", + code: `Schema::table('users', function (Blueprint $table) { + $table->dropColumn('phone'); +});`, + description: "Remove column from table", + category: "Migrations", + }, + + // Blade + { + title: "Blade Layout", + code: ` + + + @yield('title') + + + @include('partials.nav') + +
+ @yield('content') +
+ +`, + description: "Base Blade layout template", + category: "Blade", + }, + { + title: "Blade Component", + code: ``, + description: "Use a Blade component", + category: "Blade", + }, + { + title: "Blade Conditional", + code: `@if ($user->isAdmin()) +

Welcome, Admin!

+@elseif ($user->isModerator()) +

Welcome, Moderator!

+@else +

Welcome, User!

+@endif`, + description: "If/else conditional in Blade", + category: "Blade", + }, + { + title: "Blade Loop", + code: `@foreach ($users as $user) +

{{ $user->name }}

+@endforeach + +@forelse ($users as $user) +

{{ $user->name }}

+@empty +

No users found.

+@endforelse`, + description: "Loop through collection in Blade", + category: "Blade", + }, + + // Validation + { + title: "Form Request", + code: `public function rules(): array +{ + return [ + 'title' => 'required|string|max:255', + 'body' => 'required|string', + 'published_at' => 'nullable|date', + 'tags' => 'array', + 'tags.*' => 'exists:tags,id', + ]; +}`, + description: "Form request validation rules", + category: "Validation", + }, + { + title: "Custom Validation Message", + code: `public function messages(): array +{ + return [ + 'title.required' => 'A title is required', + 'body.required' => 'Please provide content for your post', + ]; +}`, + description: "Custom validation error messages", + category: "Validation", + }, + + // Middleware + { + title: "Middleware Handle", + code: `public function handle(Request $request, Closure $next): Response +{ + if (! $request->user()->isAdmin()) { + abort(403); + } + + return $next($request); +}`, + description: "Basic middleware handle method", + category: "Middleware", + }, + + // Testing + { + title: "Feature Test", + code: `public function test_users_can_view_homepage(): void +{ + $response = $this->get('/'); + + $response->assertStatus(200); + $response->assertSee('Welcome'); +}`, + description: "Basic feature/HTTP test", + category: "Testing", + }, + { + title: "Test with Authentication", + code: `public function test_authenticated_user_can_create_post(): void +{ + $user = User::factory()->create(); + + $response = $this->actingAs($user) + ->post('/posts', [ + 'title' => 'My Post', + 'body' => 'Content here', + ]); + + $response->assertRedirect('/posts'); + $this->assertDatabaseHas('posts', ['title' => 'My Post']); +}`, + description: "Test with authenticated user", + category: "Testing", + }, + + // Queries + { + title: "Eloquent Query", + code: `$users = User::where('active', true) + ->orderBy('created_at', 'desc') + ->take(10) + ->get();`, + description: "Chained Eloquent query", + category: "Queries", + }, + { + title: "Eager Loading", + code: `$posts = Post::with(['user', 'comments.user']) + ->where('published', true) + ->get();`, + description: "Eager load relationships", + category: "Queries", + }, + { + title: "Query with Pagination", + code: `$users = User::where('active', true) + ->paginate(15); + +// In Blade: {{ $users->links() }}`, + description: "Paginate query results", + category: "Queries", + }, +]; + +// ============================================================================ +// Utility Functions +// ============================================================================ + +function groupByCategory(snippets: Snippet[]): Record { + return snippets.reduce( + (acc, snippet) => { + if (!acc[snippet.category]) { + acc[snippet.category] = []; + } + acc[snippet.category].push(snippet); + return acc; + }, + {} as Record, + ); +} + +// ============================================================================ +// Main Command +// ============================================================================ + +export default function Command() { + const grouped = groupByCategory(SNIPPETS); + const categories = Object.keys(grouped); + + return ( + + {categories.map((category) => ( + + {grouped[category].map((snippet) => ( + } + /> + ))} + + ))} + + ); +} diff --git a/extensions/laravel-toolkit/src/utils/artisan.ts b/extensions/laravel-toolkit/src/utils/artisan.ts new file mode 100644 index 000000000000..cbb9829bb9fc --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/artisan.ts @@ -0,0 +1,63 @@ +import { exec } from "child_process"; +import { promisify } from "util"; +import { Cache } from "@raycast/api"; + +const execAsync = promisify(exec); +const cache = new Cache(); + +export interface ArtisanCommand { + name: string; + description: string; + usage: string; + definition?: { + arguments: Record; + options: Record; + }; + hidden?: boolean; +} + +export async function getArtisanCommands(projectPath: string, refresh = false): Promise { + const cacheKey = `artisan-commands-${projectPath}`; + + if (!refresh) { + const cached = cache.get(cacheKey); + if (cached) { + try { + return JSON.parse(cached); + } catch { + // ignore + } + } + } + + try { + const { stdout } = await execAsync(`php artisan list --format=json`, { + cwd: projectPath, + maxBuffer: 1024 * 1024 * 5, + }); + const parsed = JSON.parse(stdout); + + // The structure of Laravel's json output is { application: {...}, commands: [ ... ] } + // commands might be an array or object depending on version, usually array of objects. + let commands: ArtisanCommand[] = []; + + if (Array.isArray(parsed.commands)) { + commands = parsed.commands; + } else if (typeof parsed.commands === "object") { + // sometimes it's an object keyed by command name? + // Actually typical `php artisan list --format=json` returns "commands": [ ... ] + commands = Object.values(parsed.commands); + } + + // Filter out hidden commands usually? Or keep them? Let's keep normally usable ones. + // "hidden": true property exists. + commands = commands.filter((c) => !c.hidden); + + cache.set(cacheKey, JSON.stringify(commands)); + return commands; + } catch (error) { + console.error("Failed to fetch artisan commands", error); + // Return empty or fallback + return []; + } +} diff --git a/extensions/laravel-toolkit/src/utils/custom-packages.ts b/extensions/laravel-toolkit/src/utils/custom-packages.ts new file mode 100644 index 000000000000..790f489fc1d2 --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/custom-packages.ts @@ -0,0 +1,48 @@ +import { LocalStorage } from "@raycast/api"; + +export interface CustomPackage { + id: string; + title: string; + package: string; + type: "composer" | "npm"; + description?: string; +} + +const STORAGE_KEY = "custom_packages"; + +export async function getCustomPackages(): Promise { + const data = await LocalStorage.getItem(STORAGE_KEY); + if (!data) return []; + try { + return JSON.parse(data); + } catch (e) { + console.error("Failed to parse custom packages:", e); + return []; + } +} + +export async function saveCustomPackages(packages: CustomPackage[]): Promise { + await LocalStorage.setItem(STORAGE_KEY, JSON.stringify(packages)); +} + +export async function addCustomPackage(pkg: Omit): Promise { + const packages = await getCustomPackages(); + const newPackage = { ...pkg, id: crypto.randomUUID() }; + packages.push(newPackage); + await saveCustomPackages(packages); +} + +export async function removeCustomPackage(id: string): Promise { + const packages = await getCustomPackages(); + const filtered = packages.filter((p) => p.id !== id); + await saveCustomPackages(filtered); +} + +export async function updateCustomPackage(id: string, updates: Partial>): Promise { + const packages = await getCustomPackages(); + const index = packages.findIndex((p) => p.id === id); + if (index !== -1) { + packages[index] = { ...packages[index], ...updates }; + await saveCustomPackages(packages); + } +} diff --git a/extensions/laravel-toolkit/src/utils/editor.ts b/extensions/laravel-toolkit/src/utils/editor.ts new file mode 100644 index 000000000000..4a302ddc151e --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/editor.ts @@ -0,0 +1,55 @@ +import { getPreferenceValues, open, Application, showToast, Toast } from "@raycast/api"; +import { exec } from "child_process"; + +/** + * Opens a path in the user's configured editor. + * Falls back to the system default if no editor is configured, but warns the user. + */ +export async function openInEditor(path: string): Promise { + const { editorApplication } = getPreferenceValues(); + + if (editorApplication) { + try { + if (editorApplication.path) { + // Manual launch for specific path + const safeEditor = editorApplication.path.replace(/"/g, '\\"'); + const safePath = path.replace(/"/g, '\\"'); + exec(`"${safeEditor}" "${safePath}"`, (error) => { + if (error) { + console.error("Failed to open with editor:", error); + showToast({ + style: Toast.Style.Failure, + title: "Failed to open in selected editor", + message: error.message, + }); + } + }); + } else { + // Fallback if path is missing + await open(path, editorApplication); + } + } catch (error) { + showToast({ + style: Toast.Style.Failure, + title: "Failed to open with editor", + message: error instanceof Error ? error.message : "Unknown error", + }); + } + } else { + // No editor configured: Do NOT open in Finder. FAIL explicitly. + showToast({ + style: Toast.Style.Failure, + title: "No Editor Configured", + message: "Please select your Code Editor in the Extension Settings.", + }); + // We intentionally do NOT call open(path) here anymore. + } +} + +/** + * Gets the configured editor application, if any. + */ +export function getEditorApp(): Application | undefined { + const { editorApplication } = getPreferenceValues(); + return editorApplication; +} diff --git a/extensions/laravel-toolkit/src/utils/project-details.ts b/extensions/laravel-toolkit/src/utils/project-details.ts new file mode 100644 index 000000000000..aa2b156f7bd6 --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/project-details.ts @@ -0,0 +1,99 @@ +import * as fs from "fs"; +import * as path from "path"; +import { exec } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +export interface ProjectDetails { + laravelVersion: string; + phpVersion: string; + database: string; + debugMode: boolean; + composer?: unknown; + starterKit?: string; + detectedDependencies?: string[]; +} + +export async function getProjectDetails(projectPath: string): Promise { + const details: ProjectDetails = { + laravelVersion: "Unknown", + phpVersion: "Unknown", + database: "Unknown", + debugMode: false, + starterKit: "Unknown", + }; + + try { + // Get Laravel Version + try { + const { stdout } = await execAsync(`php artisan --version`, { cwd: projectPath }); + const match = stdout.match(/Laravel Framework\s+([0-9.]+)/); + if (match) details.laravelVersion = match[1]; + } catch { + // ignore + } + + // Get PHP Version + try { + const { stdout } = await execAsync(`php -v`); + const match = stdout.match(/^PHP\s+([0-9.]+)/); + if (match) details.phpVersion = match[1]; + } catch { + // ignore + } + + // Read composer.json for Kit Detection + try { + const composerPath = path.join(projectPath, "composer.json"); + if (fs.existsSync(composerPath)) { + const composer = JSON.parse(fs.readFileSync(composerPath, "utf-8")); + details.composer = composer; + + const require = composer.require || {}; + const requireDev = composer["require-dev"] || {}; + const allDeps = { ...require, ...requireDev }; + + const notable: string[] = []; + const NOTABLE_PACKAGES: Record = { + "laravel/jetstream": "Jetstream", + "laravel/breeze": "Breeze", + "filament/filament": "Filament", + "livewire/livewire": "Livewire", + "livewire/flux": "Flux", + "livewire/volt": "Volt", + "inertiajs/inertia-laravel": "Inertia", + "laravel/nova": "Nova", + "statamic/cms": "Statamic", + }; + + for (const [pkg, label] of Object.entries(NOTABLE_PACKAGES)) { + if (allDeps[pkg]) { + notable.push(label); + } + } + + details.starterKit = notable.length > 0 ? notable.join(", ") : "Standard"; + details.detectedDependencies = Object.keys(allDeps); + } + } catch { + // ignore + } + + // Read .env for DB and Debug + const envPath = path.join(projectPath, ".env"); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, "utf-8"); + const dbConnection = envContent.match(/^DB_CONNECTION=(.*)$/m)?.[1] || ""; + const dbDatabase = envContent.match(/^DB_DATABASE=(.*)$/m)?.[1] || ""; + const appDebug = envContent.match(/^APP_DEBUG=(.*)$/m)?.[1] || "false"; + + details.database = `${dbConnection} (${dbDatabase})`; + details.debugMode = appDebug.trim().toLowerCase() === "true"; + } + } catch (error) { + console.error("Failed to get project details", error); + } + + return details; +} diff --git a/extensions/laravel-toolkit/src/utils/projects.ts b/extensions/laravel-toolkit/src/utils/projects.ts new file mode 100644 index 000000000000..1e708b9d84cb --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/projects.ts @@ -0,0 +1,55 @@ +import { LocalStorage } from "@raycast/api"; + +export interface Project { + path: string; + name: string; + createdAt: string; + baseKit?: string; + packages?: string[]; +} + +const STORAGE_KEY = "laravel-projects"; + +/** + * Get all saved Laravel projects + */ +export async function getProjects(): Promise { + const json = await LocalStorage.getItem(STORAGE_KEY); + if (!json) return []; + try { + return JSON.parse(json); + } catch { + return []; + } +} + +/** + * Save a new project to the list + */ +export async function saveProject(project: Project): Promise { + const projects = await getProjects(); + // Check if project already exists + const existing = projects.findIndex((p) => p.path === project.path); + if (existing >= 0) { + projects[existing] = project; + } else { + projects.unshift(project); // Add to beginning + } + await LocalStorage.setItem(STORAGE_KEY, JSON.stringify(projects)); +} + +/** + * Remove a project from the list + */ +export async function removeProject(path: string): Promise { + const projects = await getProjects(); + const filtered = projects.filter((p) => p.path !== path); + await LocalStorage.setItem(STORAGE_KEY, JSON.stringify(filtered)); +} + +/** + * Clear all projects + */ +export async function clearProjects(): Promise { + await LocalStorage.removeItem(STORAGE_KEY); +} diff --git a/extensions/laravel-toolkit/src/utils/routes.ts b/extensions/laravel-toolkit/src/utils/routes.ts new file mode 100644 index 000000000000..d64814f12e65 --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/routes.ts @@ -0,0 +1,41 @@ +import { exec } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +export interface Route { + domain: string | null; + method: string; + uri: string; + name: string | null; + action: string; + middleware: string[]; +} + +export async function getRoutes(projectPath: string, filterVal = ""): Promise { + try { + const { stdout } = await execAsync(`php artisan route:list --json`, { + cwd: projectPath, + maxBuffer: 1024 * 1024 * 5, + }); + let routes: Route[] = JSON.parse(stdout); + + // Normalize middleware if it's not an array (older laravel versions might differ, but usually array) + // Sometimes middleware is a string in older versions? NO, typically array. + + if (filterVal) { + const lower = filterVal.toLowerCase(); + routes = routes.filter( + (r) => + r.uri.toLowerCase().includes(lower) || + (r.name && r.name.toLowerCase().includes(lower)) || + r.action.toLowerCase().includes(lower), + ); + } + + return routes; + } catch (error) { + console.error("Failed to fetch routes", error); + return []; + } +} diff --git a/extensions/laravel-toolkit/src/utils/tools.ts b/extensions/laravel-toolkit/src/utils/tools.ts new file mode 100644 index 000000000000..77f459dd85ad --- /dev/null +++ b/extensions/laravel-toolkit/src/utils/tools.ts @@ -0,0 +1,195 @@ +import { exec } from "child_process"; +import { promisify } from "util"; +import { LocalStorage } from "@raycast/api"; +import * as os from "os"; + +const execAsync = promisify(exec); + +export type ToolManager = "chocolatey" | "scoop" | "npm" | "composer" | "manual" | "herd"; + +export interface DevTool { + id: string; + name: string; + manager: ToolManager; + versionCmd: string; + updateCmd: string; + installVersionCmd?: string; // Command to install specific version + uninstallCmd?: string; // Command to remove + isCustom: boolean; + detectedVersion?: string; +} + +const CUSTOM_TOOLS_KEY = "custom_dev_tools"; + +/** + * Detects the package manager used for a specific binary by checking its path + */ +async function detectManager(binary: string): Promise { + if (os.platform() !== "win32") return "manual"; // Only smart detect on Windows for now + + try { + const { stdout } = await execAsync(`where ${binary}`); + const path = stdout.toLowerCase(); + + if (path.includes("herd")) return "herd"; + if (path.includes("chocolatey")) return "chocolatey"; + if (path.includes("scoop")) return "scoop"; + if (path.includes("npm") || path.includes("node_modules")) return "npm"; + } catch { + // Binary not found + } + return "manual"; +} + +/** + * Generates default commands based on the detected manager + */ +export async function getPresets(): Promise { + const tools: DevTool[] = []; + + // 1. PHP + const phpManager = await detectManager("php"); + const phpTool: DevTool = { + id: "php", + name: "PHP", + manager: phpManager, + versionCmd: "php -v", + updateCmd: "", + isCustom: false, + }; + + switch (phpManager) { + case "chocolatey": + phpTool.updateCmd = "choco upgrade php -y"; + phpTool.installVersionCmd = "choco install php --version={version} --allow-downgrade -y"; + phpTool.uninstallCmd = "choco uninstall php --remove-dependencies --force -y"; + break; + case "scoop": + phpTool.updateCmd = "scoop update php"; + phpTool.installVersionCmd = "scoop install php@{version}"; + phpTool.uninstallCmd = "scoop uninstall php --purge"; + break; + case "herd": + phpTool.updateCmd = "herd update"; + phpTool.uninstallCmd = "echo 'Herd manages its own binaries. Use Herd GUI to uninstall.'"; + break; + default: + phpTool.updateCmd = "echo 'Manual update required'"; + phpTool.uninstallCmd = "echo 'Manual install: Remove the PHP folder manually'"; + } + tools.push(phpTool); + + // 2. Composer + tools.push({ + id: "composer", + name: "Composer", + manager: "composer", + versionCmd: "composer --version", + updateCmd: "composer self-update", + installVersionCmd: "composer self-update {version}", + uninstallCmd: "composer global remove composer", + isCustom: false, + }); + + // 3. NPM + tools.push({ + id: "npm", + name: "NPM", + manager: "npm", + versionCmd: "npm -v", + updateCmd: "npm install -g npm@latest", + installVersionCmd: "npm install -g npm@{version}", + uninstallCmd: "npm uninstall -g npm", + isCustom: false, + }); + + // 4. Laravel Installer + tools.push({ + id: "laravel", + name: "Laravel Installer", + manager: "composer", + versionCmd: "laravel --version", + updateCmd: "composer global require laravel/installer", + uninstallCmd: "composer global remove laravel/installer", + isCustom: false, + }); + + // 5. Git + tools.push({ + id: "git", + name: "Git", + manager: await detectManager("git"), + versionCmd: "git --version", + updateCmd: "echo 'Update Git via your system package manager'", + uninstallCmd: "echo 'Uninstall Git via system config'", + isCustom: false, + }); + + // 6. Node.js + tools.push({ + id: "node", + name: "Node.js", + manager: await detectManager("node"), + versionCmd: "node -v", + updateCmd: "echo 'Use nvm or installer to update Node'", + uninstallCmd: "echo 'Uninstall Node via system config'", + isCustom: false, + }); + + // 7. Docker + tools.push({ + id: "docker", + name: "Docker", + manager: "manual", + versionCmd: "docker --version", + updateCmd: "echo 'Update Docker Desktop app'", + uninstallCmd: "echo 'Uninstall Docker Desktop'", + isCustom: false, + }); + + // 8. MySQL + tools.push({ + id: "mysql", + name: "MySQL", + manager: await detectManager("mysql"), + versionCmd: "mysql --version", + updateCmd: "echo 'Update MySQL via system manager'", + uninstallCmd: "echo 'Uninstall MySQL via system manager'", + isCustom: false, + }); + + return tools; +} + +export async function getCustomTools(): Promise { + const data = await LocalStorage.getItem(CUSTOM_TOOLS_KEY); + return data ? JSON.parse(data) : []; +} + +export async function saveCustomTool(tool: DevTool) { + const tools = await getCustomTools(); + const existing = tools.findIndex((t) => t.id === tool.id); + if (existing !== -1) { + tools[existing] = tool; + } else { + tools.push(tool); + } + await LocalStorage.setItem(CUSTOM_TOOLS_KEY, JSON.stringify(tools)); +} + +export async function removeCustomTool(id: string) { + const tools = await getCustomTools(); + const filtered = tools.filter((t) => t.id !== id); + await LocalStorage.setItem(CUSTOM_TOOLS_KEY, JSON.stringify(filtered)); +} + +export async function getVersion(cmd: string): Promise { + try { + const { stdout } = await execAsync(cmd); + // Try to extract strict version number X.X.X + const match = stdout.match(/(\d+\.\d+\.\d+)/); + return match ? match[1] : stdout.trim().split("\n")[0]; // Fallback to first line + } catch { + return "Not Installed"; + } +} diff --git a/extensions/laravel-toolkit/tsconfig.json b/extensions/laravel-toolkit/tsconfig.json new file mode 100644 index 000000000000..d33dd46c4812 --- /dev/null +++ b/extensions/laravel-toolkit/tsconfig.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "include": ["src/**/*", "raycast-env.d.ts"], + "compilerOptions": { + "lib": ["ES2023"], + "module": "commonjs", + "target": "ES2023", + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "resolveJsonModule": true + } +}