├── .github └── workflows │ └── release.yml ├── .gitignore ├── .indo.json ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── bin └── indo ├── docs ├── config.md ├── get-started.md ├── using-a-temporary-fork.md ├── using-vendor.md ├── using-yarn-link.md └── using-yarn-workspaces.md ├── help ├── add.md ├── clone.md ├── default.md ├── init.md ├── list.md ├── move.md ├── purge.md ├── remove.md └── share.md ├── jest.config.js ├── jest ├── global.d.ts └── setup.ts ├── package.json ├── pnpm-lock.yaml ├── spec ├── __fixtures__ │ ├── nested │ │ └── .indo.json │ ├── reused-clone │ │ ├── 1 │ │ │ └── .indo.json │ │ ├── 2 │ │ │ └── .indo.json │ │ ├── .indo.json │ │ └── README.md │ └── sparse │ │ └── .indo.json ├── __snapshots__ │ └── sparse.spec.ts.snap ├── nested.spec.ts ├── reused-clone.spec.ts ├── sparse.spec.ts └── tsconfig.json ├── src ├── cli.ts ├── commands │ ├── add.ts │ ├── clone.ts │ ├── default.ts │ ├── exec.ts │ ├── fork.ts │ ├── git.ts │ ├── init.ts │ ├── link.ts │ ├── list.ts │ ├── move.ts │ ├── pull.ts │ ├── purge.ts │ ├── remove.ts │ ├── run.ts │ ├── share.ts │ ├── unlink.ts │ └── upgrade.ts ├── core │ ├── Package.ts │ ├── buildPackages.ts │ ├── cache.ts │ ├── config.ts │ ├── cpu.ts │ ├── eachDependency.ts │ ├── findLocalPackages.ts │ ├── findPackages.ts │ ├── findVendorPackages.ts │ ├── fs.ts │ ├── getInverseDeps.ts │ ├── getNearestPackage.ts │ ├── getPublishedVersions.ts │ ├── git.ts │ ├── gitignore.ts │ ├── helpers.ts │ ├── installPackages.ts │ ├── lerna.ts │ ├── linkPackages.ts │ ├── loadAllPackages.ts │ ├── loadLinkManifest.ts │ ├── loadLocalPackages.ts │ ├── loadPackages.ts │ ├── loadVendors.ts │ ├── makeCacheFn.ts │ ├── npm.ts │ ├── printHelp.ts │ ├── promptMemory.ts │ ├── registry.ts │ ├── repairNodeModules.ts │ ├── resolveAlias.ts │ ├── searchParents.ts │ └── sparseClone.ts └── types │ └── markty-toml.d.ts ├── tsconfig.json └── tslint.json /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | test: 5 | name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | node_version: [10, 12, 14] 11 | os: [ubuntu-latest, windows-latest, macos-latest] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node_version }} 19 | 20 | - name: Install 21 | uses: pnpm/action-setup@v1.2.1 22 | with: 23 | version: 5 24 | run_install: | 25 | args: [--frozen-lockfile] 26 | 27 | - name: Test 28 | run: yarn test --coverage 29 | 30 | - name: Reset fixtures 31 | run: git clean -df 32 | 33 | - name: Upload coverage 34 | run: npx codecov 35 | env: 36 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | vendor/ 4 | -------------------------------------------------------------------------------- /.indo.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["stkb.rewrap"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "[markdown]": { 4 | "editor.wordWrap": "on", 5 | "editor.quickSuggestions": { 6 | "comments": "off", 7 | "strings": "off", 8 | "other": "off" 9 | }, 10 | "editor.formatOnSave": false 11 | }, 12 | "editor.wordWrapColumn": 100, 13 | "rewrap.wrappingColumn": 80 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Alec Larson 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 | # indo 2 | 3 | Workspaces where each package has its own commit history. 4 | 5 | Setup your monorepo with one command: 6 | 7 | ```sh 8 | npx indo 9 | ``` 10 | 11 | ## Synopsis 12 | 13 | Monorepos are great for keeping a bundle of packages tied together by a commit history, but sometimes a package needs (or already has) its own commit history. For example, you might be developing a fork of someone else's package. Indo lets you choose which packages deserve their own commit history. Just run `git clone` and Indo will notice. **Note:** Be sure to add your clones to `.gitignore` to avoid [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) (which are *not* supported by Indo). 14 | 15 | Indo automatically searches your monorepo for `package.json` files, which means it can be used with **zero setup**. The `indo` command will (1) create the `.indo.json` if none is found, (2) clone any missing repos, (3) install dependencies, (4) run `build` scripts, and (5) link local packages together. 16 | 17 | **Fun facts:** 18 | 19 | - Indo *never* hoists dependencies 20 | - Indo plays nicely with Yarn workspaces 21 | - Indo makes forking a breeze 22 | 23 |   24 | 25 | ## Guides 26 | 27 | - [Get Started](./docs/get-started.md) 28 | - [Using Vendor Packages](./docs/using-vendor.md) 29 | - [Using Yarn Workspaces](./docs/using-yarn-workspaces.md) 30 | - [Using A Temporary Fork](./docs/using-a-temporary-fork.md) 31 | - [Using `yarn link`](./docs/using-yarn-link.md) 32 | - [Configuration](./docs/config.md) 33 | 34 |   35 | 36 | ## Commands 37 | 38 | ### `indo` 39 | 40 | Run this command to bootstrap your Indo-powered monorepo, which involves 41 | cloning any missing repos, installing any dependencies, and linking together your 42 | local packages. 43 | 44 | Specify `-f`/`--force` to see which packages are linked where. Otherwise, only 45 | newly linked packages are printed. 46 | 47 | **Note:** Packages are ignored when no `version` exists in their `package.json`. 48 | 49 |   50 | 51 | ### `indo help` 52 | 53 | Print documentation for a specific command. 54 | 55 | ```sh 56 | # What does "indo clone" do? 57 | indo clone help 58 | ``` 59 | 60 | Aliases: `-h`, `--help` 61 | 62 |   63 | 64 | ### `indo clone` 65 | 66 | Shallow clone a repository and add it to "repos" in the nearest `.indo.json` config. 67 | 68 | You can even provide a package name instead of a git url! For example, `indo clone lodash` 69 | asks npm for the git url and clones it into `vendor/lodash` by default. You can also pass 70 | an optional directory name (eg: `indo clone lodash a/b/c`). 71 | 72 |   73 | 74 | ### `indo link` 75 | 76 | Link a global package to the `./vendor/` directory, and link it to packages that can use it. 77 | 78 | ```sh 79 | indo link lodash 80 | ``` 81 | 82 | However, before you can do that, you must call `indo link` in your lodash clone. 83 | 84 | ```sh 85 | # Clone "lodash" outside your monorepo. 86 | git clone https://github.com/lodash/lodash.git ~/dev/lodash 87 | cd ~/dev/lodash 88 | 89 | # Add it to Indo's global package registry. 90 | indo link 91 | ``` 92 | 93 | It's basically `yarn link` except with automatic linking to packages in your monorepo. 😻 94 | 95 | For a monorepo whose root package is unnamed, use `indo link -g ` to register it globally. 96 | Then use `indo link ` to link your local packages to it. 97 | 98 |   99 | 100 | ### `indo unlink` 101 | 102 | Remove the current package from Indo's global package registry. 103 | 104 | ```sh 105 | indo unlink 106 | ``` 107 | 108 | To revert `indo link ` commands, run `indo unlink ` and the given package names 109 | will be removed from the `./vendor/` directory (but only if they were added with `indo link`). 110 | 111 | ```sh 112 | indo unlink lodash 113 | ``` 114 | 115 |   116 | 117 | ### `indo add` 118 | 119 | Add dependencies to the current package, using its preferred `npm` client (eg: `yarn` or `pnpm`). 120 | 121 | After installing, the dependency is replaced with a local package if possible. 122 | 123 | ```sh 124 | indo add lodash 125 | ``` 126 | 127 | Supported flags: 128 | - `--prod` (enabled by default) 129 | - `-P`/`--peer` 130 | - `-D`/`--dev` 131 | - `-O`/`--optional` 132 | - `-E`/`--exact` 133 | 134 |   135 | 136 | ### `indo remove` 137 | 138 | Remove dependencies from the current package, using its preferred `npm` client. 139 | 140 | ```sh 141 | indo remove lodash 142 | ``` 143 | 144 | Aliases: `rm` 145 | 146 |   147 | 148 | ### `indo list` 149 | 150 | See which packages are detected by Indo. 151 | 152 | ```sh 153 | indo list 154 | ``` 155 | 156 | Aliases: `ls` 157 | 158 |   159 | 160 | ### `indo run` 161 | 162 | Run a npm script in every non-vendor package. 163 | 164 | ```sh 165 | indo run build 166 | ``` 167 | 168 |   169 | 170 | ### `indo exec` 171 | 172 | Run an arbitrary command in every non-vendor package. 173 | 174 | **Note:** Piping is not yet supported. 175 | 176 | ```sh 177 | indo exec -- echo \$PACKAGE_NAME 178 | ``` 179 | 180 | Injected variables include: 181 | - `PACKAGE_NAME` 182 | - `PACKAGE_ROOT` 183 | 184 |   185 | 186 | ### `indo git` 187 | 188 | Run a `git` command in every `.git` repo containing a non-vendor package. 189 | 190 | **Note:** Your customized `git` aliases are supported! 191 | 192 | ```sh 193 | indo git status 194 | ``` 195 | 196 |   197 | 198 | ### `indo purge` 199 | 200 | Remove one or more packages, cleaning up `.indo.json` along the way. 201 | 202 | For example, `indo purge foo bar` removes the `./foo` and `./bar` directories (relative to the current directory) from the filesystem and from the nearest `.indo.json` file. 203 | 204 | The given directories are not required to contain a `package.json`. For example, you can do `indo rm packages` 205 | to delete the entire `packages` directory, which may contain dozens of repos, each with its own `package.json`. Indo re-installs the dependencies of any non-vendor package that was linked to a removed package. 206 | 207 | It's basically `rm -rf` but with: 208 | - a confirmation prompt 209 | - automatic updates to the nearest `.indo.json` file 210 | - an install step for depending packages 211 | 212 |   213 | 214 | ### `indo init` 215 | 216 | Create an empty `.indo.json` file in the current directory, replacing any pre-existing `.indo.json` file. 217 | 218 | The `indo` command automatically invokes this command when neither the current directory nor any of 219 | its ancestors contain a `.indo.json` file. 220 | -------------------------------------------------------------------------------- /bin/indo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/cli') 3 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The `.indo.json` config may contain these properties. 4 | 5 | - `alias?: object` 6 | - `repos?: object` 7 | - `vendor?: string[]` 8 | 9 | The `alias` object works just like Yarn aliases, except only when linking local 10 | packages together. For example, `"foo": "bar"` maps any local dependencies on 11 | `foo` to `bar` **but only if** `bar` is a local package. 12 | 13 | The `repos` object tells Indo where to clone from. The `indo` command will search 14 | for unknown clones and offer to add them to `repos` for you. If you decline, they 15 | will be added to `vendor` instead. 16 | 17 | The `vendor` array tells Indo which globs to treat like external dependencies. 18 | Any matching package never has its dependencies managed by Indo. No linking, no 19 | installing. This defaults to `["vendor/**"]`. 20 | -------------------------------------------------------------------------------- /docs/get-started.md: -------------------------------------------------------------------------------- 1 | # Get Started 2 | 3 | To start using Indo with a monorepo, run the `indo` command. 4 | 5 | This will find every nested `.git` repository. Then it asks which repositories should be automatically cloned for other developers when they run `indo`. Select "Add to vendor" to prevent other developers from using any of the packages inside the `.git` repository. Select "Add to repos" to let Indo clone it for other developers. 6 | 7 | Now, commit the `.indo.json` file in your monorepo's root directory. This gives other developers the same configuration used by the `indo` command. 8 | 9 | And you're done! 10 | -------------------------------------------------------------------------------- /docs/using-a-temporary-fork.md: -------------------------------------------------------------------------------- 1 | # Using A Temporary Fork 2 | 3 | Sometimes you need to fork a dependency for a temporary fix, and you want every developer to use that fork instead of the max satisfying npm version. 4 | 5 | To solve this with `indo`, you do the following: 6 | 7 | 1. Clone the npm package: 8 | 9 | ```sh 10 | # Create "vendor/lodash" from the git url in package.json 11 | indo clone lodash 12 | 13 | # Or use a git url explicitly 14 | indo clone https://github.com/lodash/lodash.git ./vendor/lodash 15 | ``` 16 | 17 | 2. Apply your changes to `vendor/lodash` 18 | 19 | 3. Commit the `.indo.json` file 20 | 21 | 4. Done! 22 | 23 | Then, other developers can clone your monorepo and run `indo`, which clones `vendor/lodash` for them automatically. For more info, please see ["Get Started"](./get-started.md). 24 | -------------------------------------------------------------------------------- /docs/using-vendor.md: -------------------------------------------------------------------------------- 1 | # Using Vendor Packages 2 | 3 | "Vendor packages" are npm packages that match any glob in the "vendor" array (for more info, see ["Configuration"](./config.md)). These packages **never** have their dependencies installed (or their build scripts executed) when running the `indo` command. But they are still linked to as dependencies of non-vendor packages. 4 | 5 | This is useful when working on other dependencies that don't belong in the commit history of your monorepo (not even in its `.indo.json` file). For example, you could be developing a new feature in a dependency and testing your changes within your monorepo. 6 | 7 | To use vendor packages, use `git clone` or `ln -s` inside the `./vendor/` directory to add your repositories. When all of your clones are ready, run the `indo` command to link them to your non-vendor packages. It's that easy! 8 | 9 | Another option is to use `indo link`. More info [here](./using-yarn-link.md). 10 | 11 | ### Monorepos 12 | 13 | You can even clone a monorepo into the `./vendor/` directory. Right now, only Yarn workspaces are supported, but Lerna support will be added eventually. The `indo` command uses the `"workspaces"` glob array in each monorepo's `package.json` file to find more vendor packages. 14 | 15 | ### Linking between vendors 16 | 17 | Unfortunately, a vendor package **cannot** be automatically linked to another vendor package, as this would break the promise that we never touch the dependencies of vendor packages. 18 | 19 | As a workaround, you'll have to link the vendor packages together manually. If the vendor package is a symlink leading outside the monorepo, you should clone it before making changes, so you don't break other monorepos linked to the vendor package (if that's a concern). 20 | -------------------------------------------------------------------------------- /docs/using-yarn-link.md: -------------------------------------------------------------------------------- 1 | # Using `yarn link` 2 | 3 | Similar to `yarn link`, Indo has `indo link` for easily exposing your monorepo to local packages that exist outside your monorepo's root directory. 4 | 5 | Indo creates the `~/.indo` directory to store any registered packages. Try opening `~/.indo/registry.json` to see which packages have been linked in the past. 6 | 7 | To add a package to the global registry, run `indo link` from inside it. 8 | 9 | To use a global package, run `indo link ` in your Indo-powered monorepo. This adds a symlink to the `./vendor/` directory. 10 | 11 | To stop using a global package, run `indo rm ./vendor/` from your monorepo's root directory. 12 | 13 | To remove a package from the global registry, run `indo unlink` from inside it. 14 | -------------------------------------------------------------------------------- /docs/using-yarn-workspaces.md: -------------------------------------------------------------------------------- 1 | # Using Yarn Workspaces 2 | 3 | Non-vendor packages can be linked to packages within a local Yarn workspace. 4 | 5 | You have 2 options: 6 | 7 | 1. Clone the workspace when other developers run `indo` next. 8 | 9 | 2. Treat the workspace as a local override. 10 | 11 |   12 | 13 | ## Option 1 - Force others to use it 14 | 15 | 1. Clone the workspace (with `indo`): 16 | 17 | ```sh 18 | indo clone https://github.com/example/example.git ./vendor/example --branch next 19 | ``` 20 | 21 | 2. Prepare the workspace: 22 | 23 | ```sh 24 | cd ./vendor/example && yarn 25 | ``` 26 | 27 | 3. Expose packages in the workspace to your non-vendor packages: 28 | 29 | ```sh 30 | indo 31 | ``` 32 | 33 | 4. Done! 34 | 35 |   36 | 37 | ## Option 2 - Keep it to myself 38 | 39 | 1. Clone the workspace (with `git`): 40 | 41 | ```sh 42 | git clone https://github.com/example/example.git ./vendor/example --branch next 43 | ``` 44 | 45 | 2. Prepare the workspace 46 | 47 | ```sh 48 | cd ./vendor/example && yarn 49 | ``` 50 | 51 | 3. Expose packages in the workspace to your non-vendor packages: 52 | 53 | ```sh 54 | indo 55 | ``` 56 | 57 | 4. Make sure the workspace is ignored by git 58 | 59 | ```sh 60 | echo '/vendor/' >> .gitignore 61 | ``` 62 | 63 | 5. Done! 64 | -------------------------------------------------------------------------------- /help/add.md: -------------------------------------------------------------------------------- 1 | 2 | indo add - Add dependencies to the current package 3 | 4 | Identical to `yarn add` except both `npm` and `pnpm` can be used (if the current package 5 | prefers either of them). Once installed, the added dependencies are replaced by local packages 6 | if possible. 7 | -------------------------------------------------------------------------------- /help/clone.md: -------------------------------------------------------------------------------- 1 | 2 | indo clone - Shallow clone a git url or npm package 3 | 4 | 1. If a npm package like *lodash* is given, ask npm for the git url. 5 | Otherwise, a git url must be given 6 | 7 | 2. Clone the given branch (`-b` or `--branch` or *master*) with a `--depth` of 1 8 | 9 | 3. If no destination path is given, default to "packages/{name}" where `name` is 10 | inferred from the "package.json" file 11 | 12 | 4. Install the clone's `dependencies` and `devDependencies` using the package 13 | manager inferred from any existing lockfile. When no lockfile exists, default 14 | to using *yarn --no-lockfile* 15 | 16 | 5. Run the clone's `"build"` script 17 | 18 | 6. Update the `"repos"` object in `.indo.json` 19 | -------------------------------------------------------------------------------- /help/default.md: -------------------------------------------------------------------------------- 1 | 2 | indo - Prepare your monorepo for action! 3 | 4 | 1. Clone any missing repos 5 | 6 | 2. Find all non-vendor packages 7 | 8 | 3. Ensure all *.git* repos are tracked in `.indo.json` 9 | 10 | 4. For any non-vendor package missing its `node_modules`, install any 11 | `dependencies` and `devDependencies` using its preferred *npm* variant, 12 | and run its `"build"` script if possible 13 | 14 | 5. For every non-vendor package, find which dependencies can be satisfied 15 | by local packages, then replace them with symlinks to local packages 16 | 17 | 18 | *Sub commands* 19 | ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ 20 | 21 | - *add* [...packages] 22 | Install one or more packages, respecting the preferred *npm* variant. 23 | Afterwards, replace the package with a local equivalent, if possible. 24 | Run `indo add -h` to learn more. 25 | 26 | - *clone* \ 27 | Shallow clone a git repository or npm package. 28 | Run `indo clone -h` to learn more. 29 | 30 | - *exec* -- \ [...args] 31 | Execute a shell command in every non-vendor package. 32 | 33 | - *git* \ [...args] 34 | Run a `git` command in every non-vendor package with a `.git` folder. 35 | 36 | - *init* 37 | Create a `.indo.json` file in the current directory. 38 | 39 | - *link* [package] 40 | Link a package from the global indo registry into the `vendor` folder. 41 | If no argument is given, link the current package into the global registry. 42 | 43 | - *list* 44 | List all packages found by indo in the current monorepo. 45 | Pass `-g` to list packages in the global indo registry. 46 | 47 | - *move* \ \ 48 | Rename a package, updating `.indo.json` if needed, and run `indo` after. 49 | 50 | - *purge* [...paths] 51 | Destroy the given directory paths, updating `.indo.json` if needed. 52 | Afterwards, dependent packages have their `node_modules` repaired. 53 | Run `indo purge -h` to learn more. 54 | 55 | - *remove* [...packages] 56 | Uninstall one or more packages, respecting the preferred *npm* variant. 57 | 58 | - *run* \