├── .deepsource.toml ├── .github ├── ISSUE_TEMPLATE │ ├── BUG-REPORT.yml │ ├── FEATURE-REQUEST.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE │ └── template.md └── dependabot.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── extra └── banner.png ├── pyproject.toml ├── rrpm ├── __init__.py ├── __main__.py ├── config.py ├── ext │ ├── __init__.py │ └── loader.py ├── presets │ ├── __init__.py │ ├── base │ │ ├── base.py │ │ └── managers.py │ ├── js.py │ ├── py.py │ └── ts.py ├── rrpm.py └── utils.py └── tests ├── __init__.py └── test_rrpm.py /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG-REPORT.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | assignees: 6 | - pybash1 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: input 13 | id: contact 14 | attributes: 15 | label: Contact Details 16 | description: How can we get in touch with you if we need more info? 17 | placeholder: ex. email@example.com 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: what-happened 22 | attributes: 23 | label: What happened? 24 | description: Also tell us, what did you expect to happen? 25 | placeholder: Tell us what you see! 26 | value: "A bug happened!" 27 | validations: 28 | required: true 29 | - type: dropdown 30 | id: version 31 | attributes: 32 | label: Version 33 | description: What version of RRPM are you using? 34 | options: 35 | - 1.1.0 (Latest Stable) 36 | - 1.0.0 (Broken) 37 | validations: 38 | required: true 39 | - type: dropdown 40 | id: terminal 41 | attributes: 42 | label: What terminal did you see the problem on? 43 | multiple: true 44 | options: 45 | - Windows Terminal 46 | - Alacritty 47 | - Kitty 48 | - CMD.exe 49 | - PowerShell 50 | - ConEmu (Cmder) 51 | - Other 52 | - type: dropdown 53 | id: os 54 | attributes: 55 | label: What operating system did you see the problem on? 56 | multiple: true 57 | options: 58 | - Windows 10 or newer 59 | - Windows 7 or older 60 | - MacOS X or newer 61 | - MacOS 9 or older 62 | - ArchLinux Based 63 | - Debian Based 64 | - Other Linux 65 | - OpenBSD 66 | - Other 67 | - type: textarea 68 | id: traceback 69 | attributes: 70 | label: Relevant tracebacks/errors 71 | description: Please copy and paste any relevant tracebacks. This will be automatically formatted into code, so no need for backticks. 72 | render: python 73 | - type: checkboxes 74 | id: terms 75 | attributes: 76 | label: Code of Conduct 77 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) 78 | options: 79 | - label: I agree to follow this project's Code of Conduct 80 | required: true 81 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE]: ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discord Server 4 | url: https://discord.gg/FwsGkZAqcZ 5 | about: RRPM Discord Community 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/template.md: -------------------------------------------------------------------------------- 1 | ## The Issue that your PR fixes 2 | A brief description of the issue that your PR fixes. Link issues if needed. 3 | 4 | ## Proposed Solution 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | poetry.lock 3 | dist/ 4 | __pycache__/ 5 | .vscode/ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - hooks: 3 | - id: commitizen 4 | repo: https://github.com/commitizen-tools/commitizen 5 | rev: v2.27.1 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.0.0 (2022-09-12) 2 | 3 | ### Feat 4 | 5 | - updated the readme 6 | - **presets**: new vue(js and ts) preset 7 | - **presets**: new sveltekit(js only) preset 8 | - **presets**: new svelte(js and ts) presets 9 | - **presets**: new [astro](https://astro.build) preset 10 | - **extensions**: updated extension api to match preset and package manager api 11 | - **api**: migrated all presets to new api 12 | - **api**: migrated all js presets to new api 13 | - **api**: migrate the ts presets to new api 14 | - **api**: migrate old js presets to new api 15 | - **api**: added new base classes for package managers and presets 16 | 17 | ### Fix 18 | 19 | - fix critical bug 20 | - update main cli script to new api for all presets 21 | - **api**: improved checking for package manager installation in new api 22 | - added exception handling and proper error messages and improved detection 23 | - **import**: fix relative import paths 24 | 25 | ## v1.4.0 (2022-08-11) 26 | 27 | ### Feat 28 | 29 | - **commands**: added new command to migrate and formatted all files 30 | - **commands**: new list command and minor improvements 31 | - **commands**: add new flag to config command and renamed list command to tree 32 | - **commands**: updated get command and new remove command 33 | 34 | ## v1.3.0 (2022-06-17) 35 | 36 | ### Feat 37 | 38 | - **node**: ts support for node projects 39 | - **node**: now allows project generation of nodejs 40 | - **extensions**: include rrpmpkg as a dependency 41 | - Virtual Environment support 42 | - Pnpm create-react-app support 43 | 44 | ### Fix 45 | 46 | - Remote useless return statements 47 | - Update Badge 48 | - Fix bug where package manager was not found even when it existed 49 | 50 | ## v1.2.0 (2022-06-06) 51 | 52 | ## v1.1.0 (2022-06-04) 53 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | our Discord Server. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RRPM 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to RRPM and its packages, which are hosted in the [pybash1 user](https://github.com/pybash1) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ## Code of Conduct 8 | 9 | This project and everyone participating in it is governed by the [RRPM Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to pybash#3122 on our discord server. 10 | 11 | ## I don't want to read this whole thing I just have a question!!! 12 | 13 | > **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below. 14 | We have an official message board with a discord server and where the community chimes in with helpful advice if you have questions. 15 | 16 | * [GitHub Discussions, the official RRPM message board](https://github.com/pybash1/RRPM/discussions) 17 | * [Discord Server](https://discord.gg/FwsGkZAqcZ) 18 | 19 | ## How Can I Contribute? 20 | 21 | ### Reporting Bugs 22 | 23 | This section guides you through submitting a bug report for RRPM. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. 24 | 25 | Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out the required template, the information it asks for helps us resolve issues faster. 26 | 27 | > **Note**: If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. 28 | #### Before Submitting A Bug Report 29 | 30 | * **Check the [documentation](https://pybash.gitbook.io/rrpm)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using the latest version of RRPM. 31 | * **Check the [discussions](https://github.com/pybash1/RRPM/discussions)** for a list of common questions and problems. 32 | * **Perform a cursory search** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. 33 | 34 | #### How Do I Submit A (Good) Bug Report? 35 | 36 | Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on that repository and provide the following information by filling in the template. 37 | 38 | Explain the problem and include additional details to help maintainers reproduce the problem: 39 | 40 | * **Use a clear and descriptive title** for the issue to identify the problem. 41 | * **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started RRPM, e.g. which command exactly you used in the terminal, or how you started RRPM otherwise. When listing steps, **don't just say what you did, but explain how you did it**. For example, if you moved the cursor to the end of a line, explain if you used the mouse, or a keyboard shortcut or an RRPM command, and if so which one? 42 | * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/paste able snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 43 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 44 | * **Explain which behavior you expected to see instead and why.** 45 | * **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/RRPM/keybinding-resolver) shown**. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. 46 | 47 | Provide more context by answering these questions: 48 | 49 | * **Did the problem start happening recently** (e.g. after updating to a new version of RRPM) or was this always a problem? 50 | * If the problem started happening recently, **can you reproduce the problem in an older version of RRPM?** What's the most recent version in which the problem doesn't happen? You can download older versions of RRPM from [the releases page](https://github.com/RRPM/RRPM/releases). 51 | * **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. 52 | 53 | Include details about your configuration and environment: 54 | 55 | * **Which version of RRPM are you using?** You can get the exact version by running `RRPM -v` in your terminal 56 | * **What's the name and version of the OS you're using**? 57 | * **Are you running RRPM in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest? 58 | * **Are you using RRPM with multiple monitors?** If so, can you reproduce the problem when you use a single monitor? 59 | * **Which keyboard layout are you using?** Are you using a US layout or some other layout? 60 | 61 | ### Suggesting Enhancements 62 | 63 | This section guides you through submitting an enhancement suggestion for RRPM, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. 64 | 65 | Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](https://github.com/RRPM/.github/blob/master/.github/ISSUE_TEMPLATE/feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed. 66 | 67 | #### Before Submitting An Enhancement Suggestion 68 | 69 | * **Check the [documentation](https://pybash.gitbook.io/rrpm)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using the latest version of RRPM. 70 | * **Check if there's already an extension which provides that enhancement.** 71 | * **Perform a cursory search** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 72 | 73 | #### How Do I Submit A (Good) Enhancement Suggestion? 74 | 75 | Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue in the repository and provide the following information: 76 | 77 | * **Use a clear and descriptive title** for the issue to identify the suggestion. 78 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 79 | * **Provide specific examples to demonstrate the steps**. Include copy/paste able snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 80 | * **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 81 | * **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of RRPM which the suggestion is related to. You can use [this tool](https://gifcap.dev) to record GIFs. 82 | * **Explain why this enhancement would be useful** to most RRPM users and isn't something that can or should be implemented as a community extension. 83 | * **List some other tools or applications where this enhancement exists.** 84 | * **Specify which version of RRPM you're using.** You can get the exact version by running `RRPM -v` in your terminal 85 | * **Specify the name and version of the OS you're using.** 86 | 87 | ### Your First Code Contribution 88 | 89 | Unsure where to begin contributing to RRPM? You can start by looking through these `good-first-issue` and `help-wanted` issues: 90 | 91 | * [Good first issues][good-first-issue] - issues which should only require a few lines of code, and a test or two. 92 | * [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. 93 | 94 | Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. 95 | 96 | If you want to read about using RRPM or developing extensions for RRPM, the [RRPM Documentation](https://pybash.gitbook.io/rrpm) is free and available online. 97 | 98 | #### Local development 99 | 100 | RRPM can be developed locally. RRPM requires Oython 3.7 or newer and [Poetry](https://python-poetry.org). 101 | 102 | ### Pull Requests 103 | 104 | The process described here has several goals: 105 | 106 | - Maintain RRPM's quality 107 | - Fix problems that are important to users 108 | - Engage the community in working toward the best possible RRPM 109 | - Enable a sustainable system for RRPM's maintainers to review contributions 110 | 111 | Please follow these steps to have your contribution considered by the maintainers: 112 | 113 | 1. Follow all instructions in [the template](PULL_REQUEST_TEMPLATE.md) 114 | 2. Follow the [styleguide](#styleguide) 115 | 3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing
What if the status checks are failing?If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
116 | 117 | While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. 118 | 119 | ## Styleguide 120 | 121 | ### Git Commit Messages 122 | 123 | * Use the present tense ("Add feature" not "Added feature") 124 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 125 | * Limit the first line to 72 characters or fewer 126 | * Reference issues and pull requests liberally after the first line 127 | * Consider following [conventional commit messages](https://conventionalcommits.org) 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Py Bash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | RRPM logo 5 | 6 |

7 |
8 |

9 | 10 | GitHub license 11 | 12 | 13 | GitHub tag (latest SemVer) 14 | 15 | 16 | GitHub commit activity 17 | 18 | 19 | GitHub contributors 20 | 21 | 22 | DeepSource 23 | 24 |

25 |
26 | 27 | [**RRPM**](https://github.com/pybash1/rrpm) is the **all-in-one project and remote repository management tool**. A 28 | simple CLI tool that supports project generation for multiple languages, along with support for generating projects 29 | using different package managers and/or environments. This repository contains the **core CLI source code**. 30 | 31 | ## 🚀 Installation and Documentation 32 | 33 | `rrpm` can be installed from PyPI 34 | 35 | ```bash 36 | pip install rrpm 37 | ``` 38 | 39 | Complete documentation can be found on [GitBook](https://pybash.gitbook.io/rrpm) 40 | 41 | ## Usage 42 | 43 | ```bash 44 | 45 | Usage: python -m rrpm [OPTIONS] COMMAND [ARGS]... 46 | 47 | ┌─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 48 | │ --install-completion [bash|zsh|fish|powershell|pwsh] Install completion for the specified shell. [default: None] │ 49 | │ --show-completion [bash|zsh|fish|powershell|pwsh] Show completion for the specified shell, to copy it or customize the installation. [default: None] │ 50 | │ --help Show this message and exit. │ 51 | └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 52 | ┌─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────┐ 53 | │ config View current config file or regenerate config file │ 54 | │ create Generate a project from any of the presets and/or its variations │ 55 | │ get Clone a remote repository to directory specified in config │ 56 | │ list │ 57 | │ migrate Migrate and import all repositories from another directory │ 58 | │ remove Remove a cloned repository │ 59 | │ tree List all cloned repositories and generated projects │ 60 | └─────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 61 | 62 | ``` 63 | 64 | ## ❤️ Community and Contributions 65 | 66 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 67 | 68 | ## 📫 Have a question? Want to chat? Ran into a problem? 69 | 70 | We are happy to welcome you in our official [Discord server](https://discord.gg/FwsGkZAqcZ) or answer your questions via [GitHub Discussions](https://github.com/pybash1/rrpm/discussions)! 71 | 72 | ## 🤝 Found a bug? Missing a specific feature? 73 | 74 | Feel free to **file a new issue** with a respective title and description on the the [pybash1/rrpm](https://github.com/pybash1/rrpm/issues) repository. If you already found a solution to your problem, **we would love to review your pull request**! 75 | 76 | ## ✅ Requirements 77 | 78 | RRPM requires Python >=3.7 79 | 80 | ## Presets 81 | 82 | - [x] Python 83 | - [x] Pip 84 | - [x] Python Package 85 | - [x] FastAPI 86 | - [x] Flask 87 | - [x] Poetry 88 | - [x] Python Package 89 | - [x] FastAPI 90 | - [x] Flask 91 | - [x] Virtual Environments 92 | - [x] Python Package 93 | - [x] FastAPI 94 | - [x] Flask 95 | - [x] JavaScript 96 | - [x] NPM 97 | - [x] NodeJS 98 | - [x] ReactJS 99 | - [x] create-react-app 100 | - [x] Vite 101 | - [x] NextJS 102 | - [x] Astro 103 | - [x] Svelte 104 | - [x] SvelteKit 105 | - [x] Vue 106 | - [x] Yarn 107 | - [x] NodeJS 108 | - [x] ReactJS 109 | - [x] create-react-app 110 | - [x] Vite 111 | - [x] NextJS 112 | - [x] Astro 113 | - [x] Svelte 114 | - [x] SvelteKit 115 | - [x] Vue 116 | - [x] Pnpm 117 | - [x] NodeJS 118 | - [x] ReactJS 119 | - [x] create-react-app 120 | - [x] Vite 121 | - [x] NextJS 122 | - [x] TypeScript 123 | - [x] NPM 124 | - [x] NodeJS 125 | - [x] ReactJS 126 | - [x] create-react-app 127 | - [x] Vite 128 | - [x] NextJS 129 | - [x] Svelte 130 | - [x] Vue 131 | - [x] Yarn 132 | - [x] NodeJS 133 | - [x] ReactJS 134 | - [x] create-react-app 135 | - [x] Vite 136 | - [x] NextJS 137 | - [x] Svelte 138 | - [x] Vue 139 | - [x] Pnpm 140 | - [x] NodeJS 141 | - [x] ReactJS 142 | - [x] create-react-app 143 | - [x] Vite 144 | - [x] NextJS 145 | - [x] Svelte 146 | - [x] Vue 147 | 148 | ## 📘 License 149 | 150 | The RRPM tool is released under the under terms of the [MIT License](https://choosealicense.com/licenses/mit/). 151 | -------------------------------------------------------------------------------- /extra/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrpm-org/rrpm/df911ffc51e6f83b971f03ae6ac58196ba4ff47c/extra/banner.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "rrpm" 3 | version = "2.0.0" 4 | description = "A tool to manage all your projects easily!" 5 | readme = "README.md" 6 | homepage = "https://rrpm.vercel.app" 7 | documentation = "https://rrpm.vercel.app" 8 | repository = "https://github.com/rrpm-org/rrpm" 9 | keywords = ["repository", "git", "javascript", "projects"] 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | "Development Status :: 4 - Beta", 15 | "Environment :: Console", 16 | "Intended Audience :: Developers", 17 | "Topic :: Software Development :: Version Control", 18 | "Topic :: Utilities", 19 | "Natural Language :: English", 20 | "Topic :: Software Development :: Version Control :: Git", 21 | "Topic :: Terminals" 22 | ] 23 | authors = ["pybash1 "] 24 | license = "MIT" 25 | 26 | [tool.poetry.urls] 27 | "Bug Tracker" = "https://github.com/rrpm-org/rrpm/issues" 28 | 29 | [tool.poetry.dependencies] 30 | python = "^3.7" 31 | rich = "^12.4.4" 32 | typer = "^0.6.1" 33 | toml = "^0.10.2" 34 | questionary = "^1.10.0" 35 | rrpmpkg = "^1.0.2" 36 | 37 | [tool.poetry.dev-dependencies] 38 | pytest = "^7.1.2" 39 | black = "^22.3.0" 40 | 41 | [tool.poetry.scripts] 42 | rrpm = 'rrpm.rrpm:cli' 43 | 44 | [tool.commitizen] 45 | name = "cz_conventional_commits" 46 | version = "2.0.0" 47 | tag_format = "v$version" 48 | 49 | [build-system] 50 | requires = ["poetry-core>=1.0.0"] 51 | build-backend = "poetry.core.masonry.api" 52 | -------------------------------------------------------------------------------- /rrpm/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.0" 2 | -------------------------------------------------------------------------------- /rrpm/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .rrpm import cli 4 | 5 | if __name__ == "__main__": 6 | sys.exit(cli()) 7 | -------------------------------------------------------------------------------- /rrpm/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import toml 4 | 5 | WIN_DEFAULT_CONFIG = { 6 | "root": { 7 | "dir": "%USERPROFILE%\\Projects", 8 | "ext_dir": "%LOCALAPPDATA%\\rrpm\\extensions", 9 | }, 10 | "cli": { 11 | "display_output": False, 12 | "ignore_extension_load_error": False, 13 | }, 14 | "extensions": {"presets": [], "hooks": []}, 15 | } 16 | 17 | UNIX_DEFAULT_CONFIG = { 18 | "root": { 19 | "dir": "~/Projects", 20 | "exts_dir": "~/.config/rrpm/extensions", 21 | }, 22 | "cli": { 23 | "display_output": False, 24 | "ignore_extension_load_error": False, 25 | }, 26 | "extensions": {"presets": [], "hooks": []}, 27 | } 28 | 29 | 30 | class Config: 31 | def __init__(self): 32 | self.base_path = ( 33 | os.path.join(os.getenv("LOCALAPPDATA"), "rrpm") 34 | if platform.system().lower().startswith("win") 35 | else os.path.join(os.getenv("HOME"), ".config", "rrpm") 36 | ) 37 | if not os.path.exists(self.base_path): 38 | os.mkdir(self.base_path) 39 | if ( 40 | not os.path.exists(os.path.join(self.base_path, "config.toml")) 41 | or open(os.path.join(self.base_path, "config.toml")).read() == "" 42 | ): 43 | with open(os.path.join(self.base_path, "config.toml"), "w") as f: 44 | if platform.system().lower().startswith("win"): 45 | toml.dump(WIN_DEFAULT_CONFIG, f) 46 | if not os.path.exists(WIN_DEFAULT_CONFIG["root"]["ext_dir"]): 47 | os.mkdir( 48 | os.path.expandvars( 49 | os.path.expanduser( 50 | WIN_DEFAULT_CONFIG["root"]["ext_dir"] 51 | ) 52 | ) 53 | ) 54 | else: 55 | toml.dump(UNIX_DEFAULT_CONFIG, f) 56 | if not os.path.exists(UNIX_DEFAULT_CONFIG["root"]["ext_dir"]): 57 | os.mkdir( 58 | os.path.expandvars( 59 | os.path.expanduser( 60 | UNIX_DEFAULT_CONFIG["root"]["ext_dir"] 61 | ) 62 | ) 63 | ) 64 | 65 | def regenerate(self): 66 | if not os.path.exists(self.base_path): 67 | os.mkdir(self.base_path) 68 | with open(os.path.join(self.base_path, "config.toml"), "w") as f: 69 | if platform.system().lower().startswith("win"): 70 | toml.dump(WIN_DEFAULT_CONFIG, f) 71 | else: 72 | toml.dump(UNIX_DEFAULT_CONFIG, f) 73 | 74 | def generate(self, new_config): 75 | with open(os.path.join(self.base_path, "config.toml"), "w") as f: 76 | toml.dump(new_config, f) 77 | 78 | @property 79 | def config_path(self): 80 | return os.path.join(self.base_path, "config.toml") 81 | 82 | @property 83 | def config(self): 84 | with open(os.path.join(self.base_path, "config.toml")) as f: 85 | return toml.load(f) 86 | -------------------------------------------------------------------------------- /rrpm/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrpm-org/rrpm/df911ffc51e6f83b971f03ae6ac58196ba4ff47c/rrpm/ext/__init__.py -------------------------------------------------------------------------------- /rrpm/ext/loader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import importlib 3 | 4 | 5 | def load_extension(path, name): 6 | sys.path.append(path) 7 | try: 8 | return importlib.import_module(name, package=path) 9 | except ImportError: 10 | return None 11 | -------------------------------------------------------------------------------- /rrpm/presets/__init__.py: -------------------------------------------------------------------------------- 1 | from ..utils import get_home_dir 2 | from ..config import Config 3 | -------------------------------------------------------------------------------- /rrpm/presets/base/base.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from rich.console import Console 3 | 4 | 5 | class PackageManager: 6 | def __init__(self): 7 | pass 8 | 9 | @classmethod 10 | def check(cls): 11 | if not shutil.which(cls.cmd or cls.name): 12 | return False 13 | return True 14 | 15 | @classmethod 16 | def generate(cls): 17 | pass 18 | 19 | 20 | class Preset: 21 | package_managers = [] 22 | 23 | def __init__(self, repo: str, name: str): 24 | self.repo = repo 25 | self.name = name 26 | 27 | def generate(self, pkg: PackageManager): 28 | pass 29 | 30 | @staticmethod 31 | def exception_handler(): 32 | Console().print_exception() 33 | -------------------------------------------------------------------------------- /rrpm/presets/base/managers.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import os 3 | import shutil 4 | import subprocess 5 | import sys 6 | import time 7 | import questionary 8 | from .base import PackageManager 9 | from rich.console import Console 10 | from rich.progress import Progress 11 | from rrpm.config import Config 12 | from rrpm.utils import get_home_dir 13 | 14 | presets = ["react", "next", "vanilla", "astro", "svelte", "sveltekit", "vue"] 15 | presets_py = ["vanilla", "flask", "fastapi"] 16 | console = Console() 17 | config = Config() 18 | home = get_home_dir() 19 | 20 | 21 | class NPM(PackageManager): 22 | name = "NPM" 23 | cmd = "npm" 24 | 25 | @classmethod 26 | def generate(cls, repo: str, name: str, preset: str, ts: bool): 27 | if not cls.check(): 28 | console.print("[red]npm is not installed![/]") 29 | return 30 | 31 | if preset not in presets: 32 | console.print(f"[red]Unknown preset: '{preset}'[/]") 33 | return 34 | 35 | if ts: 36 | if preset == "react": 37 | bundler = questionary.select( 38 | "Bundler", choices=["Vite", "create-react-app"] 39 | ).ask() 40 | if os.path.exists(os.path.join(home, repo, name)): 41 | console.print("[red]Project already exists![/]") 42 | sys.exit(1) 43 | if bundler == "Vite": 44 | os.chdir(os.path.join(get_home_dir(), repo)) 45 | console.print( 46 | "[green]Creating project with Vite, TypeScript and NPM[/]" 47 | ) 48 | if config.config["cli"]["display_output"]: 49 | subprocess.run( 50 | [ 51 | "npm", 52 | "create", 53 | "vite@latest", 54 | name, 55 | "--", 56 | "--template", 57 | "react-ts", 58 | ], 59 | shell=True, 60 | ) 61 | else: 62 | subprocess.run( 63 | [ 64 | "npm", 65 | "create", 66 | "vite@latest", 67 | name, 68 | "--", 69 | "--template", 70 | "react-ts", 71 | ], 72 | shell=True, 73 | capture_output=True, 74 | ) 75 | else: 76 | os.chdir(os.path.join(home, repo)) 77 | console.print( 78 | "[green]Creating project with create-react-app, TypeScript and NPM[/]" 79 | ) 80 | if config.config["cli"]["display_output"]: 81 | subprocess.run( 82 | [ 83 | "npx", 84 | "create-react-app@latest", 85 | name, 86 | "--template", 87 | "typescript", 88 | ], 89 | shell=True, 90 | ) 91 | else: 92 | subprocess.run( 93 | [ 94 | "npx", 95 | "create-react-app@latest", 96 | name, 97 | "--template", 98 | "typescript", 99 | ], 100 | shell=True, 101 | capture_output=True, 102 | ) 103 | elif preset == "next": 104 | if os.path.exists(os.path.join(home, repo, name)): 105 | console.print("[red]Project already exists![/]") 106 | return 107 | os.mkdir(os.path.join(home, repo, name)) 108 | console.print( 109 | "[green]Creating project with create-next-app, TypeScript and NPM[/]" 110 | ) 111 | if config.config["cli"]["display_output"]: 112 | subprocess.run( 113 | ["npx", "create-next-app@latest", name, "--ts"], 114 | shell=True, 115 | ) 116 | else: 117 | subprocess.run( 118 | ["npx", "create-next-app@latest", name, "--ts"], 119 | shell=True, 120 | capture_output=True, 121 | ) 122 | return 123 | elif preset == "vanilla": 124 | if os.path.exists(os.path.join(home, repo, name)): 125 | console.print("[red]Project already exists![/]") 126 | return 127 | os.mkdir(os.path.join(home, repo, name)) 128 | os.chdir(os.path.join(home, repo, name)) 129 | console.print("[green]Creating project with NPM, and JavaScript[/]") 130 | if config.config["cli"]["display_output"]: 131 | subprocess.run( 132 | ["npm", "init"], 133 | shell=True, 134 | ) 135 | else: 136 | subprocess.run( 137 | ["npm", "init"], 138 | shell=True, 139 | capture_output=True, 140 | ) 141 | ts = questionary.confirm("Install TypeScript Globally?") 142 | ts_node = questionary.confirm("Install ts-node?") 143 | if config.config["cli"]["display_output"]: 144 | if ts: 145 | subprocess.run( 146 | ["npm", "install", "--global", "typescript"], shell=True 147 | ) 148 | else: 149 | subprocess.run( 150 | ["npm", "install", "--save-dev", "typescript"], shell=True 151 | ) 152 | if ts_node: 153 | subprocess.run( 154 | ["npm", "install", "--global", "ts-node"], shell=True 155 | ) 156 | else: 157 | if ts: 158 | subprocess.run( 159 | ["npm", "install", "--global", "typescript"], 160 | shell=True, 161 | capture_output=True, 162 | ) 163 | else: 164 | subprocess.run( 165 | ["npm", "install", "--save-dev", "typescript"], 166 | shell=True, 167 | capture_output=True, 168 | ) 169 | if ts_node: 170 | subprocess.run( 171 | ["npm", "install", "--global", "ts-node"], shell=True 172 | ) 173 | return 174 | elif preset == "astro": 175 | console.log("[red]Astro with TypeScript is not availble![/]") 176 | elif preset == "svelte": 177 | if os.path.exists(os.path.join(home, repo, name)): 178 | console.print("[red]Project already exists![/]") 179 | return 180 | os.mkdir(os.path.join(home, repo, name)) 181 | console.print( 182 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 183 | ) 184 | if config.config["cli"]["display_output"]: 185 | subprocess.run( 186 | [ 187 | "npm", 188 | "create", 189 | "vite@latest", 190 | name, 191 | "--", 192 | "--temaplte", 193 | "svelte-ts", 194 | ], 195 | shell=True, 196 | ) 197 | else: 198 | subprocess.run( 199 | [ 200 | "npm", 201 | "create", 202 | "vite@latest", 203 | name, 204 | "--", 205 | "--temaplte", 206 | "svelte-ts", 207 | ], 208 | shell=True, 209 | capture_output=True, 210 | ) 211 | return 212 | elif preset == "sveltekit": 213 | console.log("[red]SvelteKit with TypeScript is not availble![/]") 214 | elif preset == "vue": 215 | if os.path.exists(os.path.join(home, repo, name)): 216 | console.print("[red]Project already exists![/]") 217 | return 218 | os.mkdir(os.path.join(home, repo, name)) 219 | console.print( 220 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 221 | ) 222 | if config.config["cli"]["display_output"]: 223 | subprocess.run( 224 | [ 225 | "npm", 226 | "create", 227 | "vite@latest", 228 | name, 229 | "--", 230 | "--template", 231 | "vue-ts", 232 | ], 233 | shell=True, 234 | ) 235 | else: 236 | subprocess.run( 237 | [ 238 | "npm", 239 | "create", 240 | "vite@latest", 241 | name, 242 | "--", 243 | "--template", 244 | "vue-ts", 245 | ], 246 | shell=True, 247 | capture_output=True, 248 | ) 249 | return 250 | else: 251 | if preset == "react": 252 | bundler = questionary.select( 253 | "Bundler", choices=["Vite", "create-react-app"] 254 | ).ask() 255 | if os.path.exists(os.path.join(home, repo, name)): 256 | console.print("[red]Project already exists![/]") 257 | sys.exit(1) 258 | if bundler == "Vite": 259 | os.chdir(os.path.join(get_home_dir(), repo)) 260 | console.print( 261 | "[green]Creating project with Vite, JavaScript and NPM[/]" 262 | ) 263 | if config.config["cli"]["display_output"]: 264 | subprocess.run( 265 | [ 266 | "npm", 267 | "create", 268 | "vite@latest", 269 | name, 270 | "--", 271 | "--template", 272 | "react", 273 | ], 274 | shell=True, 275 | ) 276 | else: 277 | subprocess.run( 278 | [ 279 | "npm", 280 | "create", 281 | "vite@latest", 282 | name, 283 | "--", 284 | "--template", 285 | "react", 286 | ], 287 | shell=True, 288 | capture_output=True, 289 | ) 290 | else: 291 | os.chdir(os.path.join(home, repo)) 292 | console.print( 293 | "[green]Creating project with create-react-app, JavaScript and NPM[/]" 294 | ) 295 | if config.config["cli"]["display_output"]: 296 | subprocess.run( 297 | ["npx", "create-react-app@latest", name], 298 | shell=True, 299 | ) 300 | else: 301 | subprocess.run( 302 | ["npx", "create-react-app@latest", name], 303 | shell=True, 304 | capture_output=True, 305 | ) 306 | elif preset == "next": 307 | if os.path.exists(os.path.join(home, repo, name)): 308 | console.print("[red]Project already exists![/]") 309 | return 310 | os.mkdir(os.path.join(home, repo, name)) 311 | console.print( 312 | "[green]Creating project with create-next-app, JavaScript and NPM[/]" 313 | ) 314 | if config.config["cli"]["display_output"]: 315 | subprocess.run( 316 | ["npx", "create-next-app@latest", name], 317 | shell=True, 318 | ) 319 | else: 320 | subprocess.run( 321 | ["npx", "create-next-app@latest", name], 322 | shell=True, 323 | capture_output=True, 324 | ) 325 | return 326 | elif preset == "vanilla": 327 | if os.path.exists(os.path.join(home, repo, name)): 328 | console.print("[red]Project already exists![/]") 329 | return 330 | os.mkdir(os.path.join(home, repo, name)) 331 | os.chdir(os.path.join(home, repo, name)) 332 | console.print("[green]Creating project with NPM, and JavaScript[/]") 333 | if config.config["cli"]["display_output"]: 334 | subprocess.run( 335 | ["npm", "init"], 336 | shell=True, 337 | ) 338 | else: 339 | subprocess.run( 340 | ["npm", "init"], 341 | shell=True, 342 | capture_output=True, 343 | ) 344 | return 345 | elif preset == "astro": 346 | if os.path.exists(os.path.join(home, repo, name)): 347 | console.print("[red]Project already exists![/]") 348 | return 349 | os.mkdir(os.path.join(home, repo, name)) 350 | console.print( 351 | "[green]Creating project with Astro, JavaScript and NPM[/]" 352 | ) 353 | if config.config["cli"]["display_output"]: 354 | subprocess.run( 355 | ["npm", "create", "astro@latest", name], 356 | shell=True, 357 | ) 358 | else: 359 | subprocess.run( 360 | ["npm", "create", "astro@latest", name], 361 | shell=True, 362 | capture_output=True, 363 | ) 364 | return 365 | elif preset == "svelte": 366 | if os.path.exists(os.path.join(home, repo, name)): 367 | console.print("[red]Project already exists![/]") 368 | return 369 | os.mkdir(os.path.join(home, repo, name)) 370 | console.print( 371 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 372 | ) 373 | if config.config["cli"]["display_output"]: 374 | subprocess.run( 375 | [ 376 | "npm", 377 | "create", 378 | "vite@latest", 379 | name, 380 | "--", 381 | "--temaplte", 382 | "svelte", 383 | ], 384 | shell=True, 385 | ) 386 | else: 387 | subprocess.run( 388 | [ 389 | "npm", 390 | "create", 391 | "vite@latest", 392 | name, 393 | "--", 394 | "--temaplte", 395 | "svelte", 396 | ], 397 | shell=True, 398 | capture_output=True, 399 | ) 400 | return 401 | elif preset == "sveltekit": 402 | if os.path.exists(os.path.join(home, repo, name)): 403 | console.print("[red]Project already exists![/]") 404 | return 405 | os.mkdir(os.path.join(home, repo, name)) 406 | console.print( 407 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 408 | ) 409 | if config.config["cli"]["display_output"]: 410 | subprocess.run( 411 | ["npm", "create", "svelte@latest", name], 412 | shell=True, 413 | ) 414 | else: 415 | subprocess.run( 416 | ["npm", "create", "svelte@latest", name], 417 | shell=True, 418 | capture_output=True, 419 | ) 420 | return 421 | elif preset == "vue": 422 | if os.path.exists(os.path.join(home, repo, name)): 423 | console.print("[red]Project already exists![/]") 424 | return 425 | os.mkdir(os.path.join(home, repo, name)) 426 | console.print( 427 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 428 | ) 429 | if config.config["cli"]["display_output"]: 430 | subprocess.run( 431 | [ 432 | "npm", 433 | "create", 434 | "vite@latest", 435 | name, 436 | "--", 437 | "--template", 438 | "vue", 439 | ], 440 | shell=True, 441 | ) 442 | else: 443 | subprocess.run( 444 | [ 445 | "npm", 446 | "create", 447 | "vite@latest", 448 | name, 449 | "--", 450 | "--template", 451 | "vue", 452 | ], 453 | shell=True, 454 | capture_output=True, 455 | ) 456 | return 457 | 458 | 459 | class Yarn(PackageManager): 460 | name = "Yarn" 461 | cmd = "yarn" 462 | 463 | @classmethod 464 | def generate(cls, repo: str, name: str, preset: str, ts: bool): 465 | if not cls.check(): 466 | console.print("[red]yarn is not installed![/]") 467 | return 468 | 469 | if preset not in presets: 470 | console.print(f"[red]Unknown preset: '{preset}'[/]") 471 | return 472 | 473 | if ts: 474 | if preset == "react": 475 | bundler = questionary.select( 476 | "Bundler", choices=["Vite", "create-react-app"] 477 | ).ask() 478 | if os.path.exists(os.path.join(home, repo, name)): 479 | console.print("[red]Project already exists![/]") 480 | sys.exit(1) 481 | if bundler == "Vite": 482 | os.chdir(os.path.join(home, repo)) 483 | console.print( 484 | "[green]Creating project with Vite, TypeScript and Yarn[/]" 485 | ) 486 | if config.config["cli"]["display_output"]: 487 | subprocess.run( 488 | ["yarn", "create", "vite", name, "--template", "react-ts"], 489 | shell=True, 490 | ) 491 | else: 492 | subprocess.run( 493 | ["yarn", "create", "vite", name, "--template", "react-ts"], 494 | shell=True, 495 | capture_output=True, 496 | ) 497 | else: 498 | os.chdir(os.path.join(home, repo)) 499 | console.print( 500 | "[green]Creating project with create-react-app, TypeScript and Yarn[/]" 501 | ) 502 | if config.config["cli"]["display_output"]: 503 | subprocess.run( 504 | [ 505 | "yarn", 506 | "create", 507 | "react-app", 508 | name, 509 | "--template", 510 | "typescript", 511 | ], 512 | shell=True, 513 | ) 514 | else: 515 | subprocess.run( 516 | [ 517 | "yarn", 518 | "create", 519 | "react-app", 520 | name, 521 | "--template", 522 | "typescript", 523 | ], 524 | shell=True, 525 | capture_output=True, 526 | ) 527 | elif preset == "next": 528 | if os.path.exists(os.path.join(home, repo, name)): 529 | console.print("[red]Project already exists![/]") 530 | return 531 | os.mkdir(os.path.join(home, repo, name)) 532 | console.print( 533 | "[green]Creating project with create-next-app, TypeScript and Yarn[/]" 534 | ) 535 | if config.config["cli"]["display_output"]: 536 | subprocess.run( 537 | ["yarn", "create", "next-app", name, "--typescript"], 538 | shell=True, 539 | ) 540 | else: 541 | subprocess.run( 542 | ["yarn", "create", "next-app", name, "--typescript"], 543 | shell=True, 544 | capture_output=True, 545 | ) 546 | return 547 | elif preset == "vanilla": 548 | if os.path.exists(os.path.join(home, repo, name)): 549 | console.print("[red]Project already exists![/]") 550 | return 551 | os.mkdir(os.path.join(home, repo, name)) 552 | os.chdir(os.path.join(home, repo, name)) 553 | console.print("[green]Creating project with NPM, and JavaScript[/]") 554 | if config.config["cli"]["display_output"]: 555 | subprocess.run( 556 | ["yarn", "init"], 557 | shell=True, 558 | ) 559 | else: 560 | subprocess.run( 561 | ["yarn", "init"], 562 | shell=True, 563 | capture_output=True, 564 | ) 565 | ts = questionary.confirm("Install TypeScript Globally?") 566 | ts_node = questionary.confirm("Install ts-node?") 567 | if config.config["cli"]["display_output"]: 568 | if ts: 569 | subprocess.run( 570 | ["yarn", "global", "add", "typescript"], shell=True 571 | ) 572 | else: 573 | subprocess.run( 574 | ["yarn", "add", "--dev", "typescript"], shell=True 575 | ) 576 | if ts_node: 577 | subprocess.run(["yarn", "global", "add", "ts-node"], shell=True) 578 | else: 579 | if ts: 580 | subprocess.run( 581 | ["yarn", "global", "add", "typescript"], 582 | shell=True, 583 | capture_output=True, 584 | ) 585 | else: 586 | subprocess.run( 587 | ["yarn", "add", "--dev", "typescript"], 588 | shell=True, 589 | capture_output=True, 590 | ) 591 | if ts_node: 592 | subprocess.run(["yarn", "global", "add", "ts-node"], shell=True) 593 | return 594 | elif preset == "astro": 595 | console.log("[red]Astro with TypeScript is not availble![/]") 596 | elif preset == "svelte": 597 | if os.path.exists(os.path.join(home, repo, name)): 598 | console.print("[red]Project already exists![/]") 599 | return 600 | os.mkdir(os.path.join(home, repo, name)) 601 | console.print( 602 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 603 | ) 604 | if config.config["cli"]["display_output"]: 605 | subprocess.run( 606 | [ 607 | "yarn", 608 | "create", 609 | "vite", 610 | name, 611 | "--", 612 | "--temaplte", 613 | "svelte-ts", 614 | ], 615 | shell=True, 616 | ) 617 | else: 618 | subprocess.run( 619 | [ 620 | "yarn", 621 | "create", 622 | "vite", 623 | name, 624 | "--", 625 | "--temaplte", 626 | "svelte-ts", 627 | ], 628 | shell=True, 629 | capture_output=True, 630 | ) 631 | return 632 | elif preset == "sveltekit": 633 | console.log("[red]SvelteKit with TypeScript is not availble![/]") 634 | elif preset == "vue": 635 | if os.path.exists(os.path.join(home, repo, name)): 636 | console.print("[red]Project already exists![/]") 637 | return 638 | os.mkdir(os.path.join(home, repo, name)) 639 | console.print( 640 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 641 | ) 642 | if config.config["cli"]["display_output"]: 643 | subprocess.run( 644 | ["yarn", "create", "vite", name, "--", "--template", "vue-ts"], 645 | shell=True, 646 | ) 647 | else: 648 | subprocess.run( 649 | ["yarn", "create", "vite", name, "--", "--template", "vue-ts"], 650 | shell=True, 651 | capture_output=True, 652 | ) 653 | return 654 | else: 655 | if preset == "react": 656 | bundler = questionary.select( 657 | "Bundler", choices=["Vite", "create-react-app"] 658 | ).ask() 659 | if os.path.exists(os.path.join(home, repo, name)): 660 | console.print("[red]Project already exists![/]") 661 | sys.exit(1) 662 | if bundler == "Vite": 663 | os.chdir(os.path.join(home, repo)) 664 | console.print( 665 | "[green]Creating project with Vite, JavaScript and Yarn[/]" 666 | ) 667 | if config.config["cli"]["display_output"]: 668 | subprocess.run( 669 | ["yarn", "create", "vite", name, "--template", "react"], 670 | shell=True, 671 | ) 672 | else: 673 | subprocess.run( 674 | ["yarn", "create", "vite", name, "--template", "react"], 675 | shell=True, 676 | capture_output=True, 677 | ) 678 | else: 679 | os.chdir(os.path.join(home, repo)) 680 | console.print( 681 | "[green]Creating project with create-react-app, JavaScript and Yarn[/]" 682 | ) 683 | if config.config["cli"]["display_output"]: 684 | subprocess.run( 685 | ["yarn", "create", "react-app", name], 686 | shell=True, 687 | ) 688 | else: 689 | subprocess.run( 690 | ["yarn", "create", "react-app", name], 691 | shell=True, 692 | capture_output=True, 693 | ) 694 | elif preset == "next": 695 | if os.path.exists(os.path.join(home, repo, name)): 696 | console.print("[red]Project already exists![/]") 697 | return 698 | os.mkdir(os.path.join(home, repo, name)) 699 | console.print( 700 | "[green]Creating project with create-next-app, JavaScript and Yarn[/]" 701 | ) 702 | if config.config["cli"]["display_output"]: 703 | subprocess.run( 704 | ["yarn", "create", "next-app", name], 705 | shell=True, 706 | ) 707 | else: 708 | subprocess.run( 709 | ["yarn", "create", "next-app", name], 710 | shell=True, 711 | capture_output=True, 712 | ) 713 | return 714 | elif preset == "vanilla": 715 | if os.path.exists(os.path.join(home, repo, name)): 716 | console.print("[red]Project already exists![/]") 717 | return 718 | os.mkdir(os.path.join(home, repo, name)) 719 | os.chdir(os.path.join(home, repo, name)) 720 | console.print("[green]Creating project with Yarn, and JavaScript[/]") 721 | if config.config["cli"]["display_output"]: 722 | subprocess.run( 723 | ["yarn", "init"], 724 | shell=True, 725 | ) 726 | else: 727 | subprocess.run( 728 | ["yarn", "init"], 729 | shell=True, 730 | capture_output=True, 731 | ) 732 | return 733 | elif preset == "astro": 734 | if os.path.exists(os.path.join(home, repo, name)): 735 | console.print("[red]Project already exists![/]") 736 | return 737 | os.mkdir(os.path.join(home, repo, name)) 738 | console.print( 739 | "[green]Creating project with Astro, JavaScript and Yarn[/]" 740 | ) 741 | if config.config["cli"]["display_output"]: 742 | subprocess.run( 743 | ["yarn", "create", "astro", name], 744 | shell=True, 745 | ) 746 | else: 747 | subprocess.run( 748 | ["yarn", "create", "astro", name], 749 | shell=True, 750 | capture_output=True, 751 | ) 752 | return 753 | elif preset == "svelte": 754 | if os.path.exists(os.path.join(home, repo, name)): 755 | console.print("[red]Project already exists![/]") 756 | return 757 | os.mkdir(os.path.join(home, repo, name)) 758 | console.print( 759 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 760 | ) 761 | if config.config["cli"]["display_output"]: 762 | subprocess.run( 763 | ["yarn", "create", "vite", name, "--", "--temaplte", "svelte"], 764 | shell=True, 765 | ) 766 | else: 767 | subprocess.run( 768 | ["yarn", "create", "vite", name, "--", "--temaplte", "svelte"], 769 | shell=True, 770 | capture_output=True, 771 | ) 772 | return 773 | elif preset == "sveltekit": 774 | if os.path.exists(os.path.join(home, repo, name)): 775 | console.print("[red]Project already exists![/]") 776 | return 777 | os.mkdir(os.path.join(home, repo, name)) 778 | console.print( 779 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 780 | ) 781 | if config.config["cli"]["display_output"]: 782 | subprocess.run( 783 | ["yarn", "create", "svelte", name], 784 | shell=True, 785 | ) 786 | else: 787 | subprocess.run( 788 | ["yarn", "create", "svelte", name], 789 | shell=True, 790 | capture_output=True, 791 | ) 792 | return 793 | elif preset == "vue": 794 | if os.path.exists(os.path.join(home, repo, name)): 795 | console.print("[red]Project already exists![/]") 796 | return 797 | os.mkdir(os.path.join(home, repo, name)) 798 | console.print( 799 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 800 | ) 801 | if config.config["cli"]["display_output"]: 802 | subprocess.run( 803 | ["yarn", "create", "vite", name, "--", "--template", "vue"], 804 | shell=True, 805 | ) 806 | else: 807 | subprocess.run( 808 | ["yarn", "create", "vite", name, "--", "--template", "vue"], 809 | shell=True, 810 | capture_output=True, 811 | ) 812 | return 813 | 814 | 815 | class PNPM(PackageManager): 816 | name = "PNPM" 817 | cmd = "pnpm" 818 | 819 | @classmethod 820 | def generate(cls, repo: str, name: str, preset: str, ts: bool): 821 | if not cls.check(): 822 | console.print("[red]pnpm is not installed![/]") 823 | return 824 | 825 | if preset not in presets: 826 | console.print(f"[red]Unknown preset: '{preset}'[/]") 827 | return 828 | 829 | if ts: 830 | if preset == "react": 831 | bundler = questionary.select("Bundler", choices=["Vite"]).ask() 832 | if os.path.exists(os.path.join(home, repo, name)): 833 | console.print("[red]Project already exists![/]") 834 | sys.exit(1) 835 | if bundler == "Vite": 836 | os.chdir(os.path.join(home, repo)) 837 | console.print( 838 | "[green]Creating project with Vite, TypeScript and Pnpm[/]" 839 | ) 840 | if config.config["cli"]["display_output"]: 841 | subprocess.run( 842 | [ 843 | "pnpm", 844 | "create", 845 | "vite", 846 | name, 847 | "--", 848 | "--template", 849 | "react-ts", 850 | ], 851 | shell=True, 852 | ) 853 | else: 854 | subprocess.run( 855 | [ 856 | "pnpm", 857 | "create", 858 | "vite", 859 | name, 860 | "--", 861 | "--template", 862 | "react-ts", 863 | ], 864 | shell=True, 865 | capture_output=True, 866 | ) 867 | else: 868 | if shutil.which("pnpx") is None: 869 | console.print("[red]Pnpx is not installed![/]") 870 | os.chdir(os.path.join(home, repo)) 871 | console.print( 872 | "[green]Creating project with create-react-app, JavaScript and Pnpm[/]" 873 | ) 874 | if config.config["cli"]["display_output"]: 875 | subprocess.run( 876 | ["pnpx", "create-react-app", name], 877 | shell=True, 878 | ) 879 | else: 880 | subprocess.run( 881 | ["pnpx", "create-react-app", name], 882 | shell=True, 883 | capture_output=True, 884 | ) 885 | elif preset == "next": 886 | if os.path.exists(os.path.join(home, repo, name)): 887 | console.print("[red]Project already exists![/]") 888 | return 889 | os.mkdir(os.path.join(home, repo, name)) 890 | console.print( 891 | "[green]Creating project with create-next-app, TypeScript and Pnpm[/]" 892 | ) 893 | if config.config["cli"]["display_output"]: 894 | subprocess.run( 895 | ["pnpm", "create", "next-app", name, "--", "--ts"], 896 | shell=True, 897 | ) 898 | else: 899 | subprocess.run( 900 | ["pnpm", "create", "next-app", name, "--", "--ts"], 901 | shell=True, 902 | capture_output=True, 903 | ) 904 | return 905 | elif preset == "vanilla": 906 | if os.path.exists(os.path.join(home, repo, name)): 907 | console.print("[red]Project already exists![/]") 908 | return 909 | os.mkdir(os.path.join(home, repo, name)) 910 | os.chdir(os.path.join(home, repo, name)) 911 | console.print("[green]Creating project with NPM, and JavaScript[/]") 912 | if config.config["cli"]["display_output"]: 913 | subprocess.run( 914 | ["pnpm", "init"], 915 | shell=True, 916 | ) 917 | else: 918 | subprocess.run( 919 | ["pnpm", "init"], 920 | shell=True, 921 | capture_output=True, 922 | ) 923 | ts = questionary.confirm("Install TypeScript Globally?") 924 | ts_node = questionary.confirm("Install ts-node?") 925 | if config.config["cli"]["display_output"]: 926 | if ts: 927 | subprocess.run( 928 | ["pnpm", "add", "--global", "typescript"], shell=True 929 | ) 930 | else: 931 | subprocess.run( 932 | ["pnpm", "add", "--save-dev", "typescript"], shell=True 933 | ) 934 | if ts_node: 935 | subprocess.run( 936 | ["pnpm", "add", "--global", "ts-node"], shell=True 937 | ) 938 | else: 939 | if ts: 940 | subprocess.run( 941 | ["pnpm", "add", "--global", "typescript"], 942 | shell=True, 943 | capture_output=True, 944 | ) 945 | else: 946 | subprocess.run( 947 | ["pnpm", "add", "--save-dev", "typescript"], 948 | shell=True, 949 | capture_output=True, 950 | ) 951 | if ts_node: 952 | subprocess.run( 953 | ["pnpm", "add", "--global", "ts-node"], shell=True 954 | ) 955 | return 956 | elif preset == "astro": 957 | console.log("[red]Astro with TypeScript is not availble![/]") 958 | elif preset == "svelte": 959 | if os.path.exists(os.path.join(home, repo, name)): 960 | console.print("[red]Project already exists![/]") 961 | return 962 | os.mkdir(os.path.join(home, repo, name)) 963 | console.print( 964 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 965 | ) 966 | if config.config["cli"]["display_output"]: 967 | subprocess.run( 968 | [ 969 | "pnpm", 970 | "create", 971 | "vite", 972 | name, 973 | "--", 974 | "--temaplte", 975 | "svelte-ts", 976 | ], 977 | shell=True, 978 | ) 979 | else: 980 | subprocess.run( 981 | [ 982 | "pnpm", 983 | "create", 984 | "vite", 985 | name, 986 | "--", 987 | "--temaplte", 988 | "svelte-ts", 989 | ], 990 | shell=True, 991 | capture_output=True, 992 | ) 993 | return 994 | elif preset == "sveltekit": 995 | console.log("[red]SvelteKit with TypeScript is not availble![/]") 996 | elif preset == "vue": 997 | if os.path.exists(os.path.join(home, repo, name)): 998 | console.print("[red]Project already exists![/]") 999 | return 1000 | os.mkdir(os.path.join(home, repo, name)) 1001 | console.print( 1002 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 1003 | ) 1004 | if config.config["cli"]["display_output"]: 1005 | subprocess.run( 1006 | ["pnpm", "create", "vite", name, "--", "--template", "vue-ts"], 1007 | shell=True, 1008 | ) 1009 | else: 1010 | subprocess.run( 1011 | ["pnpm", "create", "vite", name, "--", "--template", "vue-ts"], 1012 | shell=True, 1013 | capture_output=True, 1014 | ) 1015 | return 1016 | else: 1017 | if preset == "react": 1018 | bundler = questionary.select( 1019 | "Bundler", choices=["Vite", "create-react-app"] 1020 | ).ask() 1021 | if os.path.exists(os.path.join(home, repo, name)): 1022 | console.print("[red]Project already exists![/]") 1023 | sys.exit(1) 1024 | if bundler == "Vite": 1025 | os.chdir(os.path.join(home, repo)) 1026 | console.print( 1027 | "[green]Creating project with Vite, JavaScript and Pnpm[/]" 1028 | ) 1029 | if config.config["cli"]["display_output"]: 1030 | subprocess.run( 1031 | [ 1032 | "pnpm", 1033 | "create", 1034 | "vite", 1035 | name, 1036 | "--", 1037 | "--template", 1038 | "react", 1039 | ], 1040 | shell=True, 1041 | ) 1042 | else: 1043 | subprocess.run( 1044 | [ 1045 | "pnpm", 1046 | "create", 1047 | "vite", 1048 | name, 1049 | "--", 1050 | "--template", 1051 | "react", 1052 | ], 1053 | shell=True, 1054 | capture_output=True, 1055 | ) 1056 | else: 1057 | if shutil.which("pnpx") is None: 1058 | console.print("[red]Pnpx is not installed![/]") 1059 | os.chdir(os.path.join(home, repo)) 1060 | console.print( 1061 | "[green]Creating project with create-react-app, JavaScript and Pnpm[/]" 1062 | ) 1063 | if config.config["cli"]["display_output"]: 1064 | subprocess.run( 1065 | ["pnpx", "create-react-app", name], 1066 | shell=True, 1067 | ) 1068 | else: 1069 | subprocess.run( 1070 | ["pnpx", "create-react-app", name], 1071 | shell=True, 1072 | capture_output=True, 1073 | ) 1074 | elif preset == "next": 1075 | if os.path.exists(os.path.join(home, repo, name)): 1076 | console.print("[red]Project already exists![/]") 1077 | return 1078 | os.mkdir(os.path.join(home, repo, name)) 1079 | console.print( 1080 | "[green]Creating project with create-next-app, JavaScript and Pnpm[/]" 1081 | ) 1082 | if config.config["cli"]["display_output"]: 1083 | subprocess.run( 1084 | ["pnpm", "create", "next-app", name], 1085 | shell=True, 1086 | ) 1087 | else: 1088 | subprocess.run( 1089 | ["pnpm", "create", "next-app", name], 1090 | shell=True, 1091 | capture_output=True, 1092 | ) 1093 | return 1094 | elif preset == "vanilla": 1095 | if os.path.exists(os.path.join(home, repo, name)): 1096 | console.print("[red]Project already exists![/]") 1097 | return 1098 | os.mkdir(os.path.join(home, repo, name)) 1099 | os.chdir(os.path.join(home, repo, name)) 1100 | console.print("[green]Creating project with NPM, and JavaScript[/]") 1101 | if config.config["cli"]["display_output"]: 1102 | subprocess.run( 1103 | ["pnpm", "init"], 1104 | shell=True, 1105 | ) 1106 | else: 1107 | subprocess.run( 1108 | ["pnpm", "init"], 1109 | shell=True, 1110 | capture_output=True, 1111 | ) 1112 | return 1113 | elif preset == "astro": 1114 | if os.path.exists(os.path.join(home, repo, name)): 1115 | console.print("[red]Project already exists![/]") 1116 | return 1117 | os.mkdir(os.path.join(home, repo, name)) 1118 | console.print( 1119 | "[green]Creating project with Astro, JavaScript and Pnpm[/]" 1120 | ) 1121 | if config.config["cli"]["display_output"]: 1122 | subprocess.run( 1123 | ["pnpm", "create", "astro@latest", name], 1124 | shell=True, 1125 | ) 1126 | else: 1127 | subprocess.run( 1128 | ["pnpm", "create", "astro@latest", name], 1129 | shell=True, 1130 | capture_output=True, 1131 | ) 1132 | return 1133 | elif preset == "svelte": 1134 | if os.path.exists(os.path.join(home, repo, name)): 1135 | console.print("[red]Project already exists![/]") 1136 | return 1137 | os.mkdir(os.path.join(home, repo, name)) 1138 | console.print( 1139 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 1140 | ) 1141 | if config.config["cli"]["display_output"]: 1142 | subprocess.run( 1143 | ["pnpm", "create", "vite", name, "--", "--temaplte", "svelte"], 1144 | shell=True, 1145 | ) 1146 | else: 1147 | subprocess.run( 1148 | ["pnpm", "create", "vite", name, "--", "--temaplte", "svelte"], 1149 | shell=True, 1150 | capture_output=True, 1151 | ) 1152 | return 1153 | elif preset == "sveltekit": 1154 | if os.path.exists(os.path.join(home, repo, name)): 1155 | console.print("[red]Project already exists![/]") 1156 | return 1157 | os.mkdir(os.path.join(home, repo, name)) 1158 | console.print( 1159 | "[green]Creating project with SvelteKit, JavaScript and NPM[/]" 1160 | ) 1161 | if config.config["cli"]["display_output"]: 1162 | subprocess.run( 1163 | ["pnpm", "create", "svelte@latest", name], 1164 | shell=True, 1165 | ) 1166 | else: 1167 | subprocess.run( 1168 | ["pnpm", "create", "svelte@latest", name], 1169 | shell=True, 1170 | capture_output=True, 1171 | ) 1172 | return 1173 | elif preset == "vue": 1174 | if os.path.exists(os.path.join(home, repo, name)): 1175 | console.print("[red]Project already exists![/]") 1176 | return 1177 | os.mkdir(os.path.join(home, repo, name)) 1178 | console.print( 1179 | "[green]Creating project with Svelte, JavaScript and NPM[/]" 1180 | ) 1181 | if config.config["cli"]["display_output"]: 1182 | subprocess.run( 1183 | ["pnpm", "create", "vite", name, "--", "--template", "vue"], 1184 | shell=True, 1185 | ) 1186 | else: 1187 | subprocess.run( 1188 | ["pnpm", "create", "vite", name, "--", "--template", "vue"], 1189 | shell=True, 1190 | capture_output=True, 1191 | ) 1192 | return 1193 | 1194 | 1195 | class Pip(PackageManager): 1196 | name = "Pip" 1197 | cmd = "pip" 1198 | 1199 | @classmethod 1200 | def generate(cls, repo: str, name: str, preset: str): 1201 | if not cls.check(): 1202 | console.print("[red]pip is not installed![/]") 1203 | return 1204 | 1205 | if preset not in presets_py: 1206 | console.print(f"[red]Unknown preset: '{preset}'[/]") 1207 | return 1208 | 1209 | console.print("[green]Creating project with Pip[/]") 1210 | if not os.path.exists(home): 1211 | os.mkdir(home) 1212 | 1213 | os.chdir(home) 1214 | if os.path.exists(os.path.join(home, repo, name)): 1215 | console.print("[red]Project already exists![/]") 1216 | return 1217 | os.chdir(os.path.join(home, repo)) 1218 | deps = ( 1219 | questionary.text("Enter comma separated list of dependencies: ") 1220 | .ask() 1221 | .split(",") 1222 | ) 1223 | dep_progress = 50 / len(deps) 1224 | with Progress() as progress: 1225 | create_task = progress.add_task("[green]Creating files", total=100) 1226 | write_task = progress.add_task("[green]Writing data", total=100) 1227 | 1228 | os.mkdir(os.path.join(home, repo, name)) 1229 | progress.update(create_task, advance=5) 1230 | time.sleep(1) 1231 | os.mkdir(os.path.join(home, repo, name, "src")) 1232 | progress.update(create_task, advance=5) 1233 | time.sleep(1) 1234 | os.mkdir(os.path.join(home, repo, name, "src", name)) 1235 | progress.update(create_task, advance=5) 1236 | time.sleep(1) 1237 | os.mkdir(os.path.join(home, repo, name, "tests")) 1238 | progress.update(create_task, advance=5) 1239 | time.sleep(1) 1240 | with open(os.path.join(home, repo, name, "requirements.txt"), "w") as f: 1241 | f.write("") 1242 | progress.update(create_task, advance=10) 1243 | time.sleep(1) 1244 | with open(os.path.join(home, repo, name, "setup.py"), "w") as f: 1245 | f.write("") 1246 | progress.update(create_task, advance=10) 1247 | time.sleep(1) 1248 | with open( 1249 | os.path.join(home, repo, name, "src", name, "__init__.py"), 1250 | "w", 1251 | ) as f: 1252 | f.write("") 1253 | progress.update(create_task, advance=10) 1254 | time.sleep(1) 1255 | with open( 1256 | os.path.join(home, repo, name, "src", name, f"{name}.py"), 1257 | "w", 1258 | ) as f: 1259 | f.write("") 1260 | progress.update(create_task, advance=10) 1261 | time.sleep(1) 1262 | with open(os.path.join(home, repo, name, "README.md"), "w") as f: 1263 | f.write("") 1264 | progress.update(create_task, advance=10) 1265 | time.sleep(1) 1266 | with open(os.path.join(home, repo, name, "LICENSE"), "w") as f: 1267 | f.write("") 1268 | progress.update(create_task, advance=10) 1269 | time.sleep(1) 1270 | with open( 1271 | os.path.join(home, repo, name, "tests", "__init__.py"), 1272 | "w", 1273 | ) as f: 1274 | f.write("") 1275 | progress.update(create_task, advance=10) 1276 | time.sleep(1) 1277 | with open( 1278 | os.path.join(home, repo, name, "tests", f"test_{name}.py"), 1279 | "w", 1280 | ) as f: 1281 | f.write("") 1282 | progress.update(create_task, advance=10) 1283 | time.sleep(1) 1284 | progress.console.print("[green]Files created successfully![/]") 1285 | for dep in deps: 1286 | out = subprocess.run(["pip", "install", dep], capture_output=True) 1287 | if out.returncode != 0: 1288 | progress.console.print( 1289 | f"[red]Failed to install dependency: {dep}[/]" 1290 | ) 1291 | else: 1292 | progress.console.print( 1293 | f"[green]Dependency: {dep.lstrip().rstrip()} installed successfully![/]" 1294 | ) 1295 | progress.update(write_task, advance=dep_progress) 1296 | progress.console.print("[green]All dependencies installed successfully![/]") 1297 | progress.console.print("[green]Writing dependencies to files[/]") 1298 | with open(os.path.join(home, repo, name, "requirements.txt"), "w") as f: 1299 | for dep in deps: 1300 | f.write(f"{dep.lstrip().rstrip()}\n") 1301 | progress.update(write_task, advance=20) 1302 | time.sleep(1) 1303 | progress.console.print("[green]Writing setup.py[/]") 1304 | with open(os.path.join(home, repo, name, "setup.py"), "w") as f: 1305 | f.write( 1306 | f"""from setuptools import setup 1307 | setup(name='{name}', 1308 | version='0.0.1', 1309 | description='Package Description', 1310 | author={getpass.getuser()}, 1311 | author_email='', 1312 | url='', 1313 | package_dir={{"":"src"}}, 1314 | packages=setuptools.find_packages(where="src"), 1315 | python_requires='>={sys.version_info.major}.{sys.version_info.minor}', 1316 | install_requires={[dep.lstrip().rstrip() for dep in deps]}, 1317 | classifiers=[] 1318 | )""" 1319 | ) 1320 | progress.update(write_task, advance=20) 1321 | time.sleep(1) 1322 | progress.console.print("[green]Writing pyproject.toml[/]") 1323 | with open(os.path.join(home, repo, name, "pyproject.toml"), "w") as f: 1324 | f.write( 1325 | "[build-system]\n" 1326 | "requires = ['setuptools>=42']\n" 1327 | "build-backend = 'setuptools.build_meta'\n" 1328 | ) 1329 | progress.update(write_task, advance=5) 1330 | time.sleep(1) 1331 | progress.console.print("[green]Data written to files successfully![/]") 1332 | progress.console.print("[green]Initializing Git Repo[/]") 1333 | out = subprocess.run( 1334 | ["git", "init"], 1335 | cwd=os.path.join(home, repo, name), 1336 | capture_output=True, 1337 | ) 1338 | if out.returncode != 0: 1339 | progress.console.print("[red]Failed to initialize git repo![/]") 1340 | else: 1341 | progress.console.print("[green]Git repo initialized successfully![/]") 1342 | progress.update(write_task, advance=1) 1343 | progress.console.print("[green]Adding files to git repo[/]") 1344 | out = subprocess.run( 1345 | ["git", "add", "."], 1346 | cwd=os.path.join(home, repo, name), 1347 | capture_output=True, 1348 | ) 1349 | if out.returncode != 0: 1350 | progress.console.print("[red]Failed to add files to git repo![/]") 1351 | else: 1352 | progress.console.print( 1353 | "[green]Files added to git repo successfully![/]" 1354 | ) 1355 | progress.update(write_task, advance=2) 1356 | progress.console.print("[green]Committing files to git repo[/]") 1357 | out = subprocess.run( 1358 | ["git", "commit", "-m", "Initial Commit from rrpm"], 1359 | cwd=os.path.join(home, repo, name), 1360 | capture_output=True, 1361 | ) 1362 | if out.returncode != 0: 1363 | progress.console.print( 1364 | "[red]Failed to commit files to git repo![/]" 1365 | ) 1366 | else: 1367 | progress.console.print( 1368 | "[green]Files committed to git repo successfully![/]" 1369 | ) 1370 | progress.update(write_task, advance=2) 1371 | console.print("[green]Package created successfully![/]") 1372 | return 1373 | 1374 | 1375 | class Poetry(PackageManager): 1376 | name = "Poetry" 1377 | cmd = "poetry" 1378 | 1379 | @classmethod 1380 | def generate(cls, repo: str, name: str, preset: str): 1381 | if not cls.check(): 1382 | console.print("[red]poetry is not installed![/]") 1383 | return 1384 | 1385 | if preset not in presets_py: 1386 | console.print(f"[red]Unknown preset: '{preset}'[/]") 1387 | return 1388 | 1389 | console.print("[green]Creating project with Poetry[/]") 1390 | if not os.path.exists(home): 1391 | os.mkdir(home) 1392 | if os.path.exists(os.path.join(home, repo, name)): 1393 | console.print("[red]Project already exists![/]") 1394 | sys.exit(1) 1395 | os.chdir(os.path.join(home, repo)) 1396 | src = questionary.confirm("Use `src` layout?").ask() 1397 | if src: 1398 | subprocess.run( 1399 | [ 1400 | "poetry", 1401 | "new", 1402 | os.path.join(home, repo, name), 1403 | "--name", 1404 | name, 1405 | "--src", 1406 | ], 1407 | shell=True, 1408 | ) 1409 | else: 1410 | subprocess.run( 1411 | [ 1412 | "poetry", 1413 | "new", 1414 | os.path.join(home, repo, name), 1415 | "--name", 1416 | name, 1417 | ], 1418 | shell=True, 1419 | ) 1420 | 1421 | 1422 | class Venv(PackageManager): 1423 | name = "Virtual Environment" 1424 | cmd = "virtualenv" 1425 | 1426 | @classmethod 1427 | def generate(cls, repo: str, name: str, preset: str): 1428 | if not cls.check(): 1429 | console.print("[red]virtualenv is not installed![/]") 1430 | return 1431 | 1432 | if preset not in presets_py: 1433 | console.print(f"[red]Unknown preset: '{preset}'[/]") 1434 | return 1435 | 1436 | if not os.path.exists(os.path.join(home, repo)): 1437 | os.mkdir(os.path.join(home, repo)) 1438 | 1439 | if config.config["cli"]["display_output"]: 1440 | subprocess.run( 1441 | [ 1442 | "python", 1443 | "-m", 1444 | "virtualenv", 1445 | os.path.join(get_home_dir(), repo, name), 1446 | ] 1447 | ) 1448 | else: 1449 | subprocess.run( 1450 | [ 1451 | "python", 1452 | "-m", 1453 | "virtualenv", 1454 | os.path.join(get_home_dir(), repo, name), 1455 | ], 1456 | capture_output=True, 1457 | ) 1458 | 1459 | deps = ( 1460 | questionary.text("Enter comma separated list of dependencies: ") 1461 | .ask() 1462 | .split(",") 1463 | ) 1464 | dep_progress = 50 / len(deps) 1465 | with Progress() as progress: 1466 | create_task = progress.add_task("[green]Creating files", total=100) 1467 | write_task = progress.add_task("[green]Writing data", total=100) 1468 | 1469 | progress.update(create_task, advance=5) 1470 | time.sleep(1) 1471 | os.mkdir(os.path.join(home, repo, name, "src")) 1472 | progress.update(create_task, advance=5) 1473 | time.sleep(1) 1474 | os.mkdir(os.path.join(home, repo, name, "src", name)) 1475 | progress.update(create_task, advance=5) 1476 | time.sleep(1) 1477 | os.mkdir(os.path.join(home, repo, name, "tests")) 1478 | progress.update(create_task, advance=5) 1479 | time.sleep(1) 1480 | with open(os.path.join(home, repo, name, "requirements.txt"), "w") as f: 1481 | f.write("") 1482 | progress.update(create_task, advance=10) 1483 | time.sleep(1) 1484 | with open(os.path.join(home, repo, name, "setup.py"), "w") as f: 1485 | f.write("") 1486 | progress.update(create_task, advance=10) 1487 | time.sleep(1) 1488 | with open( 1489 | os.path.join(home, repo, name, "src", name, "__init__.py"), 1490 | "w", 1491 | ) as f: 1492 | f.write("") 1493 | progress.update(create_task, advance=10) 1494 | time.sleep(1) 1495 | with open( 1496 | os.path.join(home, repo, name, "src", name, f"{name}.py"), 1497 | "w", 1498 | ) as f: 1499 | f.write("") 1500 | progress.update(create_task, advance=10) 1501 | time.sleep(1) 1502 | with open(os.path.join(home, repo, name, "README.md"), "w") as f: 1503 | f.write("") 1504 | progress.update(create_task, advance=10) 1505 | time.sleep(1) 1506 | with open(os.path.join(home, repo, name, "LICENSE"), "w") as f: 1507 | f.write("") 1508 | progress.update(create_task, advance=10) 1509 | time.sleep(1) 1510 | with open( 1511 | os.path.join(home, repo, name, "tests", "__init__.py"), 1512 | "w", 1513 | ) as f: 1514 | f.write("") 1515 | progress.update(create_task, advance=10) 1516 | time.sleep(1) 1517 | with open( 1518 | os.path.join(home, repo, name, "tests", f"test_{name}.py"), 1519 | "w", 1520 | ) as f: 1521 | f.write("") 1522 | progress.update(create_task, advance=10) 1523 | time.sleep(1) 1524 | progress.console.print("[green]Files created successfully![/]") 1525 | for dep in deps: 1526 | out = subprocess.run(["pip", "install", dep], capture_output=True) 1527 | if out.returncode != 0: 1528 | progress.console.print( 1529 | f"[red]Failed to install dependency: {dep}[/]" 1530 | ) 1531 | else: 1532 | progress.console.print( 1533 | f"[green]Dependency: {dep.lstrip().rstrip()} installed successfully![/]" 1534 | ) 1535 | progress.update(write_task, advance=dep_progress) 1536 | progress.console.print("[green]All dependencies installed successfully![/]") 1537 | progress.console.print("[green]Writing dependencies to files[/]") 1538 | with open(os.path.join(home, repo, name, "requirements.txt"), "w") as f: 1539 | for dep in deps: 1540 | f.write(f"{dep.lstrip().rstrip()}\n") 1541 | progress.update(write_task, advance=20) 1542 | time.sleep(1) 1543 | progress.console.print("[green]Writing setup.py[/]") 1544 | with open(os.path.join(home, repo, name, "setup.py"), "w") as f: 1545 | f.write( 1546 | f"""from setuptools import setup 1547 | setup(name='{name}', 1548 | version='0.0.1', 1549 | description='Package Description', 1550 | author={getpass.getuser()}, 1551 | author_email='', 1552 | url='', 1553 | package_dir={{"":"src"}}, 1554 | packages=setuptools.find_packages(where="src"), 1555 | python_requires='>={sys.version_info.major}.{sys.version_info.minor}', 1556 | install_requires={[dep.lstrip().rstrip() for dep in deps]}, 1557 | classifiers=[] 1558 | )""" 1559 | ) 1560 | progress.update(write_task, advance=20) 1561 | time.sleep(1) 1562 | progress.console.print("[green]Writing pyproject.toml[/]") 1563 | with open(os.path.join(home, repo, name, "pyproject.toml"), "w") as f: 1564 | f.write( 1565 | "[build-system]\n" 1566 | "requires = ['setuptools>=42']\n" 1567 | "build-backend = 'setuptools.build_meta'\n" 1568 | ) 1569 | progress.update(write_task, advance=5) 1570 | time.sleep(1) 1571 | progress.console.print("[green]Data written to files successfully![/]") 1572 | progress.console.print("[green]Initializing Git Repo[/]") 1573 | out = subprocess.run( 1574 | ["git", "init"], 1575 | cwd=os.path.join(home, repo, name), 1576 | capture_output=True, 1577 | ) 1578 | if out.returncode != 0: 1579 | progress.console.print("[red]Failed to initialize git repo![/]") 1580 | else: 1581 | progress.console.print("[green]Git repo initialized successfully![/]") 1582 | progress.update(write_task, advance=1) 1583 | progress.console.print("[green]Adding files to git repo[/]") 1584 | out = subprocess.run( 1585 | ["git", "add", "."], 1586 | cwd=os.path.join(home, repo, name), 1587 | capture_output=True, 1588 | ) 1589 | if out.returncode != 0: 1590 | progress.console.print("[red]Failed to add files to git repo![/]") 1591 | else: 1592 | progress.console.print( 1593 | "[green]Files added to git repo successfully![/]" 1594 | ) 1595 | progress.update(write_task, advance=2) 1596 | progress.console.print("[green]Committing files to git repo[/]") 1597 | out = subprocess.run( 1598 | ["git", "commit", "-m", "Initial Commit from rrpm"], 1599 | cwd=os.path.join(home, repo, name), 1600 | capture_output=True, 1601 | ) 1602 | if out.returncode != 0: 1603 | progress.console.print( 1604 | "[red]Failed to commit files to git repo![/]" 1605 | ) 1606 | else: 1607 | progress.console.print( 1608 | "[green]Files committed to git repo successfully![/]" 1609 | ) 1610 | progress.update(write_task, advance=2) 1611 | console.print("[green]Package created successfully![/]") 1612 | -------------------------------------------------------------------------------- /rrpm/presets/js.py: -------------------------------------------------------------------------------- 1 | from rich.console import Console 2 | 3 | from .base.managers import NPM, PNPM, Yarn 4 | from .base.base import Preset, PackageManager 5 | from rrpm.utils import get_home_dir 6 | from rrpm.config import Config 7 | 8 | console = Console() 9 | config = Config() 10 | home = get_home_dir() 11 | 12 | 13 | class React(Preset): 14 | package_managers = [NPM, Yarn, PNPM] 15 | 16 | def generate(self, pkg: PackageManager): 17 | pkg.generate(self.repo, self.name, "react", False) 18 | 19 | 20 | class Vanilla(Preset): 21 | package_managers = [NPM, Yarn, PNPM] 22 | 23 | def generate(self, pkg: PackageManager): 24 | pkg.generate(self.repo, self.name, "vanilla", False) 25 | 26 | 27 | class NextJS(Preset): 28 | package_managers = [NPM, Yarn, PNPM] 29 | 30 | def generate(self, pkg: PackageManager): 31 | pkg.generate(self.repo, self.name, "next", False) 32 | 33 | 34 | class Astro(Preset): 35 | package_managers = [NPM, Yarn, PNPM] 36 | 37 | def generate(self, pkg: PackageManager): 38 | pkg.generate(self.repo, self.name, "astro", False) 39 | 40 | 41 | class Svelte(Preset): 42 | package_managers = [NPM, Yarn, PNPM] 43 | 44 | def generate(self, pkg: PackageManager): 45 | pkg.generate(self.repo, self.name, "svelte", False) 46 | 47 | 48 | class SvelteKit(Preset): 49 | package_managers = [NPM, Yarn, PNPM] 50 | 51 | def generate(self, pkg: PackageManager): 52 | pkg.generate(self.repo, self.name, "sveltekit", False) 53 | 54 | 55 | class Vue(Preset): 56 | package_managers = [NPM, Yarn, PNPM] 57 | 58 | def generate(self, pkg: PackageManager): 59 | pkg.generate(self.repo, self.name, "vue", False) 60 | -------------------------------------------------------------------------------- /rrpm/presets/py.py: -------------------------------------------------------------------------------- 1 | from rich.console import Console 2 | 3 | from .base.managers import Pip, Poetry, Venv 4 | from .base.base import Preset, PackageManager 5 | from rrpm.utils import get_home_dir 6 | from rrpm.config import Config 7 | 8 | console = Console() 9 | config = Config() 10 | home = get_home_dir() 11 | 12 | 13 | class Vanilla(Preset): 14 | package_managers = [Pip, Poetry, Venv] 15 | 16 | def generate(self, pkg: PackageManager): 17 | pkg.generate(self.repo, self.name, "vanilla") 18 | 19 | 20 | class Flask(Preset): 21 | package_managers = [Pip, Poetry, Venv] 22 | 23 | def generate(self, pkg: PackageManager): 24 | pkg.generate(self.repo, self.name, "flask") 25 | 26 | 27 | class FastAPI(Preset): 28 | package_managers = [Pip, Poetry, Venv] 29 | 30 | def generate(self, pkg: PackageManager): 31 | pkg.generate(self.repo, self.name, "fastapi") 32 | -------------------------------------------------------------------------------- /rrpm/presets/ts.py: -------------------------------------------------------------------------------- 1 | from rich.console import Console 2 | 3 | from .base.managers import NPM, PNPM, Yarn 4 | from .base.base import Preset, PackageManager 5 | from rrpm.utils import get_home_dir 6 | from rrpm.config import Config 7 | 8 | console = Console() 9 | config = Config() 10 | home = get_home_dir() 11 | 12 | 13 | class React(Preset): 14 | package_managers = [NPM, Yarn, PNPM] 15 | 16 | def generate(self, pkg: PackageManager): 17 | pkg.generate(self.repo, self.name, "react", True) 18 | 19 | 20 | class Vanilla(Preset): 21 | package_managers = [NPM, Yarn, PNPM] 22 | 23 | def generate(self, pkg: PackageManager): 24 | pkg.generate(self.repo, self.name, "vanilla", True) 25 | 26 | 27 | class NextJS(Preset): 28 | package_managers = [NPM, Yarn, PNPM] 29 | 30 | def generate(self, pkg: PackageManager): 31 | pkg.generate(self.repo, self.name, "next", True) 32 | 33 | class Astro(Preset): 34 | package_managers = [NPM, Yarn, PNPM] 35 | 36 | def generate(self, pkg: PackageManager): 37 | pkg.generate(self.repo, self.name, "astro", True) 38 | 39 | 40 | class Svelte(Preset): 41 | package_managers = [NPM, Yarn, PNPM] 42 | 43 | def generate(self, pkg: PackageManager): 44 | pkg.generate(self.repo, self.name, "svelte", True) 45 | 46 | 47 | class SvelteKit(Preset): 48 | package_managers = [NPM, Yarn, PNPM] 49 | 50 | def generate(self, pkg: PackageManager): 51 | pkg.generate(self.repo, self.name, "sveltekit", True) 52 | 53 | 54 | class Vue(Preset): 55 | package_managers = [NPM, Yarn, PNPM] 56 | 57 | def generate(self, pkg: PackageManager): 58 | pkg.generate(self.repo, self.name, "vue", True) 59 | -------------------------------------------------------------------------------- /rrpm/rrpm.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import shutil 5 | import subprocess 6 | import sys 7 | from pathlib import Path 8 | 9 | import questionary 10 | from typer import Typer 11 | from rich.console import Console 12 | from rich.markdown import Markdown 13 | from rich.table import Table 14 | 15 | from .presets.py import Vanilla as VanillaPy, FastAPI, Flask 16 | from .presets.js import React, NextJS, Vanilla, Astro, Svelte, SvelteKit, Vue 17 | from .presets.ts import React as ReactTS, NextJS as NextTS, Vanilla as VanillaTS, Astro as AstroTS, Svelte as SvelteTS, SvelteKit as SvelteKitTS, Vue as VueTS 18 | from .utils import ( 19 | get_home_dir, 20 | get_domain, 21 | get_user_repo, 22 | is_domain, 23 | is_shorthand, 24 | get_all_dirs, 25 | ) 26 | from .ext.loader import load_extension 27 | from .config import Config 28 | 29 | console = Console() 30 | config = Config() 31 | cli = Typer() 32 | 33 | DOMAIN_REGEX = re.compile(r"([a-zA-Z0-9_-]+\.)?(.*)\.([a-zA-Z]+)") 34 | 35 | 36 | @cli.command(help="Clone a remote repository to directory specified in config") 37 | def get(url: str): 38 | if not is_domain(url) and not is_shorthand(url): 39 | console.print("[red]Invalid domain or shorthand![/]") 40 | return 41 | 42 | if is_domain(url) and not url.startswith("http://") and not url.startswith("https://"): 43 | url = "https://" + url 44 | 45 | home_dir = get_home_dir() 46 | try: 47 | domain = get_domain(url) 48 | except IndexError: 49 | pass 50 | 51 | if is_shorthand(url): 52 | domain = "github.com" 53 | 54 | if not os.path.exists(home_dir): 55 | os.mkdir(home_dir) 56 | 57 | if not os.path.exists(os.path.join(home_dir, domain)): 58 | os.mkdir(os.path.join(home_dir, domain)) 59 | 60 | for ext in config.config["extensions"]["hooks"]: 61 | try: 62 | load_extension( 63 | os.path.expandvars( 64 | os.path.expanduser(config.config["root"]["ext_dir"]) 65 | ), 66 | ext, 67 | ).pre_fetch(url) 68 | except Exception: 69 | console.print(f"[red]Exception occured in extension: {ext}[/]") 70 | console.print_exception() 71 | return 72 | if is_shorthand(url): 73 | url = "https://github.com/" + url 74 | 75 | try: 76 | user, repo = get_user_repo(url) 77 | except IndexError: 78 | console.print("[red]Cannot determine user/repository from given URL![/]") 79 | return 80 | 81 | user_dir = os.path.join(home_dir, domain, user) 82 | repo_dir = os.path.join(home_dir, domain, user, repo) 83 | 84 | if not os.path.exists(user_dir): 85 | os.mkdir(user_dir) 86 | 87 | if os.path.exists(repo_dir): 88 | console.print("[yellow]Repository is already cloned![/]") 89 | pull = questionary.confirm("Pull new commits instead?").ask() 90 | if pull: 91 | os.chdir(repo_dir) 92 | out = subprocess.run(["git", "pull"], capture_output=True, shell=True) 93 | if out.returncode == 0: 94 | console.print("[green]Successfully pulled new commits![/]") 95 | return 96 | else: 97 | console.print( 98 | f"[red]Failed to pull updates with exit status {out.returncode}![/]" 99 | ) 100 | return 101 | 102 | console.print(f"[green]Fetching repository from {url}[/]") 103 | out = subprocess.run( 104 | ["git", "clone", url, repo_dir], capture_output=True, shell=True 105 | ) 106 | if out.returncode == 0: 107 | console.print( 108 | f"[green]Successfully cloned repository in github.com/{user}/{repo}[/]" 109 | ) 110 | else: 111 | console.print(f"[red]Failed to clone with exit status {out.returncode}[/]") 112 | 113 | for ext in config.config["extensions"]["hooks"]: 114 | try: 115 | load_extension( 116 | os.path.expandvars( 117 | os.path.expanduser(config.config["root"]["ext_dir"]) 118 | ), 119 | ext, 120 | ).post_fetch(url) 121 | except Exception: 122 | console.print(f"[red]Exception occured in extension: {ext}[/]") 123 | console.print_exception() 124 | return 125 | 126 | 127 | @cli.command(name="remove", help="Remove a cloned repository") 128 | def remove(shorthand: str): 129 | if not is_shorthand(shorthand): 130 | console.print("[red]Invalid shorthand![/]") 131 | return 132 | 133 | home_dir = get_home_dir() 134 | 135 | if not os.path.exists(home_dir): 136 | os.mkdir(home_dir) 137 | console.print("[red]Repository does not exist![/]") 138 | return 139 | 140 | matches = [] 141 | 142 | for domain in os.listdir(home_dir): 143 | if os.path.isdir(os.path.join(home_dir, domain)): 144 | for user in os.listdir(os.path.join(home_dir, domain)): 145 | if os.path.isdir(os.path.join(home_dir, domain, user)): 146 | for repo in os.listdir(os.path.join(home_dir, domain, user)): 147 | if repo == shorthand.split("/")[1] and os.path.isdir( 148 | os.path.join(home_dir, domain, user, repo) 149 | ): 150 | matches.append(domain + "/" + user + "/" + repo) 151 | 152 | if matches != []: 153 | repo = questionary.select("Select Repository", choices=matches).ask() 154 | console.print(f"[yellow]Removing repository: '{repo}'[/]") 155 | try: 156 | shutil.rmtree(os.path.realpath(os.path.join(home_dir, repo))) 157 | except PermissionError as e: 158 | console.print("[red]Failed to remove repository: '{repo}'[/]") 159 | file = str(e).split("'") 160 | console.print(f"[red]Access denied to file: {file[1]}[/]") 161 | return 162 | console.print(f"[green]Successfully removed repository: '{repo}'[/]") 163 | else: 164 | console.print("[red]No matching repositories found![/]") 165 | 166 | 167 | @cli.command(name="tree", help="List all cloned repositories and generated projects") 168 | def tree(): 169 | home_dir = get_home_dir() 170 | if os.path.exists(home_dir): 171 | console.print(f"[red]{home_dir}[/]") 172 | for host in os.listdir(home_dir): 173 | if not host == "." and not host == "..": 174 | console.print(f" |- [blue]{host}[/]") 175 | if len(os.listdir(os.path.join(home_dir, host))) != 0: 176 | if host == "github.com": 177 | for user in os.listdir(os.path.join(home_dir, host)): 178 | console.print(f" |- [green]{user}[/]") 179 | if len(os.listdir(os.path.join(home_dir, host, user))) != 0: 180 | for repo in os.listdir( 181 | os.path.join(home_dir, host, user) 182 | ): 183 | console.print(f" |- [magenta]{repo}[/]") 184 | else: 185 | for repo in os.listdir(os.path.join(home_dir, host)): 186 | console.print(f" |- [green]{repo}[/]") 187 | 188 | 189 | @cli.command(name="list") 190 | def list_(): 191 | total = 0 192 | table = Table(title="[green]List of Repositories[/]") 193 | table.add_column("[red]Site") 194 | table.add_column("[green]Repository") 195 | table.add_column("[blue]Owner") 196 | table.add_column("[magenta]Shorthand") 197 | for i in os.listdir(os.path.realpath(get_home_dir())): 198 | if os.path.isdir(os.path.realpath(os.path.join(get_home_dir(), i))): 199 | for j in os.listdir(os.path.realpath(os.path.join(get_home_dir(), i))): 200 | if os.path.isdir(os.path.realpath(os.path.join(get_home_dir(), i, j))): 201 | for k in os.listdir( 202 | os.path.realpath(os.path.join(get_home_dir(), i, j)) 203 | ): 204 | if os.path.isdir( 205 | os.path.realpath(os.path.join(get_home_dir(), i, j, k)) 206 | ): 207 | table.add_row( 208 | f"[red]{i}", 209 | f"[green]{k}", 210 | f"[blue]{j}", 211 | f"[magenta]{j}/{k}[/]", 212 | ) 213 | total += 1 214 | console.print(table) 215 | console.print(f"[green]Total Repositories: [/][blue]{total}[/]") 216 | 217 | 218 | @cli.command( 219 | name="migrate", help="Migrate and import all repositories from another directory" 220 | ) 221 | def migrate(path: Path): 222 | if not path.exists(): 223 | console.print(f"[red]Directory: '{path}' doesn't exist![/]") 224 | return 225 | 226 | if not path.is_dir(): 227 | console.print(f"[red]Path: '{path}' is not a directory![/]") 228 | return 229 | 230 | console.print(f"Importing projects from '{path}'...") 231 | console.print("[red]Warning: All your uncommited changes will be lost!") 232 | ignore = questionary.confirm("Continue").ask() 233 | if not ignore: 234 | return 235 | dirs = get_all_dirs(path) 236 | repos_filtered = [] 237 | remotes = [] 238 | for dir_ in dirs: 239 | if ".git" in str(dir_).split("\\"): 240 | repo_list = str(dir_).split("\\") 241 | repo_name = "\\".join(repo_list[: repo_list.index(".git")]) 242 | if repo_name not in repos_filtered: 243 | repos_filtered.append(repo_name) 244 | console.print(f"[green]Found Repository in: {repo_name}") 245 | 246 | console.print(f"Total Repositories Found: {len(repos_filtered)}") 247 | for repo in repos_filtered: 248 | os.chdir(repo) 249 | out = subprocess.run(["git", "remote", "-v"], capture_output=True) 250 | if not out.stdout: 251 | repos_filtered.remove(repo) 252 | console.print( 253 | f"[red]Ignoring repository: {repo} as no remote is present[/]" 254 | ) 255 | else: 256 | remote = out.stdout.decode().split("\n")[0].split("\t")[1].split(" ")[0] 257 | remotes.append(remote) 258 | repo_name = repo.split("\\")[-1] 259 | console.print(f"[green]Using remote: {remote} for repository: {repo_name}") 260 | 261 | for remote in remotes: 262 | get(remote) 263 | repo_name = repos_filtered[remotes.index(remote)].split("\\")[-1] 264 | console.print(f"[red]Deleting project: {repo_name}") 265 | try: 266 | shutil.rmtree(os.path.realpath(repos_filtered[remotes.index(remote)])) 267 | except PermissionError: 268 | console.print( 269 | f"[red]Warning: Failed to delete repository: {repo_name}: access denied![/]" 270 | ) 271 | 272 | 273 | @cli.command(help="Generate a project from any of the presets and/or its variations") 274 | def create(name: str): 275 | home = get_home_dir() 276 | exts = [] 277 | base_choices = ["Python", "FastAPI", "Flask", "NodeJS", "React", "NextJS", "Astro", "Svelte", "SvelteKit", "Vue"] 278 | for ext in config.config["extensions"]["presets"]: 279 | try: 280 | ext_ = load_extension( 281 | os.path.expandvars( 282 | os.path.expanduser(config.config["root"]["ext_dir"]) 283 | ), 284 | ext, 285 | ).Preset.name 286 | exts.append({ext_: load_extension(config.config["root"]["ext_dir"], ext)}) 287 | base_choices += [ext_] 288 | except Exception: 289 | try: 290 | if config.config["extensions"]["ignore_extension_load_error"] is True: 291 | console.print(f"[red]Failed to load extension {ext}[/]") 292 | else: 293 | console.print(f"[red]Failed to load extension {ext}[/]") 294 | console.print_exception() 295 | console.print( 296 | "[red]To disable exitting the program, consider adding ignore_extension_load_error = true to your config file.[/]" 297 | ) 298 | return 299 | except KeyError: 300 | console.print(f"[red]Failed to load extension {ext}[/]") 301 | console.print_exception() 302 | console.print( 303 | "[red]To disable exitting the program, consider adding ignore_extension_load_error = true to your config file.[/]" 304 | ) 305 | return 306 | prj_type = questionary.select( 307 | "Project Preset", 308 | choices=base_choices, 309 | ).ask() 310 | try: 311 | repository = questionary.select( 312 | "Repository", choices=os.listdir(home) + ["Other"] 313 | ).ask() 314 | except FileNotFoundError: 315 | console.print("Invalid directory specified in config") 316 | return 317 | if repository == "Other": 318 | repository = questionary.text("Enter Domain(without 'https://'): ").ask() 319 | if DOMAIN_REGEX.match(repository) is None: 320 | console.print("[red]Invalid repository![/]") 321 | return 322 | os.mkdir(os.path.join(home, repository)) 323 | if repository == "github.com": 324 | if not os.path.exists(os.path.join(home, repository)): 325 | os.mkdir(os.path.join(home, repository)) 326 | user = questionary.select( 327 | "GitHub Username", 328 | choices=os.listdir(os.path.join(home, "github.com")) + ["Other"], 329 | ).ask() 330 | if user == "Other": 331 | user = questionary.text("Enter Username: ").ask() 332 | repository = os.path.join(repository, user) 333 | if prj_type in ["Python", "FastAPI", "Flask"]: 334 | if prj_type == "Python": 335 | preset = VanillaPy(repository, name) 336 | pms = {man.name: man for man in preset.package_managers} 337 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 338 | if not pm or not repository or not name: 339 | return 340 | preset.generate(pms.get(pm)) 341 | elif prj_type == "FastAPI": 342 | preset = FastAPI(repository, name) 343 | pms = {man.name: man for man in preset.package_managers} 344 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 345 | if not pm or not repository or not name: 346 | return 347 | preset.generate(pms.get(pm)) 348 | elif prj_type == "Flask": 349 | preset = Flask(repository, name) 350 | pms = {man.name: man for man in preset.package_managers} 351 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 352 | if not pm or not repository or not name: 353 | return 354 | preset.generate(pms.get(pm)) 355 | elif prj_type in ["NodeJS", "React", "NextJS", "Astro", "Svelte", "SvelteKit", "Vue"]: 356 | ts = questionary.confirm("Use TypeScript").ask() 357 | if ts: 358 | if prj_type == "NodeJS": 359 | preset = VanillaTS(repository, name) 360 | pms = {man.name: man for man in preset.package_managers} 361 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 362 | if not pm or not repository or not name: 363 | return 364 | preset.generate(pms.get(pm)) 365 | elif prj_type == "React": 366 | preset = ReactTS(repository, name) 367 | pms = {man.name: man for man in preset.package_managers} 368 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 369 | if not pm or not repository or not name: 370 | return 371 | preset.generate(pms.get(pm)) 372 | elif prj_type == "NextJS": 373 | preset = NextTS(repository, name) 374 | pms = {man.name: man for man in preset.package_managers} 375 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 376 | if not pm or not repository or not name: 377 | return 378 | preset.generate(pms.get(pm)) 379 | elif prj_type == "Astro": 380 | preset = AstroTS(repository, name) 381 | pms = {man.name: man for man in preset.package_managers} 382 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 383 | if not pm or not repository or not name: 384 | return 385 | preset.generate(pms.get(pm)) 386 | elif prj_type == "Svelte": 387 | preset = SvelteTS(repository, name) 388 | pms = {man.name: man for man in preset.package_managers} 389 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 390 | if not pm or not repository or not name: 391 | return 392 | preset.generate(pms.get(pm)) 393 | elif prj_type == "SvelteKit": 394 | preset = SvelteKitTS(repository, name) 395 | pms = {man.name: man for man in preset.package_managers} 396 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 397 | if not pm or not repository or not name: 398 | return 399 | preset.generate(pms.get(pm)) 400 | elif prj_type == "Vue": 401 | preset = VueTS(repository, name) 402 | pms = {man.name: man for man in preset.package_managers} 403 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 404 | if not pm or not repository or not name: 405 | return 406 | preset.generate(pms.get(pm)) 407 | 408 | else: 409 | if prj_type == "NodeJS": 410 | preset = Vanilla(repository, name) 411 | pms = {man.name: man for man in preset.package_managers} 412 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 413 | if not pm or not repository or not name: 414 | return 415 | preset.generate(pms.get(pm)) 416 | elif prj_type == "React": 417 | preset = React(repository, name) 418 | pms = {man.name: man for man in preset.package_managers} 419 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 420 | if not pm or not repository or not name: 421 | return 422 | preset.generate(pms.get(pm)) 423 | elif prj_type == "NodeJS": 424 | preset = NextJS(repository, name) 425 | pms = {man.name: man for man in preset.package_managers} 426 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 427 | if not pm or not repository or not name: 428 | return 429 | preset.generate(pms.get(pm)) 430 | elif prj_type == "Astro": 431 | preset = Astro(repository, name) 432 | pms = {man.name: man for man in preset.package_managers} 433 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 434 | if not pm or not repository or not name: 435 | return 436 | preset.generate(pms.get(pm)) 437 | elif prj_type == "Svelte": 438 | preset = Svelte(repository, name) 439 | pms = {man.name: man for man in preset.package_managers} 440 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 441 | if not pm or not repository or not name: 442 | return 443 | preset.generate(pms.get(pm)) 444 | elif prj_type == "SvelteKit": 445 | preset = SvelteKit(repository, name) 446 | pms = {man.name: man for man in preset.package_managers} 447 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 448 | if not pm or not repository or not name: 449 | return 450 | preset.generate(pms.get(pm)) 451 | elif prj_type == "Vue": 452 | preset = Vue(repository, name) 453 | pms = {man.name: man for man in preset.package_managers} 454 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 455 | if not pm or not repository or not name: 456 | return 457 | preset.generate(pms.get(pm)) 458 | else: 459 | for ext in exts: 460 | if ext.get(prj_type) is not None: 461 | try: 462 | preset = ext[prj_type].Preset(repository, name) 463 | pms = {man.name: man for man in preset.package_managers} 464 | pm = questionary.select("Package Manager", choices=pms.keys()).ask() 465 | if not pm or not repository or not name: 466 | return 467 | preset.generate(pms.get(pm)) 468 | except Exception: 469 | console.print( 470 | f"[red]Exception occured in extension: {ext[prj_type].__file__}[/]" 471 | ) 472 | console.print_exception() 473 | return 474 | break 475 | 476 | 477 | @cli.command(name="config", help="View current config file or regenerate config file") 478 | def view_config(regenerate: bool = False, generate: bool = False): 479 | if generate is True: 480 | if sys.platform.lower().startswith("win"): 481 | CONFIG = { 482 | "root": { 483 | "dir": "%USERPROFILE%\\Projects", 484 | "ext_dir": "%LOCALAPPDATA%\\rrpm\\extensions", 485 | }, 486 | "cli": { 487 | "display_output": False, 488 | "ignore_extension_load_error": False, 489 | }, 490 | "extensions": {"presets": [], "hooks": []}, 491 | } 492 | else: 493 | CONFIG = { 494 | "root": { 495 | "dir": "~/Projects", 496 | "exts_dir": "~/.config/rrpm/extensions", 497 | }, 498 | "cli": { 499 | "display_output": False, 500 | "ignore_extension_load_error": False, 501 | }, 502 | "extensions": {"presets": [], "hooks": []}, 503 | } 504 | root_dir = questionary.path( 505 | "Root Project Directory", CONFIG["root"]["dir"] 506 | ).ask() 507 | ext_dir = questionary.path( 508 | "Extensions Directory", CONFIG["root"]["ext_dir"] 509 | ).ask() 510 | output = questionary.confirm( 511 | "Display raw git command output", CONFIG["cli"]["display_output"] 512 | ).ask() 513 | ignore_error = questionary.confirm( 514 | "Ignore extension load errors", CONFIG["cli"]["ignore_extension_load_error"] 515 | ).ask() 516 | CONFIG["root"]["dir"] = os.path.realpath( 517 | os.path.expandvars(os.path.expanduser(root_dir)) 518 | ) 519 | CONFIG["root"]["ext_dir"] = os.path.realpath( 520 | os.path.expandvars(os.path.expanduser(ext_dir)) 521 | ) 522 | CONFIG["cli"]["display_output"] = output 523 | CONFIG["cli"]["ignore_extension_load_error"] = ignore_error 524 | config.generate(CONFIG) 525 | console.print("[green]Successfully saved new config![/]") 526 | return 527 | 528 | if regenerate is True: 529 | config.regenerate() 530 | console.print("[green]Config file regenerated successfully![/]") 531 | else: 532 | configstring = json.dumps(config.config, indent=2) 533 | md = f"```json\n{configstring}\n```" 534 | console.print(Markdown(md)) 535 | 536 | 537 | if __name__ == "__main__": 538 | cli() 539 | -------------------------------------------------------------------------------- /rrpm/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | from typing import Tuple 5 | from pathlib import Path 6 | 7 | from .config import Config 8 | 9 | 10 | config = Config() 11 | GH_REGEX = re.compile(r"(https://)?(www\.)?github\.com/[A-Za-z0-9_-]+/?") 12 | SHORTHAND_REGEX = re.compile(r"^[a-zA-Z-0-9]+\/[a-zA-Z-0-9_\.]+$") 13 | DOMAIN_REGEX = re.compile( 14 | r"^(http:\/\/|https:\/\/)?([a-zA-Z0-9-_]+\.)?([a-zA-Z0-9-_]+\.)([a-zA-Z0-9-_]+)" 15 | ) 16 | 17 | 18 | def get_home_dir(): 19 | if sys.platform.lower().startswith("win"): 20 | return os.path.realpath(os.path.expandvars(config.config["root"]["dir"])) 21 | return os.path.realpath(os.path.expanduser(config.config["root"]["dir"])) 22 | 23 | 24 | def is_shorthand(url: str) -> bool: 25 | if SHORTHAND_REGEX.match(url) is not None: 26 | return True 27 | return False 28 | 29 | 30 | def is_domain(url: str) -> bool: 31 | if DOMAIN_REGEX.match(url) is not None: 32 | return True 33 | return False 34 | 35 | 36 | def get_domain(url: str) -> str: 37 | return url.split("/")[2] 38 | 39 | 40 | def get_user_repo(url: str) -> Tuple[str, str]: 41 | return url.replace("https://", "").replace("http://", "").split("/")[ 42 | 1 43 | ], url.replace("https://", "").replace("http://", "").split("/")[2].replace( 44 | ".git", "" 45 | ) 46 | 47 | 48 | def recursive_file_search(path: Path): 49 | paths = [] 50 | for i in path.iterdir(): 51 | if Path(i).is_file(): 52 | paths.append(str(Path(i))) 53 | else: 54 | paths += recursive_file_search(Path(i)) 55 | return paths 56 | 57 | 58 | def get_all_dirs(path: Path): 59 | paths = [] 60 | for i in path.iterdir(): 61 | if Path(i).is_dir(): 62 | paths.append(i) 63 | paths += get_all_dirs(Path(i)) 64 | return paths 65 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrpm-org/rrpm/df911ffc51e6f83b971f03ae6ac58196ba4ff47c/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_rrpm.py: -------------------------------------------------------------------------------- 1 | from rrpm import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == "0.1.0" 6 | --------------------------------------------------------------------------------