Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rush] Feature idea: "rushcd" change directory to project folder #3771

Open
elliot-nelson opened this issue Nov 21, 2022 · 4 comments
Open
Labels
design proposal A new direction that is complex enough to have open questions but specific enough to implement
Projects

Comments

@elliot-nelson
Copy link
Collaborator

elliot-nelson commented Nov 21, 2022

Summary

@octogonz mentioned once in a conversation that it would be cool/useful if you could rush cd @acme/dynamite to change directory into the folder of the named project.

This issue is a design/brainstorm ticket to see if such a feature could be included in Rush.

Details

Rush bash/zsh alias

If you were to implement this for yourself, on your own machine, you'd need an alias or function (since an executable cannot alter the current working directory of the shell).

Here's a simple example:

rushcd() {
  local path=$(rush --quiet list --only $1 --json | jq '.projects[0].fullPath' -r -e)
  cd $path
}

Now, the real beauty of a rush cd command would be the auto-completion. Again, here's a simple version of what that could look like (for bash and zsh):

# Bash version (runs on both shells, but only works on bash)

_rushcd_completions() {
  COMPREPLY=( $(compgen -W "$(rush --quiet list)" "$2") )
}
complete -F _rushcd_completions rushcd

# zsh version (stifle error output on bash)

_rushcd() {
    local IFS=$'\n'
    for project in $(rush --quiet list); do
        _values 'Project' $project
    done
}
compdef _rushcd rushcd 2>/dev/null

Add the above scripts to your bashrc/zshrc to get a working rushcd command with auto-completion (albeit without a whole lot of error checking or correction).

Faster and uglier

In my local monorepo, the rushcd command above and the corresponding completion have to call rush and it's unfortunately quite slow... a double-TAB on a partial project name is a sluggish 1.89s.

If we're willing to get really hacky, we can do better (showing just the zsh version this time):

_rushcd2 {
  # Find the root of current monorepo
  local gitroot=$(git rev-parse --show-toplevel)
  local IFS=$'\n'
  # Strip comments from rush.json and parse with jq
  for project in $(cat $gitroot/rush.json | grep -E "^ *{|^ *}|^ *\"|^ *\[|^ *\]" | jq '.projects[].packageName' -r); do
    _values 'Project' $project
  done
}
compdef _rushcd rushcd

A double-TAB on a partial project now clocks in at 0.18s, which feels much more responsive. (Again, not much error correction.)

Windows support?

No idea -- does command and/or pwsh offer similar features? (If the user is running a subshell or bash on windows then they can use the bash version.)

What's next?

We could:

  1. Robustify this feature and provide it as an add-on shell script, to be used if desired
  2. We could "install" this feature, nvm-style, into your shell on a rush install (if user wants it?)
  3. Something else?

CI Support

Today, the de facto "cd into project folder" in a CI environment probably looks something like this:

    steps:
      - name: Project folder
        run: |
          echo PROJECT_PATH=$(node common/scripts/install-run-rush.js --quiet list --only $project --json) >> $GITHUB_ENV
      - name: Build
        run: npm run build
        working-directory: ${{ env.PROJECT_PATH }}
      - name: Terraform apply
        run: terraform apply
        working-directory: ${{ env.PROJECT_PATH }}/infra

User-facing features, like bash completions, don't apply here. But, if part of the cleanup / robustifying process above involves converting to nodejs, perhaps there's room for a new common/scripts/rushcd.js -- it takes as input the partial project name and returns (very quickly) the available completions. If this existed then CI could use it too!

    steps:
      - name: Project folder
        run: |
          echo PROJECT_PATH=$(node common/scripts/rushcd.js $project) >> $GITHUB_ENV

Standard questions

Please answer these questions to help us investigate your issue more quickly:

Question Answer
@microsoft/rush globally installed version?
rushVersion from rush.json?
useWorkspaces from rush.json?
Operating system?
Would you consider contributing a PR?
Node.js version (node -v)?
@elliot-nelson elliot-nelson changed the title [rush] Feature idea: "rushcd" [rush] Feature idea: "rushcd" change directory to project folder Nov 21, 2022
@iclanton
Copy link
Member

Definitely seems useful. Would we want to build this into Rush directly? Seems like that might be slower than people would want.

