├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── _config.yml
├── bin
└── index.js
├── images
├── logo-192.png
├── logo-512.png
└── terminal.gif
├── lib
├── actions.js
├── colors.js
├── commands
│ ├── add.js
│ ├── create.js
│ ├── edit.js
│ ├── getpath.js
│ ├── open.js
│ ├── remove.js
│ ├── rmeditor.js
│ └── seteditor.js
├── helper.js
└── logs.js
├── mocharc.json
├── npm-shrinkwrap.json
├── package.json
├── tests
├── action.spec.js
├── helper.spec.js
├── open.spec.js
└── utils
│ └── cleanup.js
└── types
└── utils.d.ts
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | old-code/
3 | dev/
4 | node_modules
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es6: true,
4 | node: true,
5 | jest: true
6 | },
7 | extends: ['google', 'prettier'],
8 | plugins: ['prettier'],
9 | globals: {
10 | Atomics: 'readonly',
11 | SharedArrayBuffer: 'readonly'
12 | },
13 | parserOptions: {
14 | ecmaVersion: 2018
15 | },
16 | rules: {
17 | 'prettier/prettier': ['error'],
18 | 'comma-dangle': 0,
19 | 'require-jsdoc': 0,
20 | 'no-trailing-spaces': 0,
21 | 'arrow-parens': 0,
22 | 'guard-for-in': 0,
23 | 'valid-jsdoc': 0,
24 | 'no-undef': 'error',
25 | 'operator-linebreak': [
26 | 'error',
27 | 'after',
28 | {
29 | overrides: { '?': 'ignore', ':': 'ignore', '+': 'ignore' }
30 | }
31 | ],
32 | indent: [
33 | 'error',
34 | 2,
35 | {
36 | CallExpression: { arguments: 'first' },
37 | ignoredNodes: [
38 | 'CallExpression > CallExpression',
39 | 'CallExpression > MemberExpression'
40 | ],
41 | SwitchCase: 1
42 | }
43 | ],
44 | 'max-len': ['error', { code: 80, ignoreComments: true }]
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | dist
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 | *.pid.lock
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # Bower dependency directory (https://bower.io/)
28 | bower_components
29 |
30 | # node-waf configuration
31 | .lock-wscript
32 |
33 | # Compiled binary addons (https://nodejs.org/api/addons.html)
34 | build/Release
35 |
36 | # Dependency directories
37 | node_modules/
38 | jspm_packages/
39 |
40 | # TypeScript v1 declaration files
41 | typings/
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Optional REPL history
50 | .node_repl_history
51 |
52 | # Output of 'npm pack'
53 | *.tgz
54 |
55 | # Yarn Integrity file
56 | .yarn-integrity
57 |
58 | # dotenv environment variables file
59 | .env
60 |
61 | # next.js build output
62 | .next
63 |
64 | # OSX specific files
65 | .DS_Store
66 |
67 | test-app
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | tests
2 | mocharc.json
3 | build
4 | dist
5 | _config.yml
6 | images
7 | .eslintrc.js
8 | .eslintignore
9 | .prettierignore
10 | .prettierrc
11 | .vscode/
12 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | old-code/
3 | dev/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "printWidth": 80,
5 | "trailingComma": "none"
6 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Production Releases
2 | (Note: Only production releases will be mentioned here, If you want to see beta releases, you can find them [here](https://github.com/saurabhdaware/projectman/releases))
3 |
4 | ### v2.0.0 *[LATEST RELEASE]*
5 |
6 | - Add `pm create` command
7 | - Remove support for binaries
8 |
9 |
10 | ### v1.3.3
11 |
12 | Command suggestions added (Thanks [@jamesgeorge007](https://github.com/jamesgeorge007) for [#PR32](https://github.com/saurabhdaware/projectman/pull/32))
13 |
14 |
15 | ### v1.3.2
16 | - `pm getpath` Question fix
17 | - prompts updated to v2.3.0
18 | - Unit testing for helper added ([@junaidrahim](https://github.com/junaidrahim) - [#27](https://github.com/saurabhdaware/projectman/pull/27))
19 |
20 |
21 | ### v1.3.1
22 |
23 | Documentation Update
24 |
25 |
26 |
27 | ### v1.3.0
28 | ---
29 | ```shell
30 | npm install -g projectman
31 | ```
32 | **Release Date:** 20th Sept, 2019
33 |
34 | ---
35 |
36 | - ## Brought dependencies from 36 packages to 4 packages!! 🎉
37 | Some internal code refactoring and asking myself "Do I really need this package?" helped me bring down dependency tree of 37 packages to 4 packages!!!
38 | - ## AutoComplete added during project selection.
39 | In `pm open` and other project selection menus. You can now start typing the letters and list will be filtered out to show projects matching the letters.
40 | - ## Ability to open/add URLs (Thank You [@ZakariaTalhami](https://github.com/ZakariaTalhami) for PR [#20](https://github.com/saurabhdaware/projectman/pull/20) )
41 | - `pm add --url [URL]` to add URLs to the projectman. `[URL]` is an optional parameter
42 | - These URLs will show up in `pm open` with (URL) appended to their names.
43 | - On selecting the URL in `pm open` they will be opened in your default browser.
44 | - This can be used to store your repositories/websites/other useful links.
45 |
46 | ---
47 | ---
48 | ### v1.2.0
49 | ```shell
50 | npm install -g projectman@1.2.0
51 | ```
52 | **Release Date:** 12th Sept, 2019
53 |
54 | ---
55 | - [Presenting ProjectMan Binaries](#presenting-projectman-binaries)
56 | - [`cd` to directory without opening the project](#cd-to-directory-without-opening-the-project)
57 | - [Added `--for-project` flag in `pm seteditor`](#added-for-project-flag-in-pm-seteditor)
58 | - [`Other` option addbed in `pm seteditor`](#other-option-added-in-pm-seteditor)
59 | - [New command `pm rmeditor`](#new-command-pm-rmeditor)
60 | ---
61 |
62 | #### > Presenting ProjectMan binaries 🎉🦸
63 | Why should Node developers have all the fun? Now use ProjectMan without NodeJS or NPM installed.
64 |
65 | Download binaries and follow installation instructions given with them :
66 |
67 | [](https://apps.saurabhdaware.in/projectman#windows) [](https://apps.saurabhdaware.in/projectman/#linux-and-mac) [](https://apps.saurabhdaware.in/projectman/#linux-and-mac)
68 |
69 | #### > `cd` to directory without opening the project.
70 | ```shell
71 | cd $(pm getpath)
72 | ```
73 | This will allow users to jump to a directory in command line without opening the project.
74 |
75 | **PR :** [#9](https://github.com/saurabhdaware/projectman/pull/9) (Thank You [@ZakariaTalhami](https://github.com/ZakariaTalhami))
76 | **Issue :** [#5](https://github.com/saurabhdaware/projectman/issues/5) (Thank you [@feitzi](https://github.com/feitzi))
77 |
78 | #### > Added `--for-project` flag in `pm seteditor`:
79 | ```shell
80 | pm seteditor --for-project
81 | ```
82 | This will allow users to set different editor for a specific project
83 | E.g. Set Atom for `Project1` and have VSCode for other projects
84 | **Isssue :** [#13](https://github.com/saurabhdaware/projectman/issues/13)
85 | **PR :** [#16](https://github.com/saurabhdaware/projectman/pull/16)
86 |
87 | #### > `Other` option added in `pm seteditor` :
88 | You can now select `other` option and type the editorCommand as an input rather than typing `pm seteditor [editorCommand]`
89 |
90 | #### > New command `pm rmeditor`:
91 | ```shell
92 | pm rmeditor
93 | ```
94 | This will allow users to remove the project specific editors.
95 | You can either `pm rmeditor` and choose the project to remove editor or `pm rmeditor --all` to remove all project specific editors.
96 |
97 | ---
98 |
99 | ## v1.1.0
100 | ```shell
101 | npm install -g projectman@1.1.0
102 | ```
103 | - ***Project specific editors*** (Thanks [#4](https://github.com/saurabhdaware/projectman/issues/4) [@fechy](https://github.com/fechy) for issue)
104 | - Now you can `pm edit` and set `editor` key in `settings.json` projects[] with the value of the command of your editor.
105 | - `settings.json` E.g.
106 | ```js
107 | {
108 | "commandToOpen": "code",
109 | "projects": [
110 | {
111 | "name":"MyCoolProject",
112 | "path":"/home/path/projects/mycoolproject",
113 | "editor":"vim"
114 | },
115 | {
116 | "name":"TwoProject",
117 | "path":"/path/something/project"
118 | }
119 | ]
120 | }
121 | ```
122 | This will allow users to open other projects in **VSCode** but use **Vim** to open `MyCoolProject`
123 |
124 | - ***Projects will not be erased after updating furthur***
125 | (However they will still be erased while installing this update so I'll recommend to take copy of your settings.json If you have added multiple projects already) (Sorry but this is the last time when you'll have to do this :cry: ))
126 | (Thank you [@codyaverett](https://github.com/codyaverett) and [@Tanuj69](https://github.com/Tanuj69) issue [#2](https://github.com/saurabhdaware/projectman/issues/2) and helping me out solving this)
127 | - ***Added `vim` in `pm seteditor`***
128 | also added a message explaining 'How to set editors/IDE that are not listed in the menu'
129 | - `pm` is now alias for `pm open`
130 | (Thank you [@johannesjo](https://github.com/johannesjo) for suggestion)
131 | - **`pm seteditor [commandToOpenEditor]` added**
132 | This will set default editor command, This can be used when the editor you want to use is not listed in `pm seteditor`. (Note: `pm seteditor` will work exactly same as it did before)
133 |
134 | ---
135 |
136 | ## v1.0.0
137 | ```shell
138 | npm install -g projectman@1.0.0
139 | ```
140 |
141 | Initial release of ProjectMan.
142 |
143 | Includes following commands:
144 |
145 | ```shell
146 | pm add
147 | pm remove
148 | pm open
149 | pm seteditor
150 | ```
151 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Setting up local development
2 | - [Fork the project](https://github.com/saurabhdaware/projectman/fork) using fork button in the top right corner.
3 | - Clone the project to your device using `git clone https://github.com/{yourUsername}/projectman.git`
4 | - `npm install` to install the required dependencies
5 | - Type `npm link` to install the content in your global directory. (Your directory will be linked to your global node folder which means if you change anything in the directory it will change inside the global directory as well)
6 | - Type `projectman open` to test your code.
7 |
8 |
9 | ## Directory Structure
10 | There are two important folders that you should care about `lib` and `bin`
11 | ```
12 | - lib
13 | -> action.js // Contains all the main logic and functions
14 | - bin
15 | -> index.js // Main file, This file is triggered when `projectman` or `pm` is called
16 | ```
17 |
18 | ## Contribution Guidelines
19 | - If you're planning to implement a new feature I will recommend you to discuss with me before you start coding so you won't end up working on something that I don't want to implement. Create an Issue with proper name and content for discussion.
20 | - If you need any help understanding the code you can reach out to me on twitter/[@saurabhcodes](https://twitter.com/saurabhcodes)
21 |
22 | - For Contributing to this project or any project on GitHub
23 | 1. Fork project.
24 | 2. Create a branch with the name of feature that you're working on (e.g. `multiple-editors`).
25 | 3. Once you're done coding create a merge request from your new branch to my `develop`. (Read the Local Development section above for local setup guidelines)
26 |
27 |
28 | ## Coding Guidelines
29 | - Please write comments wherever necessary.
30 | - Write unit tests wherever possible.
31 | - If you create a new file that exports various functions, put it inside `lib` folder
32 | - Please write proper commit messages explaining your changess.
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Saurabh Daware
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProjectMan🦸
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ProjectMan is a CLI which lets you add projects to favorites using command `pm add` and open them from anywhere you want using command `pm open`.
22 |
23 | Along with this there are also other commands like `pm seteditor`, `pm remove`, `cd $(pm getpath)` mentioned in documentation below.
24 |
25 | ---
26 | 
27 |
28 | ---
29 |
30 | ## Installation
31 |
32 | With [NodeJS](https://nodejs.org) installed in your machine,
33 | ```shell
34 | npm install -g projectman
35 | ```
36 |
37 | ---
38 |
39 | ## Commands
40 | `pm` is an alias of `projectman` so you can use `pm ` or `projectman `
41 |
42 | ### 📖 Open Project
43 |
44 | Opens project in your code editor (Check out [`pm seteditor`](#set-editor) command to set your preferred editor)
45 |
46 | **Usage :**
47 | ```shell
48 | pm open [projectName]
49 | ```
50 | `[projectName]` is an optional parameter.
51 |
52 | **Alias:** `pm o`, `pm`
53 |
54 | ### ➕ Add Project or Template
55 |
56 | Add project to favorites
57 |
58 | ```shell
59 | cd /till/the/project
60 | pm add
61 | ```
62 |
63 | *You can pass `--url` param to add a URL instead of a directory.*
64 |
65 | ### 🪄 Create Project
66 |
67 | Use added projects as a template to create new project
68 |
69 | ```shell
70 | pm create
71 | ```
72 |
73 | **Alias:** `pm c`
74 |
75 | ### 💫 Set Editor
76 |
77 | ```shell
78 | pm seteditor
79 | ```
80 | Sets default editor to open projects from.
81 |
82 | To set a different editor for a specific project,
83 |
84 | **Flag:** `--for-project`
85 | ```shell
86 | pm seteditor --for-project
87 | ```
88 | Sets different editor for a specific project.
89 | E.g You can use VSCode for other projects and Atom for `CoolProject1`
90 |
91 | If your TextEditor/IDE is not listed, You can select option `Other` from the list and give your `editorCommand`.
92 | Read [editorCommand ref](#settings-ref) for more information.
93 |
94 |
95 | ### 🏃🏽 `cd` to a project without opening.
96 | ```shell
97 | cd $(pm getpath [projectName])
98 | ```
99 |
100 | `[projectName]` is an optional parameter.
101 |
102 | **Alias :** `cd $(pm gp)`
103 |
104 | (Note: This does not work in Windows cmd, You can use it in Windows Powershell)
105 |
106 | ### ❌ Remove Project
107 | ```shell
108 | pm remove
109 | ```
110 | Removes project from favorites.
111 |
112 | ### ➖ Remove editor
113 | ```shell
114 | pm rmeditor
115 | ```
116 | Shows list of project and removes the project specific editor from the project.
117 |
118 | ```shell
119 | pm rmeditor --all
120 | ```
121 | removes all project specific editors.
122 |
123 | ---
124 |
125 | ## ⚙️ Settings.json
126 |
127 | If you want to sort projects/change name of project/change path, You can type `pm edit` to open settings.json
128 |
129 |
130 | #### Example settings:
131 | ```json
132 | {
133 | "commandToOpen": "code",
134 | "projects": [
135 | {
136 | "name": "Project1",
137 | "path": "path/to/project1"
138 | },
139 | {
140 | "name": "Project2",
141 | "path": "path/to/project2",
142 | "editor": "atom"
143 | },
144 | {
145 | "name": "Project3",
146 | "path": "path/to/project3"
147 | }
148 | ]
149 | }
150 | ```
151 | This will show three projects in `pm open` and project2 will be opened in Atom and other projects will be opened in Visual Studio Code
152 |
153 |
154 | #### Settings Ref:
155 |
156 | **> commandToOpen** :
157 | - This is your editor's command, this command will be used to open the file in your editor.
158 | - Default is `code` which opens in vscode.
159 | - This is the command that you normally use to open directories in your editor.
160 |
161 | | Editor |'commandToOpen' value|
162 | |---------------|-----------|
163 | | **VSCode** | code |
164 | | **Atom** | atom |
165 | | **Sublime** | subl |
166 | | **Vim** | vim |
167 | | **WebStorm** | wstorm |
168 |
169 | **> projects.`name` :**
170 | - This is the name that will be visible when you type `projectman open`
171 |
172 | **> projects.`path` :**
173 | - This should be the absolute path to your folder.
174 |
175 | **> projects.`editor` :**
176 | - This is optional key. In case it doesn't exist it will read value from `commandToOpen`
177 | - You can use this to specify separate editor for a particular project.
178 | - You can set it by adding `"editor": ""` in projects array in settings.json ([Example](#settingsjson) is shown above)
179 |
180 | ---
181 |
182 | ## ChangeLogs
183 |
184 | ### v2.0.0 *`@latest`*
185 |
186 | - Add `pm create` command
187 | - Remove support for binaries
188 |
189 |
190 | ***For More Changes read [CHANGELOG.md](CHANGELOG.md)***
191 |
192 | ---
193 | ## Rust Port
194 | [@hskang9](https://github.com/hskang9) has made a pretty cool rust port for projectman. You can check it out at: https://github.com/hskang9/projectman-rust
195 |
196 | ---
197 | ## Contributing to ProjectMan
198 | [](https://github.com/saurabhdaware/projectman/issues)
199 |
200 | I would be extremely happy to have people contribute to ProjectMan. You can read Contribution guidelines in **[CONTRIBUTING.md](CONTRIBUTING.md)**
201 |
202 | ---
203 |
204 | **Thank you for showing Interest! Do contribute and star [ProjectMan🦸 on GitHub](https://github.com/saurabhdaware/projectman)**
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | title: ProjectMan🦸
2 | logo: /images/logo-192.png
3 | description: Hate 'cd'ing to multiple folders to open a certain project? Use ProjectMan to add these projects and open them from command line using single command 'pm open'
4 | theme: jekyll-theme-cayman
5 |
--------------------------------------------------------------------------------
/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const program = require('commander');
3 | const { suggestCommands } = require('../lib/helper');
4 | const actions = require('../lib/actions');
5 |
6 | program.version(require('../package.json').version);
7 |
8 | // Commands
9 | program
10 | .command('open [projectName]')
11 | .alias('o')
12 | .description('Open one of your saved projects')
13 | .action(actions.openProject);
14 |
15 | program
16 | .command('create [projectName]')
17 | .alias('c')
18 | .description('Create project')
19 | .action(actions.createProject);
20 |
21 | program
22 | .command('add [projectDirectory]')
23 | .alias('save')
24 | .option('-u, --url [link]', 'Add a link to a repository to projects')
25 | .description('Save current directory as a project')
26 | .action(actions.addProject);
27 |
28 | program
29 | .command('remove [projectName]')
30 | .description('Remove the project')
31 | .action(actions.removeProject);
32 |
33 | program
34 | .command('seteditor [commandToOpen]')
35 | .description('Set text editor to use')
36 | .option(
37 | '-f|--for-project [projectName]',
38 | 'set different editor for specific project'
39 | )
40 | .action(actions.setEditor);
41 |
42 | program
43 | .command('rmeditor [projectName]')
44 | .description('Remove text editor to use')
45 | .option('-a|--all', 'remove editors from all projects')
46 | .action(actions.rmEditor);
47 |
48 | program
49 | .command('edit')
50 | .description('Edit settings.json')
51 | .action(actions.editConfigurations);
52 |
53 | program
54 | .command('getpath [projectName]')
55 | .alias('gp')
56 | .description('Get project path')
57 | .action(actions.getProjectPath);
58 |
59 | program.arguments('').action((command) => {
60 | console.log(`Command ${command} not found\n`);
61 | program.outputHelp();
62 | suggestCommands(command);
63 | });
64 |
65 | program.usage('');
66 |
67 | if (process.argv.length <= 2) {
68 | // If no command mentioned then output help
69 | actions.openProject();
70 | }
71 |
72 | // Parse arguments
73 | program.parse(process.argv);
74 |
--------------------------------------------------------------------------------
/images/logo-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saurabhdaware/projectman/81eb4d206d0822adcf3562d76f00055e60239c9b/images/logo-192.png
--------------------------------------------------------------------------------
/images/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saurabhdaware/projectman/81eb4d206d0822adcf3562d76f00055e60239c9b/images/logo-512.png
--------------------------------------------------------------------------------
/images/terminal.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saurabhdaware/projectman/81eb4d206d0822adcf3562d76f00055e60239c9b/images/terminal.gif
--------------------------------------------------------------------------------
/lib/actions.js:
--------------------------------------------------------------------------------
1 | // external dependencies
2 |
3 | const openProject = require('./commands/open');
4 | const addProject = require('./commands/add');
5 | const removeProject = require('./commands/remove');
6 | const editConfigurations = require('./commands/edit');
7 | const setEditor = require('./commands/seteditor');
8 | const rmEditor = require('./commands/rmeditor');
9 | const getProjectPath = require('./commands/getpath');
10 | const createProject = require('./commands/create');
11 |
12 | module.exports = {
13 | openProject,
14 | addProject,
15 | removeProject,
16 | editConfigurations,
17 | setEditor,
18 | rmEditor,
19 | getProjectPath,
20 | createProject
21 | };
22 |
--------------------------------------------------------------------------------
/lib/colors.js:
--------------------------------------------------------------------------------
1 | // Just some weird strings that color text inside it. You probably will not have to touch this file.
2 |
3 | exports.green = (message) => `\u001b[32m${message}\u001b[39m`;
4 |
5 | exports.boldGreen = (message) =>
6 | `\u001b[1m\u001b[32m${message}\u001b[39m\u001b[22m`;
7 |
8 | exports.boldRed = (message) =>
9 | `\u001b[1m\u001b[31m${message}\u001b[39m\u001b[22m`;
10 |
11 | exports.yellow = (message) => `\u001b[33m${message}\u001b[39m`;
12 |
13 | exports.boldYellow = (message) =>
14 | `\u001b[1m\u001b[33m${message}\u001b[39m\u001b[22m`;
15 |
16 | exports.grey = (message) => `\u001b[90m${message}\u001b[39m`;
17 |
18 | exports.boldGrey = (message) =>
19 | `\u001b[1m\u001b[90m${message}\u001b[39m\u001b[22m`;
20 |
21 | exports.bold = (message) => `\u001b[1m${message}\u001b[22m`;
22 |
23 | exports.boldBlue = (message) => this.bold(`\u001b[34m${message}\u001b[39m`);
24 |
--------------------------------------------------------------------------------
/lib/commands/add.js:
--------------------------------------------------------------------------------
1 | // external dependencies
2 | const prompts = require('prompts');
3 |
4 | // internal modules
5 | const path = require('path');
6 |
7 | // helper functions
8 | const color = require('../colors.js');
9 | const logs = require('../logs.js');
10 |
11 | const { getSettings, writeSettings, isURL, onCancel } = require('../helper.js');
12 |
13 | /*
14 |
15 | COMMAND: pm add [projectDirectory] [--url [link]]
16 | ARGUMENTS:
17 | [projectDirectory] :: Directory of the project that you want to add. OR when --url flag is added, URL of the repository.
18 |
19 | FLAG:
20 | [--url [link]] :: Adds URL.
21 |
22 | RETURNS: newProject object.
23 |
24 | */
25 | async function addProject(projectDirectory = '.', cmdObj = undefined) {
26 | const settings = getSettings();
27 | const newProject = {};
28 | let name;
29 | let enteredUrl;
30 |
31 | if (cmdObj.url) {
32 | if (projectDirectory !== '.') {
33 | console.warn(
34 | "Project's local directory value will be ignore when --url flag is on"
35 | );
36 | }
37 |
38 | if (cmdObj.url == true) {
39 | ({ enteredUrl } = await prompts(
40 | [
41 | {
42 | type: 'text',
43 | message: 'Project URL :',
44 | name: 'enteredUrl',
45 | initial: 'https://github.com/',
46 | validate: (url) => (isURL(url) ? true : 'Not a valid URL')
47 | }
48 | ],
49 | { onCancel }
50 | ));
51 | name = enteredUrl.split('/').pop(); // Get last route of URL to set default name
52 | newProject.path = enteredUrl;
53 | } else {
54 | if (!isURL(cmdObj.url)) {
55 | logs.error('Not a valid URL');
56 | console.warn(
57 | 'A valid URL looks something like ' +
58 | color.yellow('https://github.com/saurabhdaware/projectman')
59 | );
60 | return;
61 | }
62 | name = cmdObj.url.split('/').pop(); // Get last route of URL to set default name
63 | newProject.path = cmdObj.url;
64 | }
65 | } else {
66 | newProject.path = path.resolve(projectDirectory);
67 | name = newProject.path.split(path.sep).pop();
68 | }
69 |
70 | ({ finalName: newProject.name } = await prompts(
71 | [
72 | {
73 | type: 'text',
74 | message: 'Project Name :',
75 | name: 'finalName',
76 | initial: name
77 | }
78 | ],
79 | { onCancel }
80 | ));
81 |
82 | if (
83 | settings.projects.some(
84 | (project) => project.name.toLowerCase() == newProject.name.toLowerCase()
85 | )
86 | ) {
87 | logs.error('Project with this name already exists');
88 | return;
89 | }
90 |
91 | settings.projects.push(newProject);
92 |
93 | writeSettings(settings, 'add', 'Project Added');
94 |
95 | return newProject;
96 | }
97 |
98 | module.exports = addProject;
99 |
--------------------------------------------------------------------------------
/lib/commands/create.js:
--------------------------------------------------------------------------------
1 | // external dependencies
2 | const prompts = require('prompts');
3 | const gitIgnoreParser = require('gitignore-parser');
4 |
5 | // internal modules
6 | const fs = require('fs');
7 | const path = require('path');
8 | const spawn = require('child_process').spawn;
9 |
10 | // helper functions
11 | const color = require('../colors.js');
12 | const logs = require('../logs.js');
13 |
14 | const {
15 | onCancel,
16 | selectProject,
17 | copyFolderSync,
18 | getSettings,
19 | isURL,
20 | logger
21 | } = require('../helper.js');
22 |
23 | // pm create [projectName]
24 | async function createProject(projectName) {
25 | const settings = getSettings();
26 | if (settings.projects.length == 0) {
27 | logs.error('No projects to open :(');
28 | console.warn(
29 | `cd /till/project/directory/ and run ${color.boldYellow(
30 | 'pm add'
31 | )} to add projects and get started`
32 | );
33 | return;
34 | }
35 |
36 | const selectedTemplate = await selectProject(
37 | undefined,
38 | 'Select starting template'
39 | );
40 |
41 | if (!selectedTemplate) {
42 | logs.error(
43 | `Project does not exist. Add it using ${color.yellow(
44 | 'pm add [projectPath]'
45 | )} or cd to the project folder and type ${color.yellow('pm add')}`
46 | );
47 | return;
48 | }
49 |
50 | if (!projectName) {
51 | ({ projectName } = await prompts(
52 | [
53 | {
54 | type: 'text',
55 | message: 'Project Name:',
56 | name: 'projectName',
57 | initial: '',
58 | validate: (val) => {
59 | if (!val) {
60 | return false;
61 | }
62 |
63 | return true;
64 | }
65 | }
66 | ],
67 | { onCancel }
68 | ));
69 | }
70 |
71 | const newProjectDirectoryName = projectName.toLowerCase().replace(/ /g, '-');
72 | const newProjectDirectory = path.join(process.cwd(), newProjectDirectoryName);
73 |
74 | if (isURL(selectedTemplate.path)) {
75 | if (!selectedTemplate.path.includes('github.com')) {
76 | console.error(
77 | 'Currently only github.com URLs can be used to scaffold project'
78 | );
79 | return;
80 | }
81 |
82 | const childProcess = spawn(
83 | 'git',
84 | ['clone', selectedTemplate.path, newProjectDirectory],
85 | {
86 | stdio: 'inherit'
87 | }
88 | );
89 |
90 | childProcess.on('exit', (code) => {
91 | if (code === 0) {
92 | console.log('');
93 | logger.success(
94 | // eslint-disable-next-line max-len
95 | `${newProjectDirectoryName} is successfully scaffolded from ${selectedTemplate.path}`
96 | );
97 | } else {
98 | console.log('');
99 | logger.error(
100 | // eslint-disable-next-line max-len
101 | `Could not scaffold project. Git clone exitted with 1. Check error above`
102 | );
103 | }
104 | });
105 |
106 | return;
107 | }
108 |
109 | let gitignoreContent = '.git/\n';
110 | const gitignorePath = path.join(selectedTemplate.path, '.gitignore');
111 |
112 | if (fs.existsSync(gitignorePath)) {
113 | gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
114 | }
115 |
116 | const gitignore = gitIgnoreParser.compile(gitignoreContent);
117 | copyFolderSync(
118 | selectedTemplate.path,
119 | newProjectDirectory,
120 | gitignore,
121 | true,
122 | selectedTemplate.path
123 | );
124 | }
125 |
126 | module.exports = createProject;
127 |
--------------------------------------------------------------------------------
/lib/commands/edit.js:
--------------------------------------------------------------------------------
1 | const util = require('util');
2 | const exec = util.promisify(require('child_process').exec);
3 |
4 | // helper functions
5 | const color = require('../colors.js');
6 | const logs = require('../logs.js');
7 |
8 | const { SETTINGS_PATH, throwCreateIssueError } = require('../helper.js');
9 |
10 | // projectman edit
11 | async function editConfigurations() {
12 | let openSettingsCommand;
13 |
14 | if (process.platform == 'win32') {
15 | openSettingsCommand = 'Notepad ';
16 | } else if (process.platform == 'linux') {
17 | openSettingsCommand = 'xdg-open ';
18 | } else {
19 | openSettingsCommand = 'open -t ';
20 | }
21 |
22 | try {
23 | const { stderr } = await exec(`${openSettingsCommand} "${SETTINGS_PATH}"`);
24 | if (stderr) {
25 | logs.error('Error occured while opening the file: ' + SETTINGS_PATH);
26 | console.log('You can follow above path and manually edit settings.json');
27 | throwCreateIssueError(stderr);
28 | return;
29 | }
30 |
31 | console.log(
32 | color.boldGreen('>>> ') + 'Opening settings.json' + color.green(' ✔')
33 | );
34 | } catch (err) {
35 | logs.error('Error occured while opening the file: ' + SETTINGS_PATH);
36 | console.warn('You can follow above path and manually edit settings.json');
37 | throwCreateIssueError(err);
38 | }
39 | }
40 |
41 | module.exports = editConfigurations;
42 |
--------------------------------------------------------------------------------
/lib/commands/getpath.js:
--------------------------------------------------------------------------------
1 | // external dependencies
2 | const prompts = require('prompts');
3 |
4 | // helper functions
5 | const color = require('../colors.js');
6 | const logs = require('../logs.js');
7 |
8 | const { onCancel, getChoices, getSettings } = require('../helper.js');
9 |
10 | async function getProjectPath(projectName) {
11 | const settings = getSettings();
12 | let selectedProject;
13 | if (settings.projects.length === 0) {
14 | logs.error('No projects to get path :(');
15 | console.warn(
16 | `cd /till/project/directory/ and run ${color.boldYellow(
17 | 'pm add'
18 | )} to add projects and get started`
19 | );
20 | return;
21 | }
22 |
23 | if (!projectName) {
24 | const question = {
25 | name: 'selectedProject',
26 | message: 'Select project you want to cd to:',
27 | type: 'autocomplete',
28 | stdout: process.stderr,
29 | choices: getChoices(),
30 | onRender: () => {
31 | process.stderr.write('\033c');
32 | }
33 | };
34 |
35 | ({ selectedProject } = await prompts([question], { onCancel }));
36 | if (!selectedProject) {
37 | logs.error(
38 | `Project does not exist. Add it using ${color.yellow(
39 | 'pm add [projectPath]'
40 | )} or cd till the project folder and type ${color.yellow('pm add')}`
41 | );
42 | return;
43 | }
44 | } else {
45 | selectedProject = settings.projects.find(
46 | (project) => project.name.toLowerCase() == projectName.toLowerCase()
47 | );
48 | }
49 |
50 | // Print path
51 | console.log(selectedProject.path);
52 | }
53 |
54 | module.exports = getProjectPath;
55 |
--------------------------------------------------------------------------------
/lib/commands/open.js:
--------------------------------------------------------------------------------
1 | // internal modules
2 | const util = require('util');
3 | const exec = util.promisify(require('child_process').exec);
4 |
5 | // helper functions
6 | const color = require('../colors.js');
7 | const logs = require('../logs.js');
8 |
9 | const {
10 | getSettings,
11 | throwCreateIssueError,
12 | openURL,
13 | isURL,
14 | selectProject
15 | } = require('../helper.js');
16 |
17 | /**
18 | * COMMAND: pm open [projectName]
19 | *
20 | * ARGUMENTS: [projectName] :: Name of the project to open
21 | *
22 | * @param {string} projectName - Name of the project to open
23 | */
24 | async function openProject(projectName) {
25 | const settings = getSettings();
26 | if (settings.projects.length == 0) {
27 | logs.noProjectsToOpen();
28 | return;
29 | }
30 |
31 | console.log(
32 | color.boldGrey('>>> Default editor: ') + color.grey(settings.commandToOpen)
33 | );
34 | const selectedProject = await selectProject(
35 | projectName,
36 | 'Select project to open'
37 | );
38 |
39 | if (!selectedProject) {
40 | logs.projectDoesNotExist();
41 | return;
42 | }
43 |
44 | const commandToOpen = selectedProject.editor || settings.commandToOpen; // If project specific editor exists, Then open using that command else use global command
45 | let stderr;
46 | try {
47 | if (isURL(selectedProject.path)) {
48 | ({ stderr } = await openURL(selectedProject.path)); // If it is URL then open in Browser.
49 | } else {
50 | ({ stderr } = await exec(`${commandToOpen} "${selectedProject.path}"`)); // This line opens projects
51 | }
52 |
53 | if (stderr) {
54 | logs.error('Could not open project for some reason :(');
55 | throwCreateIssueError(stderr);
56 | return;
57 | }
58 |
59 | console.log(
60 | `${color.boldGreen('>>>')} Opening ${selectedProject.name} ${color.green(
61 | '✔'
62 | )}`
63 | ); // Success yay!
64 | } catch (err) {
65 | logs.error('Could not open project :('); // Something broke :(
66 | console.warn(
67 | `Are you sure your editor uses command ${color.yellow(
68 | commandToOpen
69 | )} to open directories from terminal?`
70 | );
71 | console.warn(
72 | `If not, use ${color.yellow(
73 | 'pm seteditor'
74 | )} to set Editor/IDE of your choice`
75 | );
76 | throwCreateIssueError(err);
77 | return;
78 | }
79 | }
80 |
81 | module.exports = openProject;
82 |
--------------------------------------------------------------------------------
/lib/commands/remove.js:
--------------------------------------------------------------------------------
1 | // helper functions
2 | const color = require('../colors.js');
3 | const logs = require('../logs.js');
4 |
5 | const { getSettings, writeSettings, selectProject } = require('../helper.js');
6 |
7 | // pm remove [projectName]
8 | async function removeProject(projectName) {
9 | const settings = getSettings();
10 |
11 | const { name: selectedProjectName } = await selectProject(
12 | projectName,
13 | 'Select project to remove'
14 | );
15 |
16 | if (!selectedProjectName) {
17 | logs.error(`Project with name ${selectedProjectName} does not exist.`);
18 | console.log(
19 | `Try ${color.yellow(
20 | 'pm remove'
21 | )} and select the project you want to remove`
22 | );
23 | return;
24 | }
25 | // removing project
26 | settings.projects = settings.projects.filter(
27 | (project) =>
28 | project.name.toLowerCase() !== selectedProjectName.toLowerCase()
29 | );
30 |
31 | writeSettings(settings, 'remove', 'Project Removed');
32 | }
33 |
34 | module.exports = removeProject;
35 |
--------------------------------------------------------------------------------
/lib/commands/rmeditor.js:
--------------------------------------------------------------------------------
1 | const color = require('../colors.js');
2 | const logs = require('../logs.js');
3 |
4 | const { getSettings, writeSettings, selectProject } = require('../helper.js');
5 |
6 | async function rmEditor(projectName, cmdObj) {
7 | const settings = getSettings();
8 |
9 | let newSettings;
10 | if (cmdObj.all) {
11 | newSettings = {
12 | ...settings,
13 | projects: settings.projects.map(({ ...project }) => {
14 | if (project.editor) delete project.editor;
15 | return project;
16 | })
17 | };
18 | } else {
19 | console.log(
20 | color.boldGrey('>>> Default editor: ') +
21 | color.grey(settings.commandToOpen)
22 | );
23 | const selectedProject = await selectProject(
24 | projectName,
25 | 'Select project to remove editor from'
26 | );
27 | if (!selectedProject) {
28 | logs.error(`Project with name ${selectedProject} does not exist.`);
29 | console.log(
30 | `Try ${color.yellow(
31 | 'pm rmeditor'
32 | )} and select the project you want to remove the editor from`
33 | );
34 | return;
35 | }
36 |
37 | const selectedIndex = settings.projects.findIndex(
38 | (project) =>
39 | selectedProject.name.toLowerCase() == project.name.toLowerCase()
40 | );
41 | delete settings.projects[selectedIndex].editor;
42 | newSettings = { ...settings };
43 | }
44 |
45 | writeSettings(newSettings, 'rmeditor', `TextEditor Removed`);
46 | }
47 |
48 | module.exports = rmEditor;
49 |
--------------------------------------------------------------------------------
/lib/commands/seteditor.js:
--------------------------------------------------------------------------------
1 | // external dependencies
2 | const prompts = require('prompts');
3 |
4 | // helper functions
5 | const color = require('../colors.js');
6 |
7 | const {
8 | getSettings,
9 | writeSettings,
10 | isURL,
11 | onCancel,
12 | selectProject
13 | } = require('../helper.js');
14 |
15 | const questions = [
16 | {
17 | type: 'select',
18 | message: 'Select Project Open Command',
19 | name: 'selectedEditor',
20 | choices: [
21 | {
22 | title: 'code',
23 | value: 'code'
24 | },
25 | {
26 | title: 'cursor',
27 | value: 'cursor'
28 | },
29 | {
30 | title: 'subl',
31 | value: 'subl'
32 | },
33 | {
34 | title: 'atom',
35 | value: 'atom'
36 | },
37 | {
38 | title: 'vim',
39 | value: 'vim'
40 | },
41 | {
42 | title: 'Other',
43 | value: 'other'
44 | }
45 | ]
46 | }
47 | ];
48 |
49 | // pm seteditor [command]
50 | async function setEditor(command, cmdObj = undefined) {
51 | let commandToOpen;
52 | let selectedProject;
53 | let selectedIndex = -1; // this will have the index of selectedProject
54 | let projectName;
55 | const settings = getSettings();
56 |
57 | const setEditorFilter = (project) => !isURL(project.path);
58 |
59 | if (cmdObj.forProject) {
60 | // --for-project exists
61 | projectName = cmdObj.forProject === true ? undefined : cmdObj.forProject; // if only[--for-project], set undefined else if [--for-project [projectName]] set projectName
62 | selectedProject = await selectProject(
63 | projectName,
64 | 'Select project to set editor for',
65 | setEditorFilter
66 | ); // sending undefined, calls list of projects to select from
67 | // find the index of selectedProject and add editor to that project
68 | selectedIndex = settings.projects.findIndex(
69 | (project) =>
70 | project.name.toLowerCase() == selectedProject.name.toLowerCase()
71 | );
72 | }
73 |
74 | if (!command) {
75 | const { selectedEditor } = await prompts(questions, { onCancel });
76 | if (selectedEditor == 'other') {
77 | console.warn('Enter command that you use to open Editor from Terminal');
78 | console.log(
79 | `E.g With VSCode Installed, you can type ${color.yellow(
80 | 'code '
81 | )} in terminal to open directory`
82 | );
83 | console.log(
84 | `In this case, the command would be ${color.yellow('code')}\n`
85 | );
86 | const question = {
87 | type: 'text',
88 | message: 'Enter command :',
89 | name: 'command',
90 | validate: function (val) {
91 | return val !== '';
92 | }
93 | };
94 | const { command } = await prompts([question], { onCancel });
95 | commandToOpen = command;
96 | } else {
97 | commandToOpen = selectedEditor;
98 | }
99 | } else {
100 | commandToOpen = command;
101 | }
102 |
103 | if (selectedIndex < 0) {
104 | settings.commandToOpen = commandToOpen;
105 | } else {
106 | settings.projects[selectedIndex].editor = commandToOpen;
107 | }
108 |
109 | writeSettings(
110 | settings,
111 | 'seteditor',
112 | `Text Editor Selected ${
113 | selectedIndex < 0 ? '' : `for ${settings.projects[selectedIndex].name}`
114 | }`
115 | );
116 | }
117 |
118 | module.exports = setEditor;
119 |
--------------------------------------------------------------------------------
/lib/helper.js:
--------------------------------------------------------------------------------
1 | // external dependencies
2 | const prompts = require('prompts');
3 | const program = require('commander');
4 | const didYouMean = require('didyoumean');
5 |
6 | // internal modules
7 | const path = require('path');
8 | const fs = require('fs');
9 | const util = require('util');
10 | const exec = util.promisify(require('child_process').exec);
11 | const os = require('os');
12 |
13 | // helper functions
14 | const color = require('./colors.js');
15 |
16 | // Default settings.
17 | const DEFAULT_SETTINGS = {
18 | commandToOpen:
19 | process.platform === 'darwin'
20 | ? 'open -a "/Applications/Visual Studio Code.app"'
21 | : 'code',
22 | projects: []
23 | };
24 |
25 | // Create settings.json if does not exist or just require it if it does exist
26 | const SETTINGS_DIR =
27 | process.env.NODE_ENV === 'test'
28 | ? path.join(__dirname, '..', 'tests', 'dotprojectman')
29 | : path.join(os.homedir(), '.projectman');
30 |
31 | const SETTINGS_PATH = path.join(SETTINGS_DIR, 'settings.json');
32 |
33 | /**
34 | * Returns settings if already exists, else creates default settings and returns it
35 | * @returns {import('../types/utils').SettingsType}
36 | */
37 | const getSettings = () => {
38 | let settings;
39 | try {
40 | settings = require(SETTINGS_PATH);
41 | } catch (err) {
42 | if (err.code === 'MODULE_NOT_FOUND') {
43 | // Create if doesn't exist
44 | if (!fs.existsSync(SETTINGS_DIR)) {
45 | fs.mkdirSync(SETTINGS_DIR);
46 | }
47 | fs.writeFileSync(
48 | SETTINGS_PATH,
49 | JSON.stringify(DEFAULT_SETTINGS, null, 4),
50 | 'utf8'
51 | );
52 | settings = DEFAULT_SETTINGS;
53 | }
54 | }
55 |
56 | return settings;
57 | };
58 | //
59 |
60 | const settings = getSettings();
61 |
62 | const logger = {
63 | error: (message) => console.log(color.boldRed('>>> ') + message),
64 | warn: (message) => console.log(color.boldYellow('>>> ') + message),
65 | success: (message) => console.log(color.boldGreen('>>> ') + message + ' ✔')
66 | };
67 |
68 | function throwCreateIssueError(err) {
69 | logger.error(
70 | // eslint-disable-next-line max-len
71 | 'If you think it is my fault please create issue at https://github.com/saurabhdaware/projectman with below log'
72 | );
73 | console.log(color.boldRed('Err:'));
74 | console.log(err);
75 | }
76 |
77 | // Takes data as input and writes that data to settings.json
78 | function writeSettings(
79 | data,
80 | command = '',
81 | successMessage = 'Settings updated :D'
82 | ) {
83 | fs.writeFile(SETTINGS_PATH, JSON.stringify(data, null, 2), (err) => {
84 | if (err) {
85 | if (err.code === 'EACCES') {
86 | const errCmd =
87 | process.platform == 'win32'
88 | ? `an admin`
89 | : `a super user ${color.boldYellow(`sudo pm ${command}`)}`;
90 | logger.error(`Access Denied! please try again as ${errCmd}`);
91 | return;
92 | }
93 | throwCreateIssueError(err);
94 | return;
95 | }
96 | logger.success(successMessage);
97 | });
98 | }
99 |
100 | async function openURL(url) {
101 | let stderr;
102 | switch (process.platform) {
103 | case 'darwin':
104 | ({ stderr } = await exec(`open ${url}`));
105 | break;
106 | case 'win32':
107 | ({ stderr } = await exec(`start ${url}`));
108 | break;
109 | default:
110 | ({ stderr } = await exec(`xdg-open ${url}`));
111 | break;
112 | }
113 | if (stderr) {
114 | console.log(stderr);
115 | }
116 | return stderr;
117 | }
118 |
119 | function isURL(str) {
120 | // eslint-disable-next-line max-len
121 | const regex = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/;
122 | if (!regex.test(str)) {
123 | return false;
124 | } else {
125 | return true;
126 | }
127 | }
128 |
129 | const suggestFilter = (input, choices) => {
130 | return Promise.resolve(
131 | choices.filter((choice) =>
132 | choice.title.toLowerCase().includes(input.toLowerCase())
133 | )
134 | );
135 | };
136 |
137 | const suggestCommands = (cmd) => {
138 | const suggestion = didYouMean(
139 | cmd,
140 | program.commands.map((cmd) => cmd._name)
141 | );
142 | if (suggestion) {
143 | console.log();
144 | console.log(`Did you mean ${suggestion}?`);
145 | }
146 | };
147 |
148 | const onCancel = () => {
149 | logger.error("See ya ('__') /");
150 | process.exit();
151 | return false;
152 | };
153 |
154 | function getChoices(customFilter = () => true) {
155 | const projects = [...settings.projects];
156 | const result = projects.filter(customFilter).map(({ ...project }) => {
157 | // Spreading project to make it immutable
158 | project.title = project.name;
159 | project.value = { name: project.name, path: project.path };
160 | if (project.editor && project.editor !== settings.commandToOpen) {
161 | project.title += color.grey(
162 | ` (${color.boldGrey('editor:')} ${color.grey(project.editor + ')')}`
163 | );
164 | project.value.editor = project.editor;
165 | }
166 |
167 | if (isURL(project.path)) {
168 | project.title += color.boldGrey(` (URL)`);
169 | project.description = project.path;
170 | }
171 |
172 | return project;
173 | });
174 |
175 | return result;
176 | }
177 |
178 | async function selectProject(projectName, message, customFilter = () => true) {
179 | let selectedProject;
180 | if (!projectName) {
181 | // Ask which project they want to open
182 | const questions = [
183 | {
184 | type: 'autocomplete',
185 | message: `${message}: `,
186 | name: 'selectedProject',
187 | choices: getChoices(customFilter),
188 | limit: 40,
189 | suggest: suggestFilter
190 | }
191 | ];
192 |
193 | // Redirecting to stderr in order for it to be used with command substitution
194 | ({ selectedProject } = await prompts(questions, { onCancel }));
195 | } else {
196 | // If project name is mentioned then open directly
197 | selectedProject = settings.projects.find(
198 | (project) => project.name.toLowerCase() == projectName.toLowerCase()
199 | );
200 | }
201 | return selectedProject;
202 | }
203 |
204 | /**
205 | * Recursively creates the path
206 | * @param {String} pathToCreate path that you want to create
207 | */
208 | function createPathIfAbsent(pathToCreate) {
209 | // prettier-ignore
210 | pathToCreate
211 | .split(path.sep)
212 | .reduce((prevPath, folder) => {
213 | const currentPath = path.join(prevPath, folder, path.sep);
214 | if (!fs.existsSync(currentPath)) {
215 | fs.mkdirSync(currentPath);
216 | }
217 | return currentPath;
218 | }, '');
219 | }
220 |
221 | // const isDirectoryIgnored = (ignoredPatterns, filePath) => {
222 | // const ignoredDir = ignoredPatterns.find((pattern) =>
223 | // minimatch(filePath, pattern)
224 | // );
225 |
226 | // if (ignoredDir) {
227 | // console.log('>> ignoring ', filePath);
228 | // }
229 |
230 | // return !!ignoredDir;
231 | // };
232 |
233 | const isGitIgnored = (filePath, basePath, gitignore) => {
234 | return gitignore.denies(path.relative(basePath, filePath));
235 | };
236 |
237 | /**
238 | *
239 | * @param {String} from - Path to copy from
240 | * @param {String} to - Path to copy to
241 | * @param {Array} ignore - files/directories to ignore
242 | * @param {Boolean} ignoreEmptyDirs - Ignore empty directories while copying
243 | * @return {void}
244 | */
245 | function copyFolderSync(from, to, gitignore, ignoreEmptyDirs = true, basePath) {
246 | if (isGitIgnored(from, basePath, gitignore)) {
247 | return;
248 | }
249 | const fromDirectories = fs.readdirSync(from);
250 |
251 | createPathIfAbsent(to);
252 | fromDirectories.forEach((element) => {
253 | const fromElement = path.join(from, element);
254 | const toElement = path.join(to, element);
255 | if (fs.lstatSync(fromElement).isFile()) {
256 | if (!isGitIgnored(fromElement, basePath, gitignore)) {
257 | fs.copyFileSync(fromElement, toElement);
258 | }
259 | } else {
260 | copyFolderSync(
261 | fromElement,
262 | toElement,
263 | gitignore,
264 | ignoreEmptyDirs,
265 | basePath
266 | );
267 | if (fs.existsSync(toElement) && ignoreEmptyDirs) {
268 | try {
269 | fs.rmdirSync(toElement);
270 | } catch (err) {
271 | if (err.code !== 'ENOTEMPTY') throw err;
272 | }
273 | }
274 | }
275 | });
276 | }
277 |
278 | // Helper funcitions [END]
279 |
280 | module.exports = {
281 | settings,
282 | logger,
283 | getSettings,
284 | SETTINGS_PATH,
285 | throwCreateIssueError,
286 | writeSettings,
287 | openURL,
288 | isURL,
289 | suggestCommands,
290 | onCancel,
291 | getChoices,
292 | selectProject,
293 | copyFolderSync
294 | };
295 |
--------------------------------------------------------------------------------
/lib/logs.js:
--------------------------------------------------------------------------------
1 | const color = require('./colors');
2 | const { throwCreateIssueError } = require('./helper');
3 |
4 | const error = (message) => {
5 | console.error(color.boldRed('>>>'), message);
6 | };
7 |
8 | const warn = (message) => {
9 | console.warn(color.boldYellow('>>>'), message);
10 | };
11 |
12 | const info = (message) => {
13 | console.log(color.boldBlue('>>>'), message);
14 | };
15 |
16 | const noProjectsToOpen = () => {
17 | error('No projects to open :(');
18 | info(
19 | `cd /till/project/directory/ and run ${color.yellow(
20 | 'pm add'
21 | )} to add projects and get started`
22 | );
23 | };
24 |
25 | const projectDoesNotExist = () => {
26 | error(
27 | `Project does not exist. Add it using ${color.yellow(
28 | 'pm add [projectPath]'
29 | )} or cd till the project folder and type ${color.yellow('pm add')}`
30 | );
31 | };
32 |
33 | const unknownError = (stderr) => {
34 | error('Something went wrong :(');
35 | throwCreateIssueError(stderr);
36 | };
37 |
38 | module.exports = {
39 | noProjectsToOpen,
40 | projectDoesNotExist,
41 | unknownError,
42 | error,
43 | warn
44 | };
45 |
--------------------------------------------------------------------------------
/mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "diff": true,
3 | "extension": ["js"],
4 | "opts": false,
5 | "package": "./package.json",
6 | "reporter": "spec",
7 | "slow": 75,
8 | "timeout": 2000,
9 | "ui": "bdd"
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "projectman",
3 | "version": "2.2.0",
4 | "description": "Hate opening folders? Select and open your projects in your favourite editor straight from your command line without 'CD'ing into the deeply nested folders.",
5 | "main": "bin/index.js",
6 | "bin": {
7 | "projectman": "bin/index.js",
8 | "pm": "bin/index.js"
9 | },
10 | "preferGlobal": true,
11 | "scripts": {
12 | "test": "jest",
13 | "build": "pkg ./package.json --out-path ./dist || echo '\nPlease first `npm install -g pkg` to run this command'",
14 | "eslint": "eslint lib bin",
15 | "prettier": "prettier lib bin --write"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/saurabhdaware/projectman.git"
20 | },
21 | "keywords": [
22 | "project-manager",
23 | "projectman",
24 | "open-projects",
25 | "projectmanager",
26 | "congifure-projects"
27 | ],
28 | "author": "Saurabh Daware (https://saurabhdaware.in)",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/saurabhdaware/projectman/issues"
32 | },
33 | "homepage": "https://github.com/saurabhdaware/projectman#readme",
34 | "dependencies": {
35 | "commander": "^3.0.1",
36 | "didyoumean": "^1.2.1",
37 | "gitignore-parser": "^0.0.2",
38 | "prompts": "^2.3.0"
39 | },
40 | "devDependencies": {
41 | "cli-testing-tool": "^0.2.0",
42 | "eslint": "^7.0.0",
43 | "eslint-config-google": "^0.14.0",
44 | "eslint-config-prettier": "^6.11.0",
45 | "eslint-plugin-prettier": "^3.1.3",
46 | "jest": "^27.2.0",
47 | "prettier": "^2.0.5"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/action.spec.js:
--------------------------------------------------------------------------------
1 | describe('action', () => {
2 | test('this test should be replaced with real tests', () => {
3 | expect(true).toBe(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/tests/helper.spec.js:
--------------------------------------------------------------------------------
1 | const { isURL } = require('../lib/helper');
2 | const cleanUp = require('./utils/cleanup');
3 |
4 | describe('helper', () => {
5 | afterEach(cleanUp);
6 | test('#isURL()', () => {
7 | // Links
8 | expect(isURL('https://www.google.com')).toBe(true);
9 | expect(isURL('https://www.data.gov.in')).toBe(true);
10 | expect(isURL('http://facebook.com')).toBe(true);
11 | expect(isURL('http://github.com')).toBe(true);
12 |
13 | // Not Links
14 | expect(isURL('helloworld')).toBe(false);
15 | expect(isURL('htts://www.google.com')).toBe(false);
16 | expect(isURL('hats://facebook')).toBe(false);
17 | expect(isURL('blablabla.com')).toBe(false);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/tests/open.spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { createCommandInterface, parseOutput } = require('cli-testing-tool');
3 | const cleanUp = require('./utils/cleanup');
4 | const { STRING_ESC } = require('cli-testing-tool/lib/cli-ansi-parser');
5 |
6 | const NO_PROJECT_FOUND_ERROR =
7 | '[BOLD_START][RED_START]>>>[COLOR_END][BOLD_END] No projects to open :(';
8 | const CD_TO_ADD_PROJECT_MSG =
9 | // eslint-disable-next-line max-len
10 | '[BOLD_START][BLUE_START]>>>[COLOR_END][BOLD_END] cd /till/project/directory/ and run [YELLOW_START]pm add[COLOR_END] to add projects and get started';
11 |
12 | async function addProject(dir) {
13 | const addCommandInterface = createCommandInterface(
14 | `node ${__dirname}/../bin/index.js add`,
15 | {
16 | cwd: dir
17 | }
18 | );
19 | await addCommandInterface.keys.enter(); // selects default name
20 | return addCommandInterface.getOutput();
21 | }
22 |
23 | describe('projectman open', () => {
24 | afterEach(() => {
25 | cleanUp();
26 | });
27 |
28 | test('should log no error found and pm add instructions', async () => {
29 | const commandInterface = createCommandInterface(
30 | 'node ../bin/index.js open',
31 | {
32 | cwd: __dirname
33 | }
34 | );
35 |
36 | const terminal = await commandInterface.getOutput();
37 | expect(terminal.tokenizedOutput).toBe(
38 | NO_PROJECT_FOUND_ERROR + '\n' + CD_TO_ADD_PROJECT_MSG
39 | );
40 | });
41 |
42 | // eslint-disable-next-line max-len
43 | test('should show dropdown with tests and utils and with arrow on utils', async () => {
44 | // add tests and utils
45 | await addProject(__dirname);
46 | await addProject(path.join(__dirname, 'utils'));
47 |
48 | const openCommandInterface = createCommandInterface(
49 | 'node ../bin/index.js open',
50 | {
51 | cwd: __dirname
52 | }
53 | );
54 | await openCommandInterface.keys.arrowDown(); // move down to select utils
55 | const { rawOutput } = await openCommandInterface.getOutput();
56 | /**
57 | * Why slice from last clear line?
58 | *
59 | * Even though we see one output in terminal, sometimes libraries can create multiple outputs
60 | * slicing from the last clear line just makes sure we only get final output.
61 | */
62 | const outputAfterLastLineClear = rawOutput.slice(
63 | rawOutput.lastIndexOf(`${STRING_ESC}2K`)
64 | );
65 | const parsedOutputAfterLastLineClear = parseOutput(
66 | outputAfterLastLineClear
67 | );
68 |
69 | expect(parsedOutputAfterLastLineClear.stringOutput).toBe(
70 | `? Select project to open:› \ntests \nutils`
71 | );
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/tests/utils/cleanup.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const dotProjectMan = path.join(__dirname, '..', 'dotprojectman');
5 | const cleanUp = () => {
6 | fs.rmSync(dotProjectMan, { recursive: true });
7 | };
8 |
9 | module.exports = cleanUp;
10 |
--------------------------------------------------------------------------------
/types/utils.d.ts:
--------------------------------------------------------------------------------
1 | export type ChoiceType = {
2 | title: string;
3 | };
4 |
5 | export type ProjectType = {
6 | name: string;
7 | path: string;
8 | editor?: string;
9 | };
10 |
11 | export type SettingsType = {
12 | commandToOpen: string;
13 | projects: ProjectType[];
14 | };
15 |
--------------------------------------------------------------------------------