├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
--------------------------------------------------------------------------------