BTW, I wrote and have been using a rcd (rush cd) command in PowerShell a while ago:

$RUSH_LIST_OUTPUT_CACHE = @{}
$RUSH_LIST_OUTPUT_CACHE_HASHES = @{}

function Goto-RushProject {
  param(
    [Parameter(Position=0,mandatory=$true)]
    [string]$projectName
  )

  function getRushJsonPath {
    $rushJsonFolder = Get-Location
    while ($true) {
      $rushJsonPath = Join-Path $rushJsonFolder "rush.json"
      if (Test-Path $rushJsonPath) {
        return $rushJsonPath
      } else {
        $rushJsonFolder = Split-Path -Path $rushJsonFolder -Parent
        if ($rushJsonFolder -eq "") {
          return $null
        }
      }
    }
  }

  $rushJsonPath = getRushJsonPath
  if ($null -eq $rushJsonPath) {
    throw "Not in a Rush repo."
  }

  $rushJsonHash = (Get-FileHash $rushJsonPath).Hash

  $rushListData = $RUSH_LIST_OUTPUT_CACHE[$rushJsonPath]
  if (($null -eq $rushListData) -or ($RUSH_LIST_OUTPUT_CACHE_HASHES[$rushJsonPath] -ne $rushJsonHash)) {
    $rushListData = rush list --json | ConvertFrom-Json
    $RUSH_LIST_OUTPUT_CACHE[$rushJsonPath] = $rushListData
    $RUSH_LIST_OUTPUT_CACHE_HASHES[$rushJsonPath] = $rushJsonHash
  }

  $projectEntry = $rushListData.projects | Where-Object { $_.name -eq $projectName}
  if ($projectEntry.length -eq 0) {
    throw "No project found $projectName"
  } else {
    Set-Location $projectEntry.fullPath
  }
}

New-Alias -Name "rcd" Goto-RushProject

@iclanton iclanton added this to General Discussions in Bug Triage Nov 21, 2022
@elliot-nelson
Copy link
Collaborator Author

elliot-nelson commented Nov 21, 2022

Ah, thanks for that! The hashing idea is a good one, get most of the speed improvement while still relying on rush itself to produce the structured data.

@iclanton for a powershell user, what do you "do" with that snippet to make it part of your overall shell? is there a magic file you add it to, like bashrc?

@iclanton
Copy link
Member

Yes, there is a file whose path is defined in the $PROFILE variable that executes when the shell launches, similar to .bashrc

@elliot-nelson
Copy link
Collaborator Author

elliot-nelson commented Dec 3, 2022

Here's my revamped proposal for this feature based on my prototype and @iclanton 's powershell script:

  1. First, we'll add a small new common/scripts/ nodejs script, called rushcd.js, which takes the name of a Rush project and prints to stdout the path of the project. Alternately, it can take a command-line argument that indicates it is in completion mode (maybe --complete), and then the arg can be a partial Rush project, and it will print to stdout a list of possible matching projects.
  2. The node script above is responsible for "speeding up" execution by hashing and caching project list results as long as rush.json has not changed, so we don't wait for a full "rush list" bootup time.
  3. This core rushcd.js functionality can then be wrapped by 2 shell scripts (one bash version, one powershell version). All these scripts need to do is walk backwards in the directory tree until they find rush.json, call common/scrips/rushcd.js, and then cd into the folder specified.
  4. Finally, we need to provide at least 3 shell completion routines (bash, zsh, and pwsh) that specify how to complete rushcd.
  5. Last, we need a nice way to install the functionality of (3) and (4), since they need to be copied into the user's .zshrc or .profile or or other init-type terminal script. We could start with just a script that prints out exactly what the user needs to add to their profile script, but maybe we could enhance it with auto-adding it for the user. We need to decide the modality of this install -- can "rush install" do it for you with a special flag? How does a user find out about this cool extra feature if they don't read the docs for it?

I think this is the full feature, let me know your thoughts!

@octogonz octogonz added the design proposal A new direction that is complex enough to have open questions but specific enough to implement label May 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design proposal A new direction that is complex enough to have open questions but specific enough to implement
Projects
Status: General Discussions
Bug Triage
  
General Discussions
Development

No branches or pull requests

3 participants