├── .gitignore ├── assets └── workspace-cli-demo.gif ├── workspaces-cli.code-workspace ├── package.json ├── .github └── workflows │ └── publish.yml ├── LICENSE ├── README.md └── bin └── workspaces-cli.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /assets/workspace-cli-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woudsma/workspaces-cli/HEAD/assets/workspace-cli-demo.gif -------------------------------------------------------------------------------- /workspaces-cli.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@woudsma/workspaces-cli", 3 | "version": "1.0.9", 4 | "description": "A small command-line tool to easily list all your different VS Code Workspaces", 5 | "main": "bin/workspaces-cli.js", 6 | "preferGlobal": true, 7 | "bin": { 8 | "workspaces": "bin/workspaces-cli.js", 9 | "ws": "bin/workspaces-cli.js" 10 | }, 11 | "dependencies": { 12 | "@jsdevtools/readdir-enhanced": "^6.0.0", 13 | "enquirer": "^2.3.4" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+ssh://git@github.com/woudsma/workspaces-cli.git" 18 | }, 19 | "keywords": [ 20 | "vs", 21 | "code", 22 | "workspaces", 23 | "cli", 24 | "list", 25 | "select" 26 | ], 27 | "author": "Tjerk Woudsma", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/woudsma/workspaces-cli/issues" 31 | }, 32 | "homepage": "https://github.com/woudsma/workspaces-cli#readme" 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.js' 7 | - '**.json' 8 | 9 | jobs: 10 | publish-npm: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npx bump-cli *.json --yes && npm publish --access public 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | 23 | publish-gpr: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-node@v1 28 | with: 29 | node-version: 12 30 | registry-url: https://npm.pkg.github.com/ 31 | scope: '@woudsma' 32 | - run: npx bump-cli *.json --yes && npm publish --access public 33 | env: 34 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tjerk Woudsma 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 | # Workspaces CLI 2 | ![Publish to npm](https://github.com/woudsma/workspaces-cli/workflows/Publish%20to%20npm/badge.svg?branch=master) 3 | 4 | A small command-line tool to easily list all your different VS Code Workspaces. 5 | 6 | `ws` searches for all `.code-workspace` files in a user-specified root directory. 7 | By default, `ws` searches only 1 level deep to prevent traversing folders like `node-modules`, `vendor`, etc. 8 | 9 | ![workspaces-cli-demo](assets/workspace-cli-demo.gif) 10 | 11 | --- 12 | ## Installation 13 | ```sh 14 | npm i -g @woudsma/workspaces-cli 15 | ``` 16 | 17 | ## Requirements 18 | - Node.js version >= 8 19 | - Make sure you have installed the `code` command in your `$PATH` - [How to](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line) 20 | 21 | ## Usage 22 | #### Create a workspace 23 | Create a workspace by opening a project in VS Code, optionally add more projects to your current workspace. 24 | Save the workspace with: 25 | ``` 26 | File -> Save Workspace As... 27 | ``` 28 | #### Run `ws` 29 | I've made a `_workspaces` folder in my personal projects folder, where I keep workspaces that include multiple projects. Other workspaces are usually stored in their own project folder. `ws` searches 1 level deep. 30 | More info on [Multi-root workspaces](https://github.com/microsoft/vscode-docs/blob/master/docs/editor/multi-root-workspaces.md). 31 | Example: 32 | ``` 33 | $ ws 34 | Workspaces root directory: /Users/woudsma/Projects 35 | ? Select workspace … 36 | ❯ _workspaces/hasura-test 37 | _workspaces/kirby-react-test 38 | _workspaces/mount-spaces 39 | workspaces-cli/workspaces-cli 40 | ``` 41 | The workspace for this project is saved at `/Users/woudsma/Projects/workspaces-cli/workspaces-cli.code-workspace` for example. 42 | 43 | #### CLI 44 | ``` 45 | $ ws [-h|--help] 46 | ``` 47 | `ws` is an alias of `workspaces`. 48 | 49 | ## First time configuration 50 | `ws` reads your workspaces root directory from `~/.workspacesrc`. 51 | If no configuration can be found, `ws` will try to create a `~/.workspacesrc` file with the workspaces root directory that you've provided. 52 | ``` 53 | $ ws 54 | No configuration found in /Users/woudsma/.workspacesrc 55 | Creating /Users/woudsma/.workspacesrc 56 | ? Enter workspaces root directory, e.g. ~/Projects › ~/ 57 | ``` 58 | ## Configuration 59 | Multiple root directories can be specified by adding them to `WORKSPACES_ROOT_DIR` in `~/.workspacesrc`, seperated by comma. 60 | Example: 61 | ```sh 62 | # in ~/.workspacesrc 63 | WORKSPACES_ROOT_DIR=/Users/woudsma/Projects,/Users/woudsma/Company/clients 64 | ``` 65 | 66 | Turn off the subshell when selecting a workspace. 67 | ```sh 68 | # USE_SUBSHELL=true (default) 69 | echo USE_SUBSHELL=false >> ~/.workspacesrc 70 | ``` 71 | The default search depth can be changed by adding `READDIR_DEPTH=` to `~/.workspacesrc`. 72 | ```sh 73 | # READDIR_DEPTH=1 (default) 74 | echo READDIR_DEPTH=2 >> ~/.workspacesrc 75 | ``` 76 | --- 77 | Pull requests are welcome! 78 | 79 | --- 80 | ## License 81 | 82 | MIT. 83 | -------------------------------------------------------------------------------- /bin/workspaces-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const os = require('os') 4 | const { existsSync, readFileSync, appendFileSync } = require('fs') 5 | const { execSync, spawnSync } = require('child_process') 6 | const { Input, Select } = require('enquirer') 7 | const readdir = require('@jsdevtools/readdir-enhanced') 8 | 9 | const FLAG_HELP = process.argv.includes('-h') || process.argv.includes('--help') 10 | const rcfile = `${os.homedir()}/.workspacesrc` 11 | const ext = '.code-workspace' 12 | 13 | const main = async () => { 14 | if (FLAG_HELP) { 15 | console.log(` 16 | Usage: 17 | $ ws [-h|--help] 18 | 19 | Configuration 20 | Multiple root directories can be specified by adding them to WORKSPACES_ROOT_DIR in ~/.workspacesrc, seperated by comma. 21 | Example: 22 | # In ~/.workspacesrc 23 | WORKSPACES_ROOT_DIR=/Users/woudsma/Projects,/Users/woudsma/Company/clients 24 | 25 | Turn off the subshell when selecting a workspace 26 | # USE_SUBSHELL=true (default) 27 | echo USE_SUBSHELL=false >> ~/.workspacesrc 28 | 29 | The default search depth can be changed by adding READDIR_DEPTH= to ~/.workspacesrc. 30 | Example: 31 | # READDIR_DEPTH=1 (default) 32 | echo READDIR_DEPTH=2 >> ~/.workspacesrc 33 | `) 34 | process.exit(0) 35 | } 36 | 37 | const config = await new Promise((resolve, reject) => { 38 | if (existsSync(rcfile)) { 39 | resolve(readFileSync(rcfile).toString()) 40 | } else { 41 | console.log(`No configuration found in ${rcfile}\nCreating ${rcfile}`) 42 | 43 | const inputPrompt = new Input({ 44 | message: `Enter workspaces root directory, e.g. ~/Projects`, 45 | default: '~/' 46 | }) 47 | 48 | inputPrompt.run() 49 | .then(input => { 50 | const workspacesRootDir = input.replace('~/', `${os.homedir()}/`) 51 | appendFileSync(rcfile, `WORKSPACES_ROOT_DIR=${workspacesRootDir}`) 52 | resolve(readFileSync(rcfile).toString()) 53 | }) 54 | .catch(err => reject(err)) 55 | } 56 | }) 57 | 58 | const { 59 | WORKSPACES_ROOT_DIR = os.homedir(), 60 | USE_SUBSHELL = true, 61 | READDIR_DEPTH = 1, 62 | } = config 63 | .split('\n') 64 | .map(e => e.split('=')) 65 | .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) 66 | 67 | console.log('Workspaces root directory:', WORKSPACES_ROOT_DIR) 68 | 69 | const choices = [].concat(...WORKSPACES_ROOT_DIR 70 | .split(',') 71 | .filter(Boolean) 72 | .map(dir => readdir 73 | .sync(dir, { deep: Number(READDIR_DEPTH) }) 74 | .filter(filepath => filepath.includes(ext)) 75 | .map(workspacePath => ({ 76 | workspace: `[${dir.split('/').pop()}] ${workspacePath.replace(ext, '')}`, 77 | path: `${dir}/${workspacePath}`, 78 | cwd: `${dir}/${workspacePath.split('/').slice(0, -1).join('/')}` 79 | })))) 80 | 81 | const selectPrompt = new Select({ 82 | message: 'Select workspace', 83 | choices: choices.map(({ workspace }) => workspace), 84 | }) 85 | 86 | selectPrompt.run() 87 | .then(choice => choices 88 | .find(({ workspace, path, cwd }) => choice === workspace 89 | && execSync(`code ${path}`) 90 | && JSON.parse(USE_SUBSHELL) 91 | && (console.log('Entering workspace directory in a subshell'), true) 92 | && (console.log(cwd), true) 93 | && (console.log(`Enter 'exit' to exit subshell`), true) 94 | && spawnSync(process.env.SHELL, ['-i'], { 95 | cwd, 96 | env: process.env, 97 | stdio: 'inherit', 98 | }))) 99 | .catch(console.error) 100 | } 101 | 102 | process.on('uncaughtException', err => { 103 | console.error(err) 104 | process.exit(1) 105 | }) 106 | 107 | main() 108 | --------------------------------------------------------------------------------